2017-04-27

Java 腦袋學 Python Dict(Java Map)

Python dict 等於是 Java 的 Map,很像 Python list,差別在 list 用 int 做 index 對應到 item,而 dict 是用 key 對應到 value,而是「幾乎」可以使用任意資料型別作為 key。

用 dict() 或大括弧建立名值對(key-value pair),和 list 與 tuple 一樣是用中括弧存取。
d = dict() # 與 d = {} 相同
dict1 = { 'name': 'Neil', 'sex': 'M', 'children': 2 }
print(type(dict1)) # <class 'dict'>
print(dict1) # {'name': 'Neil', 'sex': 'M', 'children': 2}
print('I\'m ' + dict1['name']) #I'm Neil
當然也可以使用 int 做 index,不過就是不必從 0 開始,也沒有排序。

特別注意 dict() 是內建的 Python 函式名稱,也就是說 dict 是「保留字」,不要拿來做變數名稱,不然會 debug 到瘋掉。

順序

dict 和 list 有一點很大的不同,就是 dict 沒有順序(也是因為 index 不是 int 的關係,很難排序),影響最大的就是 list 的 slice 完全不能用。

但是這個特點有一個很特別的後果,即使在定義時 key 的順序不同,但只要結果是相同的(就是 key 和 value 完全相符),那就可以視為兩個相同的 dict,list 就不行這樣了。
print([ 'A', 'B', 'C' ] == [ 'A', 'B', 'C' ]) # True
print([ 'A', 'B', 'C' ] == [ 'A', 'C', 'B' ]) # False
print({ 'name': 'Neil', 'sex': 'M' } == { 'sex': 'M', 'name': 'Neil' }) # True
可以使用 Python 內建函式 sorted() 對 dict 的 key 進行排序。
dict1 = { 'name': 'Neil', 'sex': 'M', 'children': 2 }
print(dict1.keys()) # dict_keys(['name', 'sex', 'children'])
print(sorted(dict1.keys())) # ['children', 'name', 'sex']
存取不存在的 dict key 會得到 KeyError,存取不存在的 list index 會得到 IndexError。

dict.keys(), dict.values(), dict.items()

keys() 和 values() 都是回傳 list-like,而 items() 回傳的是 list-like of tuple。
dict1 = { 'name': 'Neil', 'sex': 'M', 'children': 2 }
print(dict1.keys()) # dict_keys(['name', 'sex', 'children']),順序是隨機的
print(dict1.values()) # dict_values(['Neil', 'M', 2])
print(dict1.items()) # dict_items([('name', 'Neil'), ('sex', 'M'), ('children', 2)])
print(len(dict1)) # 3,指 key 的數量
list-like 不是真的 list,不能修改,但可以用在 for 迴圈裡,也可以用 list() 轉成真的 list。
dict1 = { 'name': 'Neil', 'sex': 'M', 'children': 2 }
for k, v in dict1.items():
    print(str(k) + ' > ' + str(v))
# name > Neil
# sex > M
# children > 2
for k in dict1: # 也可以這樣簡單用
    print(str(k) + ' > ' + str(dict1[k]))
也可以使用 in 與 not in 檢查 keys() 與 values() 是否存在。
print('sex' in dict1.keys()) # True
print('sex' in dict1) # True,等同於 dict1.keys()
print(2 in dict1.values()) # True

dict、tuple 與 zip

可以使用 list of tuple 或者 zip 建立或修改 dict
# 用 list of tuple 建立 dict
d1 = dict([('Name', 'Neil'), ('Food', 'Banana'), ('Sport', 'Jogging')])
print(d1) # {'Name': 'Neil', 'Food': 'Banana', 'Sport': 'Jogging'}

# 用 zip 建立 dict
d2 = dict(zip(['Name', 'Food', 'Sport'], ['Neil', 'Banana', 'Jogging']))
print(d2) # {'Name': 'Neil', 'Food': 'Banana', 'Sport': 'Jogging'}

# 用 list of tuple 更新 dict
d1.update([('Food', 'Pineapple'), ('Learning', 'Python')])
print(d1) # {'Name': 'Neil', 'Food': 'Pineapple', 'Sport': 'Jogging', 'Learning': 'Python'}

# 用 zip 更新 dict
d2.update(zip(['Name', 'Like'], ['Emma', 'Sleep']))
print(d2) # {'Name': 'Emma', 'Food': 'Banana', 'Sport': 'Jogging', 'Like': 'Sleep'}

Hash 演算

不同於 list,dict 的 key 是用 Hash 演算,所以查詢非常快,在進行 in 查值時(x in list 或者 x in dict),list 的查詢時間是與 item 數量等比,但是 dict 的查詢時間不受 item 數量的影響。

一開始提到「幾乎」可以使用任意資料型別作為 dict 的 key,原因就在作為 key 的物件必須能進行 Hash 演算(Hashable),而 Hashable 的一個大前提就是 immutable,不然加入後又去變更值,就會改變 Hash 值,進而破壞 dict key 必須 unique 的規矩。

因此 list 與 dict 都不能做 dict 的 key,不過做 dict 的 value 是沒有限制的。

這裡發現第一個 tuple 存在的需求,tuple 可以視為唯讀的 list,也就是 immutable,當然可以作為 dict 的 key。

dict.get(), dict.setdefault()

不想每次存取前都用 in 與 not int 檢查 key 是否存在,可以改用 get() 與 setdefault()。
dict1 = { 'name': 'Neil', 'sex': 'M', 'children': 2 }
print(dict1.get('name', 'CW')) # Neil,已存在,回傳原值
print(dict1) # {'name': 'Neil', 'sex': 'M', 'children': 2}
print(dict1.get('married', True)) # True
print(dict1) # {'name': 'Neil', 'sex': 'M', 'children': 2}
print(dict1.setdefault('favorite', 'banana')) # banana
print(dict1) # {'name': 'Neil', 'sex': 'M', 'children': 2, 'favorite': 'banana'}
print(dict1.setdefault('favorite', 'chocolate')) # banana
print(dict1) # {'name': 'Neil', 'sex': 'M', 'children': 2, 'favorite': 'banana'}
get() 不存在的 key 不會修改 dict,setdefault() 不存在的 key 才會修改 dict。

使用 setdefault() 統計字串中字元出現的次數。
def charCount(s):
    count = {}
    for c in s:
        count.setdefault(c, 0)
        count[c] += 1
    return count
get() 的作法更簡潔。
def charCount(s):
    count = {}
    for c in s:
        count[c] = count.get(c, 0) + 1
    return count

輸出

利用 pprint 可以輸出漂亮的 dict,尤其是巢狀堆疊時。
dict1 = { 'name': 'Neil', 'sex': 'M', 'children': 2, 'nested':
    { 'name': 'Neil', 'sex': 'M', 'children': 2, 'nested':
        { 'name': 'Neil', 'sex': 'M', 'children': 2 }}}
print(dict1)
import pprint
pprint.pprint(dict1)

有沒注意到,除了 list 與 str 加上續行符號可以忽略縮排,dict 也是,猜測 tuple 應該也可以吧。
---
---
---

沒有留言:

張貼留言