說明一下這個代碼所有的考量.
首先是 順序,這裡的順序有兩個部分,一個是輸出行的順序,一個是項目合併之後的順序.我們觀察到:
pr333 sd23a2 thisisa 1001 1005
pr333 sd23a2 sentence 1001 1005
pr33w sd11aa we 1022 1002
pr33w sd11aa have 1022 1002
pr33w sd11aa adream 1033 1002
要變成:
pr333 sd23a2 thisisa|sentence 1001 1005
pr33w sd11aa we|have|adream 1022|1033 1002
輸出行的順序要顧及: pr333 先於 pr33w
項目合併之後的順序要顧及: thisisa 先於 sentence
這代表我們 使用的資料型態必須要能維持順序
其次是速度,我們都知道序列型態是線性搜尋,為了效率,使用映射型態是比較好的.
三種考量之下,就如同 moling3650 大所言,OrderedDict 是個好選擇.這可以解決行輸出的問題,不過合併項目由於只需要用到 key 而不需要用到 value 所以使用 OrderedDict 有點可惜,不過目前標準庫中沒有 OrderSet 的選擇,所以只好將就著用一下.
有關於 OrderedDict 可以參閱 OrderedDict
最後 linkse7en 大的觀點非常好,這類文檔處理的問題,如果能夠邊讀邊寫,邊讀邊處理絕對是 有效率(因為只需要進行一次文檔的走訪)(討論請見評論部分 moling 大的觀點) 且 省資源(馬上輸出完畢,不必浪費空間儲存資料).但因為考慮到有可能重複的 數據對 有可能跨行出現,所以依然是多花一點資源來確保穩固性.
代碼(Python3):
from collections import OrderedDict
data = OrderedDict()
DPAIR = slice(0,2)
MSG = slice(2,None)
with open('data.txt', 'r') as reader:
for line in reader:
line = line.strip()
items = tuple(line.split())
msgs = data.setdefault(items[DPAIR], [OrderedDict({}) for msg in items[MSG]])
for idx, msg in enumerate(msgs):
msg.setdefault(items[MSG][idx], None)
for (dp1, dp2), msgs in data.items():
print(dp1, dp2, *['|'.join(msg.keys()) for msg in msgs])
關於代碼部分也做個說明(也許我寫的不是最好,但有些心得可以分享).
首先是 slice 類的應用.
身為一個 Python programmer,我們對 序列型態 取切片(slicing) 應該都不陌生.
items[start:stop:step]
其實可以寫成:
items[slice(start, stop, step)]
# example
items[:5] 可以寫成 items[slice(0,5)]
items[7:] 可以寫成 items[slice(7,None)]
那好處是什麼呢?
我們可以利用這個特性對切片進行命名,以這個問題的代碼為例,原本要取出 數據對 與 其他資料 可以用:
items = tuple(line.split())
items[0:2] # 這是用來做 key 的數據對
items[2:] # 這是其他的資料項
但是這種方式其實閱讀起來不夠清晰,我們可以幫這兩個範圍取個名字,所以:
DPAIR = slice(0,2)
MSG = slice(2,None)
items[DPAIR] # 這是用來做 key 的數據對
items[MSG] # 這是其他的資料項
我們可以用比較優雅易讀的方式來從 items 中取值.
其次是 setdefault,這個函數相當實用,舉例:
dic.setdefault(key, default_value)
如果字典(或其他相符的映射型態)中存在鍵值 key 則回傳 dic[key] 否則回傳自動在字典中插入新的鍵值對 dic[key] = default_value 並且回傳 default_value.
最後一個想分享的是嵌套 tuple 的拆解:
for (a, b), c, d in ((1,2) ,3, 4):
print(a, b, c, d) # 印出 1 2 3 4
這個技巧可以很方便的用來拆解巢狀嵌套的 tuple.
感謝大家不嫌我話多...