Python 入門指南 5.0

單元 27 - 解碼

~~學習進度表~~

解碼 (decoding) 需要用到與編碼相同的轉換表格

Tbana kw dm wvuur.
×                 

012345678910111213141516171819202122232425
abcdefghijklmnopqrstwuvxyz

所謂的解碼也就是將編碼 (encoding) 過的小寫英文字母回覆成原來的英文小寫字母,由上圖可以看出,我們儲存密碼表所用的字串 (string) ,恰巧依索引值 (index) 可推回原來的英文小寫字母,這是說,索引值 0 編碼後為 'q' 的話,因為照順序的字母表索引值 0'a' 變成 'q' ,所以解碼就是把 'q' 換成 'a'

換句話說,我們只要找到編碼後字元在字母表中的索引值,再加上 DIFF 就可以轉換成原本的字母,實際上我們需要用到巢狀迴圈 (nested loop) ,巢狀迴圈就是在迴圈 (loop) 中有其他的迴圈,第一層迴圈取得解碼字串的所有字元,第二層迴圈則是計算編碼後字元在字母表中的索引值。

這就是說對單一英文句子而言,我們需要外層迴圈判斷每個字元是否為英文小寫字母,若是英文小寫字母,我們就需要內層迴圈找出對應的索引值。由這樣的概念設計的解碼方法 (method) toDecode() ,加進 encrypt07.py 中,如下

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
# 使用標準程式庫中 random 的 randint()
from random import randint

# 定義 Encrypt 類別
class Encrypt:
    # 定義建構子
    def __init__(self):
        # 建立密碼表
        self.set_code()

    # _code 的 getter
    @property
    def code(self):
        return self.__code

    # _code 的 setter
    def set_code(self):
        # 取得 a 、 b 值
        a = 0
        b = 0
        while a % 2 == 0:
            a = randint(0, 9)
            b = randint(0, 9)
        # 利用公式建立密碼表
        self.__code = ""
        c = "a"
        i = 0
        while i < 26:
            x = c
            y = ord(x) * a + b
            m = y % 26
            self.__code += chr(m + 97)
            c = chr(ord(c) + 1)
            i += 1

    # 編碼的方法
    def toEncode(self, str):
        # 暫存編碼結果的字串
        result = ""

        # 利用迴圈走完參數字串的所有字元
        for c in str:
            # 判斷該字元是否為英文小寫字母
            # 若是英文小寫字母就進行編碼轉換
            c1 = ord(c) >= 97
            c2 = ord(c) <= 122
            if c1 and c2:
                m = ord(c) - 97
                result += self.code[m]
            else:
                result += c

        # 結束回傳編碼過的字串
        return result

    # 解碼的方法
    def toDecode(self, str):
        # 暫存解碼結果的字串
        result = ""

        i = 0
        # 第一層迴圈逐一取得每一個字元
        while i < len(str):
            # 判斷該字元是否為英文小寫字母
            # 若是英文小寫字母就進行解碼轉換
            i1 = ord(str[i]) >= 97
            i2 = ord(str[i]) <= 122
            if i1 and i2:
                j = 0
                # 第二層迴圈尋找該字元在密碼表中
                # 的索引值,加上 DIFF 就可轉換
                # 回原本的字元
                while j < len(self.code):
                    if str[i] == self.code[j]:
                        result += chr(j + 97)
                    j += 1
            else:
                result += str[i]

            i += 1

        # 結束回傳解碼過的字串
        return result

# 測試部分
if __name__ == '__main__':
    e = Encrypt()
    print()
    print(e.code)
    s1 = "There is no spoon."
    print("Input : " + s1)
    s2 = e.toEncode(s1)
    print("Encode: " + s2)
    s3 = e.toDecode(s2)
    print("Decode: " + s3)
    print()

# 檔名: encrypt07.py
# 說明:《Python入門指南》的範例程式
# 網站: http://kaiching.org
# 作者: 張凱慶
# 時間: 2023 年 5 月

巢狀迴圈是迴圈中包含另一個迴圈,由於我們利用縮排的方式編輯程式碼,看起來內層迴圈像是凹陷進去的巢,故稱之為巢狀迴圈。

第一層迴圈,也就是外層迴圈會依序取得英文句子的每個字元,然後判斷是否為英文小寫字母,如果該字元是英文小寫字母,就會啟動內層迴圈找出表格中對應的索引值出來。控制變數 i 在最後會遞增,接著判斷下一個字元。

注意,這個巢狀迴圈用了兩個 while 、兩個 if ,依順序 whileifwhileif ,這是我們打算讓程式執行的順序,若是沒有依照這樣的順序,程式可能會跑出無法預期的結果。

Encrypt 類別 (class) 到這邊大致已開發完成,最後的測試部分也加入了解碼的相關程式碼

86
87
88
89
90
91
92
93
94
95
96
if __name__ == '__main__':
    e = Encrypt()
    print()
    print(e.code)
    s1 = "There is no spoon."
    print("Input : " + s1)
    s2 = e.toEncode(s1)
    print("Encode: " + s2)
    s3 = e.toDecode(s2)
    print("Decode: " + s3)
    print()

執行結果如下

$ python encrypt07.py
 
wfoxgpyhqzirajsbktcludmven
Input : There is no spoon.
Encode: Thgtg qc js cbssj.
Decode: There is no spoon.
 
$

結果如預期,下面我們進入 GUI 之前先岔開討論一個問題,就是我們程式雖然順利跑出結果,可是一點也不 Python 說,另外還有幾個小問題,因此我們要先來重構 (refactoring) 程式碼。

中英文術語對照
類別class
解碼decoding
編碼encoding
索引值index
迴圈loop
方法method
巢狀迴圈nested loop
重構refactoring
字串string
重點整理
1. 解碼與編碼用到相同的轉換表格,因為索引值 0 等於 'a' ,索引值 1 等於 'b' ,餘下可類推。
2. 巢狀迴圈是指迴圈中有其他迴圈,因為用縮排的方式編排程式碼,凹陷下去的地方看起來像鳥巢,故有此名。
3. 解碼利用巢狀迴圈進行,第一層迴圈逐一取得每一個字元,第二層迴圈尋找該字元在密碼表中的索引值。
問題與討論
1. 為什麼編碼跟解碼可以用一樣的轉換表格?
2. 可以不用巢狀迴圈來解碼嗎?
3. 巢狀迴圈可以用一樣名稱的控制變數嗎?
練習
1. 寫一個程式 exercise2701.py ,利用標準程式庫 random 中的 shuffle() ,攪亂具有 "0""9" 不重複數字的數字字串列,以此建立猜不重複數字遊戲的四位數答案。 參考程式碼
2. 承上題,將新程式寫在 exercise2702.py 中,設定第一個數字不能為零。 參考程式碼
3. 承上題,將新程式寫在 exercise2703.py 中,設計 Guess 類別,利用 手set_answer() 方法建立答案。 參考程式碼
4. 承上題,將新程式寫在 exercise2704.py 中,在 Guess 類別中增加 repeat_test() 方法,檢查輸入是否有重複數字。 參考程式碼
5. 承上題,將新程式寫在 exercise2705.py 中,在 Guess 類別中增加 find_AB() 方法, 計算輸入的 A 、 B 值。 參考程式碼
6. 承上題,將新程式寫在 exercise2706.py 中,在命令列設計一次性的遊戲進行方式。 參考程式碼
7. 承上題,將新程式寫在 exercise2707.py 中,加入迴圈直到猜對才結束遊戲。 參考程式碼
8. 承上題,將新程式寫在 exercise2708.py 中,加入計算猜測次數。 參考程式碼
9. 承上題,將新程式寫在 exercise2709.py 中,將命令列執行部分整理到 guess_game() 函數中。 參考程式碼
10. 承上題,將新程式寫在 exercise2710.py 中,改寫 guess_game() 函數,讓使用者可以選擇猜測數字的數量。 參考程式碼

上一頁 單元 26 - 編碼
回 Python 入門指南 5.0 首頁
下一頁 單元 28 - 重構
回 Python 教材首頁
回程式語言教材首頁