list comprehension从元组列表构建嵌套字典

时间:2017-11-01 01:42:14

标签: python dictionary list-comprehension

我有从数据库中获取的user_idanalysis_type_id索引的数据(计数)。这是一个3元组的列表。样本数据:

counts  = [(4, 1, 4), (3, 5, 4), (2, 10, 4), (2, 10, 5)]

每个元组的第一项是count,第二项是analysis_type_id,最后一项user_id

我想将其放入字典中,因此我可以快速检索计数:给定user_idanalysis_type_id。它必须是一个两级字典。有没有更好的结构?

要“手动”构建两级字典,我会编码:

dict = {4:{1:4,5:3,10:2},5:{10:2}}

user_id是第一个dict密钥级别,analysis_type_id是第二个(子)密钥,count是字典中的值。

如何通过列表理解在dict键中创建“双深度”? 或者我是否需要求助于嵌套的for循环,我首先遍历唯一的user_id值,然后找到匹配的analysis_type_id并将这些计数填入...一次一个字典?

5 个答案:

答案 0 :(得分:6)

两个元组键

我建议放弃嵌套字典的想法,直接使用两个元组作为键。像这样:

d = { (user_id, analysis_type_id): count for count, analysis_type_id, user_id in counts}

字典是哈希表。在python中,每两个元组都有一个哈希值(不是两个哈希值),因此每两个元组根据其(相对)唯一哈希查找。因此,这比查找两个单独键的哈希值更快(大多数情况下快2倍)(首先是user_id,然后是analysis_type_id)。

然而,beware of premature optimization。除非你进行数百万次查询,否则单位dict的性能提升不太重要。在这里支持使用这两个元组的真正原因是两元组解决方案的语法和可读性远远优于其他解决方案 - 也就是说,假设绝大多数时候您将希望基于a访问项目一对值,而不是基于单个值的项目组。

考虑使用namedtuple

创建一个用于存储这些键的命名元组可能很方便。这样做:

from collections import namedtuple
IdPair = namedtuple("IdPair", "user_id, analysis_type_id")

然后在词典理解中使用它:

d = { IdPair(user_id, analysis_type_id): count for count, analysis_type_id, user_id in counts}

并按照以下方式访问您感兴趣的计数:

somepair = IdPair(user_id = 4, analysis_type_id = 1)
d[somepair]

这有时候有用的原因是你可以这样做:

user_id = somepair.user_id # very nice syntax

一些其他有用的选项

上述解决方案的一个缺点是查找失败的情况。在这种情况下,您将只获得如下所示的追溯:

>>> d[IdPair(0,0)]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: IdPair(user_id=0, analysis_type_id=0)

这不是很有帮助;它是user_id无法比拟的,还是analysis_type_id,还是两者兼而有之?

您可以通过创建自己的dict类型为自己创建一个更好的工具,为您提供包含更多信息的精彩回溯。它可能看起来像这样:

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)]

就像常规dict一样使用它。

但是,当您尝试访问缺失的对时,简单地将新对添加到dict(计数为零)可能更有意义。如果是这种情况,我会使用defaultdict并在访问丢失的密钥时将其设置为零(使用默认值int作为工厂函数)。像这样:

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))

现在,如果您尝试访问丢失的密钥,则计数将设置为零。但是,方法的一个问题是所有键都将设置为零:

value = my_dict['I'm not a two tuple, sucka!!!!'] # <-- will be added to my_dict

为了防止这种情况发生,我们回到创建CountsDict的想法,除非在这种情况下,您的特殊dict将是defaultdict的子类。但是,与常规defaultdict不同,它会检查以确保密钥在添加之前是有效类型。作为奖励,我们可以确保作为键添加的任何两个元组变为IdPair

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)

使用它就像常规defaultdict

一样
my_dict = CountsDict(default_factory=int, 
                     ((user_id, analysis_type_id), count) for count, analysis_type_id, user_id in counts))

注意:在上面我没有这样做,以便在创建实例时将两个元组键转换为IdPair(因为在实例创建期间没有使用__setitem__)。要创建此功能,我们还需要实现__init__方法的覆盖。

总结

在所有这些中,更有用的选项完全取决于您的用例。

答案 1 :(得分:2)

最易读的解决方案是使用guides,它可以保存嵌套循环,并且如果密钥已经存在则会检查:

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}})

如果你真的想要单线理解,你可以使用defaultdict这个笨拙:

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])}

如果您的初始数据已按user_id排序,则可以自行保存排序。

答案 2 :(得分:0)

这对defaultdict对象很有用。您可以创建一个defaultdict,其元素始终是dicts。然后你可以将计数填入正确的序列中,如下所示:

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)

或者您可以使用setdefault的标准词语:

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循环。

答案 3 :(得分:0)

您可以使用以下逻辑。它不需要导入任何包,只需要正确使用for循环。

counts = [(4, 1, 4), (3, 5, 4), (2, 10, 4), (2, 10, 5)] dct = {x[2]:{y[1]:y[0] for y in counts if x[2] == y[2]} for x in counts }

“”“输出将是{4:{1:4,5:3,10:2},5:{10:2}}”“”

答案 4 :(得分:0)

您可以列出带有条件的嵌套循环的理解,并将其中一个或多个用于元素选择:

void setup(){
....
  //Initialize Ticker every 40ms
  Data_Rec.attach_ms(40, 40ms_Data );
}

void 40ms_Data (){
WiFiClient client = server.available();

  Serial.println("40ms_Data A");
  if (client) {
      Serial.println("40ms_Data B");
    if (client .connected()){
       Serial.println("40ms_Data C");    
       client.println(40ms_Data [0]); 
    }
    else{
       client.stop(); 
    }
   }
}