Java 入門指南

單元 17 - 編碼與解碼

~~學習進度表~~

編碼 (encoding) 需要用到轉換表格,我們利用陣列 (array) 儲存這個表格,簡單說,就是利用 Unicode 排列順序,相對到表格的對應關係

There is no spoon.
×                 

012345678910111213141516171819202122232425
qzirajsbktcludmvenwfoxgpyh

上面是用了如下的表格

code_array = "qzirajsbktcludmvenwfoxgpyh"

我們先來想一想程式如何完成編碼工作,假設是對以下的字串 (string) 進行編碼

"There is no spoon."

首先, 'T' 不是英文小寫字母,因此跳過,然後 'h''e''r''e' 都是英文小寫字母,對照表格,需要轉換為 'b''a''n''a' ,接下來遇到一個空格字元 ' ' ,也跳過,然後 'i''s' 也都是英文小寫字母,需要轉換為 'k''w' ,餘下類推。

所以需要利用一個迴圈 (loop) 進行上述編碼工作,逐一檢查字串中的每一個字元 (character) ,若是屬於英文小寫字母的編碼範圍就是 Unicode 編碼 97122 之間,我們先將該字元轉換為整數,然後減掉 97 就會是表格字串中對應字元索引值。

這是說,第 0 個字元(索引值為 0'T' 不在英文小寫字母編碼的範圍,因此程式不會處理,然後到第 1 個字元 'h' ,這是英文小寫字母編碼為 104 ,減去 97 之後為 7 ,對應到上面的表格會是 'b' ,因此得到的新字串第 1 個新字元就是 'b' ,餘下會一直進行重複的工作到字串結束為止。

因此,我們對 toEncode() 的設計如下

// 編碼的方法
public String toEncode(String s) {
    char[] cs = s.toCharArray();
    int i, ci, d, m;
    char r;
    String rs = "";
    Character cc;
    
    for (i = 0; i < cs.length; i++) {
        if (cs[i] >= 97 && cs[i] <= 122) {
            ci = cs[i];
            m = ci - 97;
            cs[i] = code[m];
        }
    }
    
    for (i = 0; i < cs.length; i++) {
        cc = new Character(cs[i]);
        rs = rs.concat(cc.toString());
    }
    
    return rs;
}

toEncode() 接收一個字串 s 當參數 (parameter) ,也回傳一個新字串 rss 就是要編碼的字串,而 rs 則是編碼過的字串。由於字串被設計成不可變的 (immutable) ,因此無法直接修改參數 s ,而要用一個新的字串變數。

由於接收的是字串,因此要先把字串轉換成字元陣列

char[] cs = s.toCharArray();

進行編碼轉換的迴圈

for (i = 0; i < cs.length; i++) {
    if (cs[i] >= 97 && cs[i] <= 122) {
        ci = cs[i];
        m = ci - 97;
        cs[i] = code[m];
    }
}

逐一判斷字元陣列中的字元,若該字元屬於英文小寫字母就進行編碼轉換。字元陣列的屬性 (field) length() 記錄陣列中的元素總數,迴圈的控制變數 i 最大不超過 length ,因為索引從 0 開始,最後一個元素的索引為 length

找到密碼表中要轉換的字元後,這裡直接將字元指派到本來的字元陣列中

cs[i] = code[m];

最後再把字元陣列中的所有元素合併成字串

for (i = 0; i < cs.length; i++) {
    cc = new Character(cs[i]);
    rs = rs.concat(cc.toString());
}

完整的實作檔案可以參考「範例程式碼」的 Encrypt06.java

解碼 (decoding) 需要用到與編碼相同的轉換表格,也就是要將編碼過的小寫英文字母回覆成原來的英文小寫字母,我們儲存密碼表所用的陣列,恰巧依索引值 (index) 可推回原來的英文小寫字母,這是說,索引值 0'q' ,所以是將原本的 'a' 變成 'q' ,因此,解碼就是依索引值重新加上 diff 即可。

實際上我們需要用到巢狀迴圈 (nested loop) ,也就是在迴圈中有其他的迴圈,對單一英文句子而言,我們需要一個迴圈判斷每個字元是否為英文小寫字母,若是英文小寫字母,我們就需要另一個迴圈找出對應的索引值。由這樣的概念設計的解碼方法 toDecode() ,如下

// 解碼的方法
public String toDecode(String s) {
    char[] cs = s.toCharArray();
    int i, j;
    char r;
    String rs = "";
    Character cc;
    
    for (i = 0; i < cs.length; i++) {
        if (cs[i] >= 97 && cs[i] <= 122) {
            for (j = 0; j <= code.length; j++) {
                if (cs[i] == code[j]) {
                    cs[i] = (char) (j + 97);
                    break;
                }
            }
        }
    }
    
    for (i = 0; i < cs.length; i++) {
        cc = new Character(cs[i]);
        rs = rs.concat(cc.toString());
    }
    
    return rs;
}

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

toDecode()toEncode() 相似,同樣需要一個字串當參數,結果也回傳一個字串。

解碼轉換由巢狀迴圈的部份來進行

for (i = 0; i < cs.length; i++) {
    if (cs[i] >= 97 && cs[i] <= 122) {
        for (j = 0; j <= code.length; j++) {
            if (cs[i] == code[j]) {
                cs[i] = (char) (j + 97);
                break;
            }
        }
    }
}

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

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

編譯後執行,結果如下

zejotydinsxchmrwbglqvafkpu
There is no spoon.
Titgt nl mr lwrrm.
There is no spoon.

編碼與解碼的結果都正確無誤, Encrypt 類別的發展已經大體完備,正式進入 GUI 開發之前,我們需要重新檢視 Encrypt 一下,看是否需要重構 (refactoring) 。

相關教學影片

上一頁 單元 16 - 修正後的數學公式
回 Java 入門指南首頁
下一頁 單元 18 - 重構
回 Java 教材首頁
回程式語言教材首頁
中英文術語對照
array陣列
character字元
decoding解碼
encoding編碼
field屬性
immutable不可變的
index索引值
loop迴圈
nested loop巢狀迴圈
parameter參數
refactoring重構
string字串
參考資料
1. The Java™ Tutorials: Declaring Classes
2. The Java™ Tutorials: Declaring Member Variables
3. The Java™ Tutorials: Defining Methods
4. The Java™ Tutorials: Providing Constructors for Your Classes
5. The Java™ Tutorials: Returning a Value from a Method
6. The Java™ Tutorials: Using the this Keyword
7. The Java™ Tutorials: Controlling Access to Members of a Class
8. The Java™ Tutorials: Understanding Class Members
9. The Java™ Tutorials: Initializing Fields
重點整理
1. 轉換表格利用字元陣列來儲存,字元陣列可用索引值存取元素,也就是字元。
2. 編碼時利用迴圈依序取得要編碼字串,先將編碼字串轉換成字元陣列,然後先判斷是否為英文小寫字母,如果是英文小寫字母就進行編碼。
3. 解碼用到巢狀迴圈,巢狀迴圈是指迴圈中還有另一個迴圈,第一層迴圈依序取得每一個解碼字元,第二層迴圈則是找出解碼字元在密碼表中的索引值,這個索引值也就是該字元在字母表中的索引值。
問題與討論
1. 要怎麼判斷一個字元是英文小寫字母或大寫字母?
2. 可以不用巢狀迴圈來解碼嗎?
3. 巢狀迴圈可以用一樣名稱的控制變數嗎?
練習
1. 承接上一個單元的猜數字遊戲,將新專案寫在 Exercise1701 中,在遊戲迴圈中檢查使用者輸入的長度,若是長度不等於 4 就印出提示訊息進行下一輪。
2. 承上題,將新專案寫在 Exercise1702 中,把答案 answer 設定為 "0123456789" ,然後加入以下程式碼
List<String> answerList = new ArrayList<String>(Arrays.asList(answer.split("")));
Collections.shuffle(answerList);
for (String s : answerList) {
    answer += s;
}
answer = answer.substring(0, 4);
這是利用 Collectionsshuffle() 方法攪亂十個阿拉伯數字的順序,再截取前四個數字當答案。