熱門文章

2023年8月7日 星期一

Python 字典 (dictionary) 及 C++ STL map

作者:王一哲
日期:2023年8月7日



前言


Python 的字典 (dictionary) 及 C++ STL map 非常相似,都是關聯式的容器,利用 key 值存取資料,以下是兩種的使用方法。


Python 字典 (dictionary)


建立字典


語法為
字典 = {key1: value1, key2: value2, ...}
key 的資料格式通常用是整數或字串,如果使用字串要在前後加上引號,單引號或雙引號皆可,只要有成對使用即可。key 不能重複,每個 key 會對應到一筆資料,資料可以是任意的格式。例如以下的程式碼,a 是以字串作為 key,資料為整數;b 的 key 與資料皆為整數。
a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50}
b = {1: 10, 2: 20, 3: 30, 4: 40, 5: 50}


由已存在的資料作為 key 建立字典


語法為
字典 = dict.fromkeys(keys, value)
例如以下的程式碼,以數組 keys 的內容作為 key,所有的資料皆為 1。
keys = ('A', 'B', 'C', 'D', 'E')
b = dict.fromkeys(keys, 1)


讀取資料


語法為
字典[key]
例如以下的程式碼,如果想要讀取 a 當中 key 為 "A" 的資料,以及讀取 b 當中 key 為 1 的資料,語法為
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 並停止程式。
另一種語法為 get
字典.get(key, key不存在時的預設回傳值)
如果 key不存在時的預設回傳值可省略,如果沒有設定會回傳 None。例如以下的程式碼
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


新增或改變資料


語法為
字典[key] = 資料
如果 key 不存在會新增這個 key 對應的資料,如果 key 已存在會改變這個 key 對應的資料。例如以下的程式碼
a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50}
a["F"] = 60  # 新增資料 "F": 60
a["A"] = 70  # 改變資料 "A": 70

另一種語法是利用 update,由另一個字典物件更新資料,語法為
字典1.update(字典2)
如果字典2的 key 於字典1中不存在會新增這個 key 對應的資料,如果 key 已存在會改變這個 key 對應的資料。例如以下的程式碼
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}


列出所有的 key


語法為
字典.keys()
不需要加上任何參數,例如以下的程式碼
a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50}
print(a.keys())  # 印出 dict_keys(['A', 'B', 'C', 'D', 'E'])


列出所有的資料


語法為
字典.values()
不需要加上任何參數,例如以下的程式碼
a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50}
print(a.values())  # 印出 dict_values([10, 20, 30, 40, 50])


取得長度


使用內建的 len,語法為
len(字典)
不需要加上任何參數,回傳值格式為 int。例如以下的程式碼
a = {"A": 10, "B": 20, "C": 30, "D": 40, "E": 50}
print(len(a))  # 印出 5


取得並移除指定的 key 及資料


語法為
字典.pop(key, key不存在時的預設回傳值)
如果沒有設定 key不存在時的預設回傳值,當 key 不存在時會回傳錯誤訊息 KeyError 並停止程式。例如以下的程式碼
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 並停止程式


取得並移除最後一筆資料


語法為
字典.popitem()
不需要任何參數,從字共中移除最後一筆資料,回傳值格式為數組 (tuple),內容為 (key, 資料)。例如以下的程式碼
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}


清除所有資料


語法為
字典.clear()
不需要任何參數。

複製字典物件

語法為
字典2 = 字典1.copy()
不需要任何參數,將字典1複製到字典2。不要使用等號指定內容,如果這樣做,在字典2的內容時也會改變字典1的內容。例如以下的程式碼
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}


C++ STL map 函式庫


要先引入函式庫,函式庫的官方說明書在此 cplusplus.com std::map
#include <map>

建立 map


語法為
// 産生空的 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}, ...};
</code></pre>
可以使用所有內建的資料格式,例如 int、float、string、char,甚至也可以使用自訂的資料格式。例如以下的程式碼
<pre><code class="language-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());


新增或改變資料


語法為
名稱[key] = 資料;
如果 key 不存在會新增這個 key 對應的資料,如果 key 已存在會改變這個 key 對應的資料。例如以下的程式碼。例如以下的程式碼
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}}


取出 key 對應的資料


語法有兩種
名稱[key];
名稱.at(key);
例如以下的程式碼
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 並中止程式


如果 key 不存在才填入資料

語法為
名稱.emplace(key, value);
例如以下的程式碼
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}}

也可以用 insert 及 pair 填入資料,語法為
名稱.insert(pair<key資料格式, value資料格式> (key, value));
接續上方的程式碼
a.insert(pair<char, int> ('D', 40));  // a 的內容變為 {{'A', 10}, {'B', 20}, {'C', 30}, {'D', 40}}


迭代器 iterator


迭代器可以用來取得物件對應的記憶體位址,有以下幾種:
名稱.begin();  // 回傳起點位址
名稱.end();    // 回傳終點位址
名稱.rbegin(); // 回傳終點位址,由後向前向讀取資料
名稱.rend();   // 回傳起點位址,由後向前向讀取資料
名稱.cbegin(); // 回傳起點位址,回傳值為常數
名稱.cend();   // 回傳終點位址,回傳值為常數
名稱.crbegin();// 回傳終點位址,由後向前向讀取資料,回傳值為常數
名稱.crend();  // 回傳起點位址,由後向前向讀取資料,回傳值為常數
迭代器的變數名稱通常是 it,寫法有兩種,例如以下的程式碼
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::iterator 只能用在 begin()、end(),還是用 auto 會比較方便。如果要印出 key 寫成 it->first,如果要印出 value 寫成 it->second。如果要依序印出 map 中所有的 key 及 value,可以配合迭代器處理,例如以下的程式碼
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輸出皆為
A 10
B 20
C 30
D 40
E 50
語法4、5輸出皆為
E 50
D 40
C 30
B 20
A 10


回傳指定 key 對應位址的迭代器


語法為
auto it = 名稱.find(key);

另外有兩個工具,lower_bound 及 upper_bound,語法為
auto it = 名稱.lower_bound(key);
auto it = 名稱.upper_bound(key);  // 回傳的位置包含這個 key 

還有一個工具可以同時回傳指定 key 對應的 lower_bound 及 upper_bound,語法為
auto ret = 名稱.equal_range(key);
回傳值分為兩個部分,ret.first 為 lower_bound,ret.second 為 upper_bound。例如以下的程式碼
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


刪除指定 key 或範圍的資料


語法為
名稱.erase(key);
名稱.erase(起點位址, 終點位址);
不包含終點位址。例如以下的程式碼
// 使用 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}}


清除所有資料


語法為
名稱.clear();
不需要加上任何參數,清空資料後 map 長度為 0。


交換兩個 map 的資料


語法為
名稱.swap(map2名稱);


檢查 map 是否為空


語法為
名稱.empty();
不需要加上任何參數,如果 map 是空的回傳 1,如果 map 中有資料回傳 0。


取得 map 長度


語法為
名稱.size();
不需要加上任何參數,回傳堆疊長度,格式為 size_t,沒有正負號的整數。


回傳 map 最大長度


語法為
名稱.max_size();
不需要任何參數,回傳值格式為 size_t,沒有正負號的整數。在我使用的系統中回傳值為 230584300921369395。


檢查輸入的項目是否在 map 中作為 key


語法為
名稱.count(項目);
例如以下的程式碼
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


結語


以上是我目前常用到的 Python 字典及 C++ STL map 功能,如果之後有遇到其它需求會再補充內容。


參考資料


  1. https://www.w3schools.com/python/python_ref_dictionary.asp
  2. cplusplus.com std::map




HackMD 版本連結:https://hackmd.io/@yizhewang/SkmMGt2jn

沒有留言:

張貼留言