Python:使用dict理解/生成器计算列表中的出现次数

时间:2014-11-04 09:30:45

标签: python performance dictionary generator

我想编写一些测试来分析python中不同操作的效率,即字典理解和字典生成器的比较。

为了测试这个,我想我会尝试一个简单的例子:使用字典计算列表中的单词数。

现在我知道你可以使用collections.Counter执行此操作(根据答案:How can I count the occurrences of a list item in Python?),但我的目标是测试性能是一种记忆。

一种“长手”方式是在基本循环中完成。

from pprint import pprint

# Read in some text to create example data
with open('text.txt') as f:
    words = f.read().split()

dict1 = {}
for w in words:
    if not dict1.get(w):
        dict1[w] = 1
    else:
        dict1[w] += 1
pprint(dict1)

结果:

{'a': 62,
 'aback': 1,
 'able': 1,
 'abolished': 2,
 'about': 6,
 'accept': 1,
 'accepted': 1,
 'accord': 1,
 'according': 1,
 'across': 1,
 ...

然后我在字典理解中尝试做同样的事情时遇到了一些困难:

dict2  = { w: 1 if not dict2.get(w) else dict2.get(w) + 1
            for w in words }

我收到了一个错误:

NameError: global name 'dict2' is not defined

我尝试在前面定义dict:

dict2 = {}
dict2  = { w: 1 if not dict2.get(w) else dict2.get(w) + 1
            for w in words }
pprint(dict2)

但当然计数都设为1:

{'a': 1,
 'aback': 1,
 'able': 1,
 'abolished': 1,
 'about': 1,
 'accept': 1,
 'accepted': 1,
 'accord': 1,
 'according': 1,
 'across': 1,
 ...

我对dict理解有类似的问题:

dict3 = dict( (w, 1 if not dict2.get(w) else dict2.get(w) + 1)
                for w in words)

所以我的问题是:如何最有效地使用字典理解/生成器来计算列表中出现的次数?

更新:@Rawing提出了另一种方法{word:words.count(word) for word in set(words)},但这会绕过我试图测试的机制。

3 个答案:

答案 0 :(得分:5)

使用字典理解你不能有效地(至少在内存方面)这样做,因为那时你必须跟踪另一个字典中的当前计数,即更多的内存消耗。这是你如何使用字典理解(完全不推荐: - )):

>>> words = list('asdsadDASDFASCSAASAS')
>>> dct = {}
>>> {w: 1 if w not in dct and not dct.update({w: 1})
                  else dct[w] + 1
                  if not dct.update({w: dct[w] + 1}) else 1 for w in words}
>>> dct
{'a': 2, 'A': 5, 's': 2, 'd': 2, 'F': 1, 'C': 1, 'S': 5, 'D': 2}

另一种方法是首先对单词列表进行排序,然后使用itertools.groupby对它们进行分组,然后计算每个组的长度。如果你愿意,可以将dict-comprehension转换为生成器,但是这需要先读取内存中的所有单词:

from itertools import groupby
words.sort()
dct = {k: sum(1 for _ in g) for k, g in groupby(words)}

请注意,该地段的最快collections.defaultdict

d = defaultdict(int)
for w in words: d[w] += 1 

时间比较:

>>> from string import ascii_letters, digits
>>> %timeit words = list(ascii_letters+digits)*10**4; words.sort(); {k: sum(1 for _ in g) for k, g in groupby(words)}
10 loops, best of 3: 131 ms per loop
>>> %timeit words = list(ascii_letters+digits)*10**4; Counter(words)
10 loops, best of 3: 169 ms per loop
>>> %timeit words = list(ascii_letters+digits)*10**4; dct = {}; {w: 1 if w not in dct and not dct.update({w: 1}) else dct[w] + 1 if not dct.update({w: dct[w] + 1}) else 1 for w in words}
1 loops, best of 3: 315 ms per loop
>>> %%timeit
... words = list(ascii_letters+digits)*10**4
... d = defaultdict(int)
... for w in words: d[w] += 1
... 
10 loops, best of 3: 57.1 ms per loop
>>> %%timeit
words = list(ascii_letters+digits)*10**4
d = {}
for w in words: d[w] = d.get(w, 0) + 1
... 
10 loops, best of 3: 108 ms per loop

#Increase input size 

>>> %timeit words = list(ascii_letters+digits)*10**5; words.sort(); {k: sum(1 for _ in g) for k, g in groupby(words)}
1 loops, best of 3: 1.44 s per loop
>>> %timeit words = list(ascii_letters+digits)*10**5; Counter(words)
1 loops, best of 3: 1.7 s per loop
>>> %timeit words = list(ascii_letters+digits)*10**5; dct = {}; {w: 1 if w not in dct and not dct.update({w: 1}) else dct[w] + 1 if not dct.update({w: dct[w] + 1}) else 1 for w in words}

1 loops, best of 3: 3.19 s per loop
>>> %%timeit
words = list(ascii_letters+digits)*10**5
d = defaultdict(int)
for w in words: d[w] += 1
... 
1 loops, best of 3: 571 ms per loop
>>> %%timeit
words = list(ascii_letters+digits)*10**5
d = {}
for w in words: d[w] = d.get(w, 0) + 1
... 
1 loops, best of 3: 1.1 s per loop

答案 1 :(得分:2)

你可以这样做:

>>> words=['this','that','is','if','that','is','if','this','that']
>>> {i:words.count(i) for i in words}
{'this': 2, 'is': 2, 'if': 2, 'that': 3}

答案 2 :(得分:1)

这是一种理解不适应/有效的用例。

如果您可以在一次操作中构建集合,那么理解就很好。事实并非如此,因为:

  • 您可以在中使用并相应地更改值
  • 或者您必须首先计算密钥集(Rawing解决方案),然后您浏览列表一次以获取密钥集,并且每个密钥一次
恕我直言,最有效的方法是迭代的方式。