# 字典 (dictionary) - 跟串列類似,都是一種儲存資料的容器 - 定義:contains unordered key-value pairs - 字典內的元素是以key-value對的形式存在,key值是不能重複的。 - 字典的 key 需要是 immutable(不可更改)且 hashable(唯一雜湊值 → 唯一)。 - 常見的 immutable 資料型別包括: - 數字(int, float) - 字串(str) - 元組(tuple) ## 字典的底層原理 ### 插入: 當我們初始化一個空字典時,CPython會初始化一個擁有8個None元素的一維Array、一個空的二維Array ```python= dict1 = {} ''' indices = [None, None, None, None, None, None, None, None] entries = [] ''' ``` 當我們在空字典加入一條數據,python會計算key的hash values,並對這個值取對8的餘數, 0代表這條數據是第一條數據。 ```python= hashValue = hash("小波") print(hashValue1) print(hashValue1 % 8) # 結果: # -5923010074522229261 # 3 ``` ```python= dict1['小波'] = 183 ''' indices = [None, None, None, 0, None, None, None, None] entries = [[-5923010074522229261, 小波的記憶體位址, 183的記憶體位址]] ''' ``` 當我們再加入一條數據時,迪西的hash values對8取餘數是7。 ```python= dict1['小波'] = 183 dict1['迪西'] = 244 ''' indices = [None, None, None, 0, None, None, None, 1] entries = [ [-5923010074522229261, 小波的記憶體位址, 183的記憶體位址], [-588543676937807417, 迪西的記憶體位址, 244的記憶體位址] ] ''' ``` ### 查詢 當我們想取得迪希所對應的value,python會根據餘數7找到 indices 中索引7的值是1,並去找entries索引1的值。 所以字典與list相比查詢速度很快,舉個例子: ```python= list1 = [10, 20, 30, 40, 50] 50 in list1 ``` 這段程式碼必須走訪整個list才能算出結果 當使用字典: ```python= dict1 = {10:1, 20:2, 30:3, 40:4, 50:5} 50 in dict1 ``` 只要將50 hash一下取對5的餘數就能在indices直接取得entries的索引值 資料量越大字典與list的查詢速度的差距就越大。 > 參考: > <a href='https://blog.csdn.net/weixin_47906106/article/details/121772942'>详解Python字典的底层原理——哈希表(Python面试必备)</a> > <a href='https://www.cnblogs.com/jiakecong/p/15881370.html'>python字典底层原理</a> > <a href='https://blog.csdn.net/be5yond/article/details/120021250'>Python 深入理解dict---为什么 dict 查询元素比 list 快?</a> ## 建立一個空字典 ```python= people = {} # 建立空字典 print("天線寶寶身高辭典", people) # 天線寶寶身高辭典 {} people['TA'] = 176 people['小波'] = 183 people['迪西'] = 244 people['拉拉'] = 259 people['丁丁'] = 300 print("天線寶寶身高辭典", people) # 天線寶寶身高辭典 {'TA': 176, '小波': 183, '迪西': 244, '拉拉': 259, '丁丁': 300} ``` ![upgit_20240813_1723541679.png](https://raw.githubusercontent.com/kcwc1029/obsidian-upgit-image/main/2024/08/upgit_20240813_1723541679.png) ## 字典基本操作 ```python dict1 = {'TA': 176, '小波': 183, '迪西': 244, '拉拉': 259, '丁丁': 300} print(dict1["丁丁"]) # 300 # 列出字典資料型態 print("資料型態是: ",type(dict1)) # 資料型態是: <class 'dict'> ``` ## 取值(更新) ```python person = {'TA': 176, '小波': 183, '迪西': 244, '拉拉': 259, '丁丁': 300} # NOTE: key() print(person.keys()) # dict_keys(['TA', '小波', '迪西', '拉拉', '丁丁']) # 檢查 key 是否存在 print('TA' in person) # 輸出: True print('助教' in person) # 輸出: False # NOTE: value() print(person.values()) # dict_values([176, 183, 244, 259, 300]) print(person['TA']) print(person.get('TA')) person['TA'] = 180 # 設置 value # NOTE: item (key-value 對) print(person.items()) # dict_items([('TA', 180), ('小波', 183), ('迪西', 244), ('拉拉', 259), ('丁丁', 300)]) # 遍歷 item for key, value in person.items(): print(f"Key: {key}, Value: {value}") ``` ## 新增字典特定元素 ```python person = {'TA': 176, '小波': 183, '迪西': 244, '拉拉': 259, '丁丁': 300} person["利姆路·坦派斯特"]=130 # 更新跟新增一樣 print(person) # {'TA': 176, '小波': 183, '迪西': 244, '拉拉': 259, '丁丁': 300, '利姆路·坦派斯特': 130} ``` <!-- ![image](https://hackmd.io/_uploads/rJIp4AjO0.png) --> <!-- ![image](https://hackmd.io/_uploads/B1WNoCodA.png) --> ## 刪除字典元素 ### 刪除字典特定元素 ```python person = {'TA': 176, '小波': 183, '迪西': 244, '拉拉': 259, '丁丁': 300, "利姆路·坦派斯特": 130} del person['小波'] print("新字典內容:", person) # 新字典內容: {'TA': 176, '迪西': 244, '拉拉': 259, '丁丁': 300, '利姆路·坦派斯特': 130} ``` ### 刪除字典所有元素 ```python person = {'TA': 176, '小波': 183, '迪西': 244, '拉拉': 259, '丁丁': 300, "利姆路·坦派斯特": 130} person.clear( ) print("新字典內容:", person) # 新字典內容: {} ``` ```python person = {'TA': 176, '小波': 183, '迪西': 244, '拉拉': 259, '丁丁': 300, "利姆路·坦派斯特": 130} copy = person.copy( ) print("位址 = ", id(person), " person元素 = ", person) print("位址 = ", id(copy), " copy元素 = ", copy) # 位址 = 1802586334400 person元素 = {'TA': 176, '小波': 183, '迪西': 244, '拉拉': 259, '丁丁': 300, '利姆路·坦派斯特': 130} # 位址 = 1802586329216 copy元素 = {'TA': 176, '小波': 183, '迪西': 244, '拉拉': 259, '丁丁': 300, '利姆路·坦派斯特': 130} ``` ## 取得字典元素數量 ```python person = {'TA': 176, '小波': 183, '迪西': 244, '拉拉': 259, '丁丁': 300, "利姆路·坦派斯特": 130} print("person字典元素數量= ", len(person)) # person字典元素數量= 6 ``` ## 檢驗是否存在於字典 ```python person = {'TA': 176, '小波': 183, '迪西': 244, '拉拉': 259, '丁丁': 300, "利姆路·坦派斯特": 130} key = input("請輸入鍵(key) = ") print("鍵(key) = ", key) value = input("請輸入值(value) = ") print("值(value) = ", value) if key in person: print(f"{key}已經在字典了") else: person[key] = value print("新的person字典內容 = ", person) # 鍵(key) = 蜜莉姆·拿渥 # 值(value) = 135 # 新的person字典內容 = {'TA': 176, '小波': 183, '迪西': 244, '拉拉': 259, '丁丁': 300, '利姆路·坦派斯特': 130, '蜜莉姆·拿渥': '135'} ``` ## 字典合併 ```python personA = {'TA': 176, '小波': 183} personB = {'利姆路·坦派斯特': 130, '蜜莉姆·拿渥': 135} personA.update(personB) # A的後面加B print(personA) # {'TA': 176, '小波': 183, '利姆路·坦派斯特': 130, '蜜莉姆·拿渥': 135} ``` ## 搭配字典使用的函數 ### len() ```python people = { "維爾德拉·坦派斯特":{ "身高":130, "瞳色": "金瞳", "萌點": {"傲嬌","中二病"} }, "蜜莉姆·拿渥":{ "別號": "破壞之暴君噠", "身高":135, "瞳色": "藍瞳", "萌點": {"裝","條紋過膝襪","噠"} } } # 列印字典元素個數 print("people字典元素個數", len(people)) # people字典元素個數 2 print("people字典元素個數['維爾德拉·坦派斯特']元素個數 ", len(people['維爾德拉·坦派斯特'])) # people字典元素個數['維爾德拉·坦派斯特']元素個數 3 print("people字典元素個數['蜜莉姆·拿渥']元素個數 ", len(people['蜜莉姆·拿渥'])) # people字典元素個數['蜜莉姆·拿渥']元素個數 4 ``` ### get() 如果沒有找到指定的值,就返回預設值 ```python person = {'TA': 176, '小波': 183, '迪西': 244, '拉拉': 259, '丁丁': 300, "利姆路·坦派斯特": 130} temp = person.get("蜜莉姆·拿渥") print(temp) # None temp = person.get("蜜莉姆·拿渥", 135) print(temp) # 135 ``` ### setdefault() - 這個方法基本上與 get()相同,不同之處在於 get()方法不會改變字典內容。使用setdefault()方法時若所搜尋的鍵不在,會將"鍵:值"加入字典,如果有設定預設值則將鍵:預設值加入字典,如果沒有設定預設值則將鍵:none 加入字典。 ```python= # key在字典內 person = {'TA': 176, "利姆路·坦派斯特": 130} temp = person.setdefault("蜜莉姆·拿渥") print("temp = ", temp) # temp = None print("person字典", person) # person字典 {'TA': 176, '利姆路·坦派斯特': 130, '蜜莉姆·拿渥': None} del person['蜜莉姆·拿渥'] temp = person.setdefault("蜜莉姆·拿渥", 135) print("temp = ", temp) # temp = 135 print("person字典", person) # person字典 {'TA': 176, '利姆路·坦派斯特': 130, '蜜莉姆·拿渥': 135} ``` # 集合(set) - 一個集合裡所有的鍵都不會重複,元素裡的內容可以是整數(int)、浮點數(float)、字串(string)、元組(tuple)、布林(boolen),但是不可以是串列(list)、字典(dict)、集合(set)。 - 因為集合不會包含重複的資料的特性,常用來進行去除重複的字元、或判斷元素間是否有交集、聯集或差集之類的關聯性。 ## 宣告 ```python # 方式1 fruits = ['apple', 'orange', 'apple', 'banana', 'orange'] x = set(fruits) # {'orange', 'apple', 'banana'} print(x) # 方式2 y = set(['apple', 'orange', 'apple', 'banana', 'orange']) print(y) # {'orange', 'apple', 'banana'} ``` ## add() 添加元素到集合中 ```python my_set = {1, 2, 3} my_set.add(4) print(my_set) # {1, 2, 3, 4} ``` ## clear() 清空集合中的所有元素。 ```python my_set = {1, 2, 3} my_set.clear() print(my_set) # set() ``` ## copy() 複製集合。 ```python original_set = {1, 2, 3} copied_set = original_set.copy() print(original_set) # {1, 2, 3} print(copied_set) # {1, 2, 3} ``` ## discard() 移除集合中的指定元素,如果元素不存在,则不发生任何操作。 ```python my_set = {1, 2, 3, 4} my_set.discard(3) print(my_set) # {1, 2, 4} ``` ## 集合差集 difference() ```python set1 = {1, 2, 3, 4} set2 = {3, 4, 5, 6} result = set1.difference(set2) # 输出: {1, 2} ``` ## 集合交集 intersection() ```python set1 = {1, 2, 3, 4} set2 = {3, 4, 5, 6} result = set1.intersection(set2) # 输出: {3, 4} ``` ## 判断是否沒有交集 isdisjoint() ```python set1 = {1, 2, 3} set2 = {4, 5, 6} result = set1.isdisjoint(set2) # True,因为两个集合没有交集 ``` ## issubset() 判断一个集合是否是另一个集合的子集 ```python set1 = {1, 2} set2 = {1, 2, 3, 4} result = set1.issubset(set2) # True,因为set1是set2的子集 ``` ## issuperset() 判断一个集合是否是另一个集合的超集 ```python set1 = {1, 2, 3, 4} set2 = {1, 2} result = set1.issuperset(set2) # True,因为set1是set2的超集 ``` ## union() ```python set1 = {1, 2, 3} set2 = {3, 4, 5} result = set1.union(set2) # 输出: {1, 2, 3, 4, 5} ``` ## set comprehension 如 list comprehension一樣, set也有set comprehension ```python= a = {x for x in 'abracadabra' if x not in 'abc'} # >>>a: {r, d} ``` > 參考: > [5. 資料結構— Python 3.12.5 說明文件](https://docs.python.org/zh-tw/3/tutorial/datastructures.html) ## lab作業題目 - 繳交方式 : 將程式碼以及程式輸出截圖放入名為'學號_Lab05'的資料夾,並上傳到github - 繳交時間 : 9/25 12:00 ### lab05_01 - 有一天助教需要紀錄每個學生的多次考試成績,並計算每個學生的平均成績。請你幫助教設計一個程式,輸入每個學生的多次考試成績,然後計算每個學生的平均成績。 - 學生的成績如下: ```python= scores = { 'Alice': [85, 87, 90], 'Bob': [78, 82, 89], 'Charlie': [95, 92, 88], 'David': [64, 68, 70], 'Eve': [90, 91, 85] } ``` - 範例程式輸出如下: ![upgit_20240813_1723541807.png](https://raw.githubusercontent.com/kcwc1029/obsidian-upgit-image/main/2024/08/upgit_20240813_1723541807.png) - 解答: ```python= scores = { 'Alice': [85, 87, 90], 'Bob': [78, 82, 89], 'Charlie': [95, 92, 88], 'David': [64, 68, 70], 'Eve': [90, 91, 85] } average_scores = {student: sum(grades) / len(grades) for student, grades in scores.items()} for student, avg in average_scores.items(): print(f"{student}: {avg:.2f}") ``` ### lab05_02 - 有一天助教需要紀錄班上每個學生的成績,並找到成績最高的學生。請你幫助教設計一個程式,輸入每個學生的成績,然後找到成績最高的學生。 - 學生的成績如下: ```python= scores = { 'Alice': 85, 'Bob': 92, 'Charlie': 78, 'David': 64, 'Eve': 90 } ``` - 範例程式輸出如下: ![upgit_20240813_1723541826.png](https://raw.githubusercontent.com/kcwc1029/obsidian-upgit-image/main/2024/08/upgit_20240813_1723541826.png) - 解答: ```python= scores = { 'Alice': 85, 'Bob': 92, 'Charlie': 78, 'David': 64, 'Eve': 90 } highest_score = max(scores.values()) top_student = [student for student, score in scores.items() if score == highest_score] print(f"成績最高的學生是 {top_student[0]},分數是 {highest_score}") ``` ### lab05_03 - 有一天助教需要紀錄班上每個學生的詳細資料,並找到年齡最大的學生。請你幫助教設計一個程式,輸入每個學生的詳細資料(包括年齡),然後找到年齡最大的學生。 - 學生的資料如下: ```python= students = { 'Alice': {'age': 20, 'grade': 'A'}, 'Bob': {'age': 22, 'grade': 'B'}, 'Charlie': {'age': 19, 'grade': 'A'}, 'David': {'age': 21, 'grade': 'C'}, 'Eve': {'age': 20, 'grade': 'B'} } ``` - 範例程式輸出如下: ![upgit_20240813_1723541850.png](https://raw.githubusercontent.com/kcwc1029/obsidian-upgit-image/main/2024/08/upgit_20240813_1723541850.png) - 解答: ```python= students = { 'Alice': {'age': 20, 'grade': 'A'}, 'Bob': {'age': 22, 'grade': 'B'}, 'Charlie': {'age': 19, 'grade': 'A'}, 'David': {'age': 21, 'grade': 'C'}, 'Eve': {'age': 20, 'grade': 'B'} } oldest_age = max(student['age'] for student in students.values()) oldest_student = [name for name, info in students.items() if info['age'] == oldest_age] print(f"年齡最大的學生是 {oldest_student[0]},年齡是 {oldest_age}") ``` ### lab05_04 - 有一天助教需要統計班上學生選修課程的聯集。請你幫助教設計一個程式,輸入每個學生選修的課程,然後找到所有學生選修過的所有課程。 - 學生選修的課程如下: ```python= students = { 'Alice': {'Math', 'Science'}, 'Bob': {'Math', 'History', 'Science'}, 'Charlie': {'Math'}, 'David': {'Science', 'History'}, 'Eve': {'Math', 'Science', 'History'} } ``` - 範例程式輸出如下: ![upgit_20240813_1723541878.png](https://raw.githubusercontent.com/kcwc1029/obsidian-upgit-image/main/2024/08/upgit_20240813_1723541878.png) - 解答: ```python= students = { 'Alice': {'Math', 'Science'}, 'Bob': {'Math', 'History', 'Science'}, 'Charlie': {'Math'}, 'David': {'Science', 'History'}, 'Eve': {'Math', 'Science', 'History'} } all_courses = set.union(*students.values()) print(f"所有學生選修過的課程有:{', '.join(all_courses)}") ``` ### lab05_05 - 有一天助教需要統計班上某兩個學生選修課程的差集。請你幫助教設計一個程式,輸入這兩個學生選修的課程,然後找到其中一個學生選修但另一個學生沒選修的課程。 - 請幫助教找到 student_A 選修但 student_B 沒選修的課程並輸出結果: - 學生選修的課程如下: ```python= student_A = {'Math', 'Science', 'Art'} student_B = {'Math', 'History'} ``` - 範例程式輸出如下: ![upgit_20240813_1723541896.png](https://raw.githubusercontent.com/kcwc1029/obsidian-upgit-image/main/2024/08/upgit_20240813_1723541896.png) - 解答: ```python= student_A = {'Math', 'Science', 'Art'} student_B = {'Math', 'History'} unique_courses = student_A.difference(student_B) print(f"student_A 選修但 student_B 沒選修的課程有:{', '.join(unique_courses)}") ```