list comprehension to build a nested dictionary from a list of tuples
我有从数据库中获取的由
1 | counts = [(4, 1, 4), (3, 5, 4), (2, 10, 4), (2, 10, 5)] |
其中每个元组的第一项是
我想把它放到字典里,这样我就可以很快地检索到计数了:给定一个
为了"手工"构建两级词典,我将编码:
1 | dict = {4:{1:4,5:3,10:2},5:{10:2}} |
号
其中
如何通过列表理解在dict键中创建"双深度"?或者,我是否需要使用嵌套的for循环,首先迭代唯一的
两个元组键
我建议放弃嵌套字典的想法,直接使用两个元组作为键。就像这样:
1 | d = { (user_id, analysis_type_id): count for count, analysis_type_id, user_id in counts} |
字典是哈希表。在Python中,每个两个元组都有一个哈希值(而不是两个哈希值),因此每个两个元组都是基于其(相对)唯一的哈希进行查找的。因此,这比查找两个单独密钥的散列(首先是
但是,要注意提前优化。除非你做了数百万次的查找,否则平板电脑
创建一个命名的元组来存储这些键可能比较方便。这样做:
1 2 | from collections import namedtuple IdPair = namedtuple("IdPair","user_id, analysis_type_id") |
。
然后在字典理解中使用它:
1 | d = { IdPair(user_id, analysis_type_id): count for count, analysis_type_id, user_id in counts} |
访问您感兴趣的计数:
1 2 | somepair = IdPair(user_id = 4, analysis_type_id = 1) d[somepair] |
。
这有时很有用的原因是您可以这样做:
1 | user_id = somepair.user_id # very nice syntax |
。其他一些有用的选择
上述解决方案的一个缺点是查找失败。在这种情况下,您只会得到如下的回溯:
1 2 3 4 | >>> d[IdPair(0,0)] Traceback (most recent call last): File"<stdin>", line 1, in <module> KeyError: IdPair(user_id=0, analysis_type_id=0) |
这不是很有帮助;是
您可以通过创建自己的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class CountsDict(dict): """A dict for storing IdPair keys and count values as integers. Provides more detailed traceback information than a regular dict. """ def __getitem__(self, k): try: return super().__getitem__(k) except KeyError as exc: raise self._handle_bad_key(k, exc) from exc def _handle_bad_key(self, k, exc): """Provides a custom exception when a bad key is given.""" try: user_id, analysis_type_id = k except: return exc has_u_id = next((True for u_id, _ in self if u_id==user_id), False) has_at_id = next((True for _, at_id in self if at_id==analysis_type_id), False) exc_lookup = {(False, False):KeyError(f"CountsDict missing pair: {k}"), (True, False):KeyError(f"CountsDict missing analysis_type_id:" f"{analysis_type_id}"), (False, True):KeyError(f"CountsDict missing user_id: {user_id}")} return exc_lookup[(user_id, analysis_type_id)] |
。
就像普通的
但是,当您尝试访问丢失的一对时,只需将新的对添加到您的
1 2 3 | from collections import defaultdict my_dict = defaultdict(default_factory=int, ((user_id, analysis_type_id), count) for count, analysis_type_id, user_id in counts)) |
现在,如果尝试访问丢失的密钥,则计数将设置为零。但是,此方法的一个问题是所有键都将设置为零:
1 | value = my_dict['I'm not a two tuple, sucka!!!!'] # <-- will be added to my_dict |
。
为了防止这种情况发生,我们回到制作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from collections import defaultdict class CountsDict(defaultdict): """A dict for storing IdPair keys and count values as integers. Missing two-tuple keys are converted to an IdPair. Invalid keys raise a KeyError. """ def __getitem__(self, k): try: user_id, analysis_type_id = k except: raise KeyError(f"The provided key {k!r} is not a valid key.") else: # convert two tuple to an IdPair if it was not already k = IdPair(user_id, analysis_type_id) return super().__getitem__(k) |
号
就像普通的
1 2 | my_dict = CountsDict(default_factory=int, ((user_id, analysis_type_id), count) for count, analysis_type_id, user_id in counts)) |
号
注意:在上面的例子中,我没有这样做,在创建实例时,两个元组键被转换为
在所有这些中,更有用的选项完全取决于您的用例。
最可读的解决方案是使用
1 2 3 4 5 6 | from collections import defaultdict dct = defaultdict(dict) # do not shadow the built-in 'dict' for x, y, z in counts: dct[z][y] = x dct # defaultdict(dict, {4: {1: 4, 5: 3, 10: 2}, 5: {10: 2}}) |
如果你真的想要一个一行的理解,你可以使用
1 2 | from itertools import groupby dct = {k: {y: x for x, y, _ in g} for k, g in groupby(sorted(counts, key=lambda c: c[2]), key=lambda c: c[2])} |
号
如果初始数据已经按用户ID排序,则可以保存排序。
您可以列出对具有条件的嵌套循环的理解,并将其中一个或多个条件用于元素选择:
1 2 3 4 5 6 7 8 9 10 11 | # create dict with tuples line_dict = {str(nest_list[0]) : nest_list[1:] for nest_list in nest_lists for elem in nest_list if elem== nest_list[0]} print(line_dict) # create dict with list line_dict1 = {str(nest_list[0]) list(nest_list[1:]) for nest_list in nest_lists for elem in nest_list if elem== nest_list[0]} print(line_dict1) Example: nest_lists = [("a","aa","aaa","aaaa"), ("b","bb","bbb","bbbb") ("c","cc","ccc","cccc"), ("d","dd","ddd","dddd")] Output: {'a': ('aa', 'aaa', 'aaaa'), 'b': ('bb', 'bbb', 'bbbb'), 'c': ('cc', 'ccc', 'cccc'), 'd': ('dd', 'ddd', 'dddd')}, {'a': ['aa', 'aaa', 'aaaa'], 'b': ['bb', 'bbb', 'bbbb'], 'c': ['cc', 'ccc', 'cccc'], 'd': ['dd', 'ddd', 'dddd']} |
您可以使用以下逻辑。它不需要导入任何包,只是我们应该正确地使用for循环。
埃多克斯1〔24〕
"输出为4:1:4,5:3,10:2,5:10:2"
这对于
1 2 3 4 5 6 7 8 9 10 11 12 | from collections import defaultdict counts = [(4, 1, 4), (3, 5, 4), (2, 10, 4), (2, 10, 5)] dct = defaultdict(dict) for count, analysis_type_id, user_id in counts: dct[user_id][analysis_type_id]=count dct # defaultdict(dict, {4: {1: 4, 5: 3, 10: 2}, 5: {10: 2}}) # if you want a 'normal' dict, you can finish with this: dct = dict(dct) |
号
或者,您只需在
1 2 3 4 5 6 7 8 | counts = [(4, 1, 4), (3, 5, 4), (2, 10, 4), (2, 10, 5)] dct = dict() for count, analysis_type_id, user_id in counts: dct.setdefault(user_id, dict()) dct[user_id][analysis_type_id]=count dct # {4: {1: 4, 5: 3, 10: 2}, 5: {10: 2}} |
号
我不认为你能很好地理解列表,但是没有必要害怕这种事情的for循环。