# Python 字典 (dictionary) 及 C++ STL map > 作者:王一哲 > 日期:2023年8月7日 ## 前言 Python 的字典 (dictionary) 及 C++ STL map 非常相似,都是關聯式的容器,利用 key 值存取資料,以下是兩種的使用方法。 <br /> ## Python 字典 (dictionary) ### 建立字典 語法為 ```python 字典 = {key1: value1, key2: value2, ...} ``` key 的資料格式通常用是整數或字串,如果使用字串要在前後加上引號,單引號或雙引號皆可,只要有成對使用即可。key 不能重複,每個 key 會對應到一筆資料,資料可以是任意的格式。例如以下的程式碼,a 是以字串作為 key,資料為整數;b 的 key 與資料皆為整數。 ```python a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50} b = {1: 10, 2: 20, 3: 30, 4: 40, 5: 50} ``` <br /> ### 由已存在的資料作為 key 建立字典 語法為 ```python 字典 = dict.fromkeys(keys, value) ``` 例如以下的程式碼,以數組 keys 的內容作為 key,所有的資料皆為 1。 ```python keys = ('A', 'B', 'C', 'D', 'E') b = dict.fromkeys(keys, 1) ``` <br /> ### 讀取資料 語法為 ```python 字典[key] ``` 例如以下的程式碼,如果想要讀取 a 當中 key 為 "A" 的資料,以及讀取 b 當中 key 為 1 的資料,語法為 ```python a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50} b = {1: 10, 2: 20, 3: 30, 4: 40, 5: 50} print(a["A"]) # 印出 10 print(b[1]) # 印出 10 ``` 但如果 key 不存在,會回傳錯誤訊息 keyError 並停止程式。 <br /> 另一種語法為 get ```python 字典.get(key, key不存在時的預設回傳值) ``` 如果 key不存在時的預設回傳值可省略,如果沒有設定會回傳 None。例如以下的程式碼 ```python a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50} tmp = a.get("A", -1) # 回傳 10 tmp = a.pop("F", -1) # key 不存在,回傳 -1 tmp = a.pop("F") # key 不存在,回傳 None ``` <br /> ### 新增或改變資料 語法為 ```python 字典[key] = 資料 ``` 如果 key 不存在會新增這個 key 對應的資料,如果 key 已存在會改變這個 key 對應的資料。例如以下的程式碼 ```python a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50} a["F"] = 60 # 新增資料 "F": 60 a["A"] = 70 # 改變資料 "A": 70 ``` <br /> 另一種語法是利用 update,由另一個字典物件更新資料,語法為 ```python 字典1.update(字典2) ``` 如果字典2的 key 於字典1中不存在會新增這個 key 對應的資料,如果 key 已存在會改變這個 key 對應的資料。例如以下的程式碼 ```python a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50} b = {"F": 60, "G": 70, "H": 80} a.update(b) # a 的內容變為 {'A': 10, 'B': 20, 'C': 30, 'D': 40, 'E': 50, 'F': 60, 'G': 70, 'H': 80} c = {"A": -10, "C": -30, "E": -50} a.update(c) # a 的內容變為 {'A': -10, 'B': 20, 'C': -30, 'D': 40, 'E': -50, 'F': 60, 'G': 70, 'H': 80} ``` <br /> ### 列出所有的 key 語法為 ```python 字典.keys() ``` 不需要加上任何參數,例如以下的程式碼 ```python a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50} print(a.keys()) # 印出 dict_keys(['A', 'B', 'C', 'D', 'E']) ``` <br /> ### 列出所有的資料 語法為 ```python 字典.values() ``` 不需要加上任何參數,例如以下的程式碼 ```python a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50} print(a.values()) # 印出 dict_values([10, 20, 30, 40, 50]) ``` <br /> ### 取得長度 使用內建的 len,語法為 ```python len(字典) ``` 不需要加上任何參數,回傳值格式為 int。例如以下的程式碼 ```python a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50} print(len(a)) # 印出 5 ``` <br /> ### 取得並移除指定的 key 及資料 語法為 ```python 字典.pop(key, key不存在時的預設回傳值) ``` 如果沒有設定 key不存在時的預設回傳值,當 key 不存在時會回傳錯誤訊息 KeyError 並停止程式。例如以下的程式碼 ```python a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50} tmp = a.pop("E", None) # 回傳 50,a 的內容變為 {'A': 10, 'B': 20, 'C': 30, 'D': 40} tmp = a.pop("F", None) # key 不存在,回傳 None tmp = a.pop("F") # key 不存在,回傳錯誤訊息 KeyError 並停止程式 ``` <br /> ### 取得並移除最後一筆資料 語法為 ```python 字典.popitem() ``` 不需要任何參數,從字共中移除最後一筆資料,回傳值格式為數組 (tuple),內容為 (key, 資料)。例如以下的程式碼 ```python a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50} tmp = a.popitem() # 回傳 ('E', 50),a 的內容變為 {'A': 10, 'B': 20, 'C': 30, 'D': 40} ``` <br /> ### 清除所有資料 語法為 ```python 字典.clear() ``` 不需要任何參數。 <br /> ### 複製字典物件 語法為 ```python 字典2 = 字典1.copy() ``` 不需要任何參數,將字典1複製到字典2。不要使用等號指定內容,如果這樣做,在字典2的內容時也會改變字典1的內容。例如以下的程式碼 ```python a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50} b = a.copy() b["A"] = -10 # a 的內容仍然為 {'A': 10, 'B': 20, 'C': 30, 'D': 40, 'E': 50} c = a c["A"] = -10 # a 的內容變為 {'A': -10, 'B': 20, 'C': 30, 'D': 40, 'E': 50} ``` <br /> ## C++ STL map 函式庫 要先引入函式庫,函式庫的官方說明書在此 [cplusplus.com std::map](https://cplusplus.com/reference/map/map/)。 ```cpp #include <map> ``` <br /> ### 建立 map 語法為 ```cpp // 産生空的 map map<key資料格式, value資料格式> 名稱; // 由 map2 複製資料 map<key資料格式, value資料格式> 名稱 (map2名稱.begin(), map2名稱.end()); // 由 map2 複製資料 map<key資料格式, value資料格式> 名稱 (map2名稱); // 産生 map 並指定資料,可以不加等號 map<key資料格式, value資料格式> 名稱 {{key1, value1}, {key2, value2}, ...}; map<key資料格式, value資料格式> 名稱 = {{key1, value1}, {key2, value2}, ...}; ``` 可以使用所有內建的資料格式,例如 int、float、string、char,甚至也可以使用自訂的資料格式。例如以下的程式碼 ```cpp // a 是空的 map<char, int> a; // b 的內容為 {{'A', 10}, {'B', 20}, {'C', 30}, {'D', 40}, {'E', 50}} map<char, int> b = {{'A', 10}, {'B', 20}, {'C', 30}, {'D', 40}, {'E', 50}}; // 由 b 複製內容到 c map<char, int> c (b); // 由 b 複製內容到 d map<char, int> d (b.begin(), b.end()); ``` <br /> ### 新增或改變資料 語法為 ```cpp 名稱[key] = 資料; ``` 如果 key 不存在會新增這個 key 對應的資料,如果 key 已存在會改變這個 key 對應的資料。例如以下的程式碼。例如以下的程式碼 ```cpp map<char, int> a; a['A'] = 10; // a 的內容變為 {{'A', 10}} a['B'] = 20; // a 的內容變為 {{'A', 10}, {'B', 20}} a['C'] = 30; // a 的內容變為 {{'A', 10}, {'B', 20}, {'C', 30}} a['D'] = 40; // a 的內容變為 {{'A', 10}, {'B', 20}, {'C', 30}, {'D', 40}} a['E'] = 50; // a 的內容變為 {{'A', 10}, {'B', 20}, {'C', 30}, {'D', 40}, {'E', 50}} a['A'] = -10; // a 的內容變為 {{'A', -10}, {'B', 20}, {'C', 30}, {'D', 40}, {'E', 50}} ``` <br /> ### 取出 key 對應的資料 語法有兩種 ```cpp 名稱[key]; 名稱.at(key); ``` 例如以下的程式碼 ```cpp map<char, int> a {{'A', 10}, {'B', 20}, {'C', 30}, {'D', 40}, {'E', 50}}; cout << a['A'] << "\n"; // 印出 10 cout << a.at('A') << "\n"; // 印出 10 cout << a['F'] << "\n"; // 'F' 沒有對應的值,印出 0 cout << a.at('F') << "\n"; // 'F' 沒有對應的值,回傳錯誤訊息 std::out_of_range 並中止程式 ``` <br /> ### 如果 key 不存在才填入資料 語法為 ```cpp 名稱.emplace(key, value); ``` 例如以下的程式碼 ```cpp map<char, int> a {{'A', 10}}; // a 的內容為 {{'A', 10}} a.emplace('A', -10); // a 的內容仍為 {{'A', 10}} a.emplace('B', 20); // a 的內容變為 {{'A', 10}, {'B', 20}} a.emplace('C', 30); // a 的內容變為 {{'A', 10}, {'B', 20}, {'C', 30}} ``` <br /> 也可以用 insert 及 pair 填入資料,語法為 ```cpp 名稱.insert(pair<key資料格式, value資料格式> (key, value)); ``` 接續上方的程式碼 ```cpp a.insert(pair<char, int> ('D', 40)); // a 的內容變為 {{'A', 10}, {'B', 20}, {'C', 30}, {'D', 40}} ``` <br /> ### 迭代器 iterator 迭代器可以用來取得物件對應的記憶體位址,有以下幾種: ```cpp= 名稱.begin(); // 回傳起點位址 名稱.end(); // 回傳終點位址 名稱.rbegin(); // 回傳終點位址,由後向前向讀取資料 名稱.rend(); // 回傳起點位址,由後向前向讀取資料 名稱.cbegin(); // 回傳起點位址,回傳值為常數 名稱.cend(); // 回傳終點位址,回傳值為常數 名稱.crbegin();// 回傳終點位址,由後向前向讀取資料,回傳值為常數 名稱.crend(); // 回傳起點位址,由後向前向讀取資料,回傳值為常數 ``` 迭代器的變數名稱通常是 it,寫法有兩種,例如以下的程式碼 ```cpp map<char, int> a = {{'A', 10}, {'B', 20}, {'C', 30}, {'D', 40}, {'E', 50}}; map<char, int>::iterator it = a.begin(); auto it = a.begin(); ``` 但是 map<char, int>::iterator 只能用在 begin()、end(),還是用 auto 會比較方便。如果要印出 key 寫成 it->first,如果要印出 value 寫成 it->second。如果要依序印出 map 中所有的 key 及 value,可以配合迭代器處理,例如以下的程式碼 ```cpp= map<char, int> a = {{'A', 10}, {'B', 20}, {'C', 30}, {'D', 40}, {'E', 50}}; // 語法1 for(map<char, int>::iterator it = a.begin(); it != a.end(); it++) { cout << it->first << " " << it->second << "\n"; } // 語法2 for(auto it = a.begin(); it != a.end(); it++) { cout << it->first << " " << it->second << "\n"; } // 語法3 for(auto it = a.cbegin(); it != a.cend(); it++) { cout << it->first << " " << it->second << "\n"; } // 語法4 cout << "rbegin" << "\n"; for(auto it = a.rbegin(); it != a.rend(); it++) { cout << it->first << " " << it->second << "\n"; } // 語法5 cout << "crbegin" << "\n"; for(auto it = a.crbegin(); it != a.crend(); it++) { cout << it->first << " " << it->second << "\n"; } ``` 語法1、2、3輸出皆為 ```cpp A 10 B 20 C 30 D 40 E 50 ``` 語法4、5輸出皆為 ```cpp E 50 D 40 C 30 B 20 A 10 ``` <br /> ### 回傳指定 key 對應位址的迭代器 語法為 ```cpp auto it = 名稱.find(key); ``` <br /> 另外有兩個工具,lower_bound 及 upper_bound,語法為 ```cpp auto it = 名稱.lower_bound(key); auto it = 名稱.upper_bound(key); // 回傳的位置包含這個 key ``` <br /> 還有一個工具可以同時回傳指定 key 對應的 lower_bound 及 upper_bound,語法為 ```cpp auto ret = 名稱.equal_range(key); ``` 回傳值分為兩個部分,ret.first 為 lower_bound,ret.second 為 upper_bound。例如以下的程式碼 ```cpp map<char, int> a = {{'A', 10}, {'B', 10}, {'C', 20}, {'D', 10}, {'E', 20}}; auto ret = a.equal_range('B'); cout << ret.first->first << " " << ret.first->second << "\n"; // 印出 B 10 cout << ret.second->first << " " << ret.second->second << "\n"; // 印出 C 20 ``` <br /> ### 刪除指定 key 或範圍的資料 語法為 ```cpp 名稱.erase(key); 名稱.erase(起點位址, 終點位址); ``` 不包含終點位址。例如以下的程式碼 ```cpp // 使用 find map<char, int> a = {{'A', 10}, {'B', 20}, {'C', 30}, {'D', 40}, {'E', 50}}; a.erase('B'); // 刪除 'B' 及對應的資料,a 的內容變為 {'A', 10}, {'C', 30}, {'D', 40}, {'E', 50}} auto it = a.find('C'), it2 = a.find('E'); a.erase(it, it2); // 刪除 'C'、'E' 之間的 key 及對應的資料,a 的內容變為 {'A', 10}, {'E', 50}} // 使用 lower_bound 及 upper_bound map<char, int> b = {{'A', 10}, {'B', 10}, {'C', 20}, {'D', 10}, {'E', 20}}; auto lower = b.lower_bound('B'); auto upper = b.upper_bound('D'); b.erase(lower, upper); // 刪除 'B'、'C'、'D' 及對應的資料,b 的內容變為 {'A', 10}, {'E', 50}} ``` <br /> ### 清除所有資料 語法為 ```cpp 名稱.clear(); ``` 不需要加上任何參數,清空資料後 map 長度為 0。 <br /> ### 交換兩個 map 的資料 語法為 ```cpp 名稱.swap(map2名稱); ``` <br /> ### 檢查 map 是否為空 語法為 ```cpp 名稱.empty(); ``` 不需要加上任何參數,如果 map 是空的回傳 1,如果 map 中有資料回傳 0。 <br /> ### 取得 map 長度 語法為 ```cpp 名稱.size(); ``` 不需要加上任何參數,回傳堆疊長度,格式為 size_t,沒有正負號的整數。 <br /> ### 回傳 map 最大長度 語法為 ```cpp 名稱.max_size(); ``` 不需要任何參數,回傳值格式為 size_t,沒有正負號的整數。在我使用的系統中回傳值為 230584300921369395。 <br /> ### 檢查輸入的項目是否在 map 中作為 key 語法為 ```cpp 名稱.count(項目); ``` 例如以下的程式碼 ```cpp map<char, int> a = {{'A', 10}, {'B', 10}, {'C', 20}, {'D', 10}, {'E', 20}}; cout << a.count('C') << "\n"; // 印出 1 cout << a.count('F') << "\n"; // 印出 0 ``` <br /> ## 結語 以上是我目前常用到的 Python 字典及 C++ STL map 功能,如果之後有遇到其它需求會再補充內容。 <br /> ## 參考資料 1. https://www.w3schools.com/python/python_ref_dictionary.asp 2. [cplusplus.com std::map](https://cplusplus.com/reference/map/map/) --- ###### tags:`C++`、`Python`