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 ,依順序 while 、 if 、 while 、 if ,這是我們打算讓程式執行的順序,若是沒有依照這樣的順序,程式可能會跑出無法預期的結果。

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 教材首頁
回程式語言教材首頁