【问题标题】:Append to a dict of lists with a dict comprehension附加到具有字典理解的列表字典
【发布时间】:2012-06-30 18:13:47
【问题描述】:

假设我有一个很大的单词列表。举个例子:

>>> with open('/usr/share/dict/words') as f:
...     words=[word for word in f.read().split('\n') if word]

如果我想通过这个单词列表的第一个字母建立索引,这很容易:

d={}
for word in words:
   if word[0].lower() in 'aeiou':
       d.setdefault(word[0].lower(),[]).append(word)
       # You could use defaultdict here too...

结果如下:

{'a':[list of 'a' words], 'e':[list of 'e' words], 'i': etc...}

有没有办法用 Python 2.7、3+ 字典理解来做到这一点?换句话说,dict理解语法是否可以在构建dict时附加由键表示的列表?

即:

  index={k[0].lower():XXX for k in words if k[0].lower() in 'aeiou'}

其中 XXX 在创建 index 时为键执行附加操作或列表创建。

编辑

接受建议和基准测试:

def f1():   
    d={}
    for word in words:
        c=word[0].lower()
        if c in 'aeiou':
           d.setdefault(c,[]).append(word)

def f2():
   d={}
   {d.setdefault(word[0].lower(),[]).append(word) for word in words 
        if word[0].lower() in 'aeiou'} 

def f3():
    d=defaultdict(list)                       
    {d[word[0].lower()].append(word) for word in words 
            if word[0].lower() in 'aeiou'}         

def f4():
    d=functools.reduce(lambda d, w: d.setdefault(w[0], []).append(w[1]) or d,
       ((w[0].lower(), w) for w in words
        if w[0].lower() in 'aeiou'), {}) 

def f5():   
    d=defaultdict(list)
    for word in words:
        c=word[0].lower() 
        if c in 'aeiou':
            d[c].append(word)       

产生这个基准:

   rate/sec    f4     f2     f1     f3     f5
f4       11    -- -21.8% -31.1% -31.2% -41.2%
f2       14 27.8%     -- -11.9% -12.1% -24.8%
f1       16 45.1%  13.5%     --  -0.2% -14.7%
f3       16 45.4%  13.8%   0.2%     -- -14.5%
f5       18 70.0%  33.0%  17.2%  16.9%     --

使用默认 dict 的直接循环最快,其次是集合理解和 setdefault 循环。

感谢您的想法!

【问题讨论】:

  • 除非你是打代码的,否则为了简洁,请不要写不可读的代码。 Readability counts.
  • {w[0]:[ww for ww in words if ww.startswith(w[0])] for w in words}
  • @astynax: 聪明,但真的很慢
  • setdefault 当然更好,但那是 dict 理解 :)
  • reduce(lambda d, w: d.setdefault(w[0], []).append(w) or d, words, {}),但这不是字典理解 :)

标签: python dictionary


【解决方案1】:

否 - 字典推导式旨在在每次迭代时生成不重叠的键;他们不支持聚合。对于这个特定的用例,循环是有效完成任务的正确方法(在线性时间内)。

【讨论】:

    【解决方案2】:

    使用 dict 理解是不可能的(至少很容易或直接)。

    使用集合或列表理解是可能的,但可能会滥用语法:

    # your code:    
    d={}
    for word in words:
       if word[0].lower() in 'aeiou':
           d.setdefault(word[0].lower(),[]).append(word)        
    
    # a side effect set comprehension:  
    index={}   
    r={index.setdefault(word[0].lower(),[]).append(word) for word in words 
            if word[0].lower() in 'aeiou'}     
    
    print r
    print [(k, len(d[k])) for k in sorted(d.keys())]  
    print [(k, len(index[k])) for k in sorted(index.keys())]
    

    打印:

    set([None])
    [('a', 17094), ('e', 8734), ('i', 8797), ('o', 7847), ('u', 16385)]
    [('a', 17094), ('e', 8734), ('i', 8797), ('o', 7847), ('u', 16385)]
    

    在迭代words 列表后,集合推导生成一个带有setdefault() 方法结果的集合。在这种情况下,set([None]) 的总和。它还会产生您想要的生成列表字典的副作用。

    它不像直接循环结构那样可读(恕我直言),应该避免(恕我直言)。它不会更短,也可能不会更快。这是关于 Python 的更有趣的琐事,而不是有用的——恕我直言……也许是为了赢得赌注?

    【讨论】:

    • 感谢您告诉我为什么我不应该这样做!
    【解决方案3】:

    我会使用filter:

    >>> words = ['abcd', 'abdef', 'eft', 'egg', 'uck', 'ice']
    >>> index = {k.lower() : list(filter(lambda x:x[0].lower() == k.lower(),words)) for k in 'aeiou'}
    >>> index
    {'a': ['abcd', 'abdef'], 'i': ['ice'], 'e': ['eft', 'egg'], 'u': ['uck'], 'o': []}
    

    【讨论】:

    • 如果words 是一个迭代器,这将中断。
    • 但 OP 正在获取一个单词列表,即iterable
    • 当然 - 但这种方法也有其他缺点(例如,运行时间增加五倍 - 根据输入的大小,常数因素 有时会很明显. 与仅使用 for 循环相比,它的可读性也较差。
    • 我同意,在可读性方面,for 循环总是很好。是的,这种方法也有点贵O(len('aeiou')*len(words))
    【解决方案4】:

    这不完全是 dict 理解,而是:

    reduce(lambda d, w: d.setdefault(w[0], []).append(w[1]) or d,
           ((w[0].lower(), w) for w in words
            if w[0].lower() in 'aeiou'), {})
    

    【讨论】:

      【解决方案5】:

      不回答听写理解的问题,但它可能会帮助搜索此问题的人。在一个简化的示例中,当将运行中不断增长的列表填充到新字典中时,请考虑在列表推导中调用一个函数,诚然,这只不过是一个循环。

      def fill_lists_per_dict_keys(k, v):
          d[k] = (
              v
              if k not in d 
              else d[k] + v
          )
      
      # global d
      d = {}
      out = [fill_lists_per_dict_keys(i[0], [i[1]]) for i in d2.items()]
      

      out 只是为了抑制每个循环的None 输出。

      如果您想在运行时甚至在列表解析内使用新字典,或者如果您遇到字典被每个循环覆盖的另一个原因,请在脚本开头使用 global d 将其设为全局(注释掉,因为这里没有必要)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-04-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-09-27
        • 2010-12-17
        • 1970-01-01
        相关资源
        最近更新 更多