以最快的方式计算python中的重复单词

时间:2013-01-17 08:05:52

标签: python dictionary hashtable performance word-count

我试图在23万字的列表上计算重复的单词。我使用python字典这样做。代码如下:

for words in word_list:
    if words in word_dict.keys():
       word_dict[words] += 1
    else:
       word_dict[words] = 1

以上代码花了3分钟!我运行相同的代码超过150万字,它运行超过25分钟,我失去了耐心并终止。然后我发现我可以使用here中的以下代码(也如下所示)。结果是如此令人惊讶,它在几秒钟内完成!所以我的问题是什么是更快的方式来做这个操作?我想字典创建过程必须花费O(N)时间。 Counter方法如何能够在几秒钟内完成此过程并创建一个精确的单词词典作为键和频率的值?

from collections import Counter
word_dict = Counter(word_list)

5 个答案:

答案 0 :(得分:5)

正因为如此:

if words in word_dict.keys():

.keys()返回所有键的列表。列表需要线性时间进行扫描,因此您的程序以二次方运行!

请改为尝试:

if words in word_dict:

另外,如果您有兴趣,可以自己查看Counter implementation。它是用常规Python编写的。

答案 1 :(得分:3)

你的字典计数方法构造不好。

您可以通过以下方式使用defaultdict

d = defaultdict(int)

for word in word_list:
    d[word] += 1

但是itertools的counter方法仍然更快,即使它做的几乎相同,因为它是在一个更有效的实现中编写的。但是,使用计数器方法,您需要将一个列表传递给count,而使用defaultdict,您可以放置​​来自不同位置的源并具有更复杂的循环。

最终这是你的偏好。如果计算一个列表,counter是要走的路,如果从多个源迭代,或者你只是想在你的程序中使用一个计数器而不想要额外的查找来检查一个项是否已被计数。然后defaultdict是你的选择。

答案 2 :(得分:1)

您实际上可以查看Counter代码,这是在init上调用的更新方法:

(请注意,它使用了定义self.get

的本地定义的性能技巧
def update(self, iterable=None, **kwds):
    '''Like dict.update() but add counts instead of replacing them.

    Source can be an iterable, a dictionary, or another Counter instance.

    >>> c = Counter('which')
    >>> c.update('witch')           # add elements from another iterable
    >>> d = Counter('watch')
    >>> c.update(d)                 # add elements from another counter
    >>> c['h']                      # four 'h' in which, witch, and watch
    4

    '''
    # The regular dict.update() operation makes no sense here because the
    # replace behavior results in the some of original untouched counts
    # being mixed-in with all of the other counts for a mismash that
    # doesn't have a straight-forward interpretation in most counting
    # contexts.  Instead, we implement straight-addition.  Both the inputs
    # and outputs are allowed to contain zero and negative counts.

    if iterable is not None:
        if isinstance(iterable, Mapping):
            if self:
                self_get = self.get
                for elem, count in iterable.iteritems():
                    self[elem] = self_get(elem, 0) + count
            else:
                super(Counter, self).update(iterable) # fast path when counter is empty
        else:
            self_get = self.get
            for elem in iterable:
                self[elem] = self_get(elem, 0) + 1
    if kwds:
        self.update(kwds)

答案 3 :(得分:0)

您也可以尝试使用defaultdict作为更具竞争力的选择。尝试:

from collections import defaultdict

word_dict = defaultdict(lambda: 0)
for word in word_list:
    word_dict[word] +=1

print word_dict

答案 4 :(得分:0)

monkut 提到的类似,最好的方法之一是使用 .get() 函数。这要归功于 Charles Severance 和 Python For Everybody Course

用于测试:

# Pretend line is as follow. 
# It can and does contain \n (newline) but not \t (tab).
line = """Your battle is my battle . We fight together . One team . One team . 
Shining sun always comes with the rays of hope . The hope is there . 
Our best days yet to come . Let the hope light the road .""".lower()

他的代码(和我的笔记):

# make an empty dictionary
# split `line` into a list. default is to split on a space character
# etc., etc.
# iterate over the LIST of words (made from splitting the string)
counts = dict()
words = line.split() 
for word in words: 
    counts[word] = counts.get(word,0) + 1

您的代码:

for words in word_list:
if words in word_dict.keys():
   word_dict[words] += 1
else:
   word_dict[words] = 1

.get() 这样做:

  • 返回字典中与 word 关联的 VALUE。
  • 否则(如果单词不是字典中的关键字,则返回 0

无论返回什么,我们都会为其添加1。因此它处理了第一次看到这个词的基本情况。我们不能使用字典推导式,因为推导式分配给的变量将不存在,因为我们正在创建该变量。含义

this: counts = { word:counts.get(word,0) + 1 for word in words} 是不可能的,因为 counts (正在创建并同时分配给。或者,因为) counts 变量尚未完全定义时我们(再次)将其引用到 .get()

输出

>> counts
{'.': 8,
'always': 1,
'battle': 2,
'best': 1,
'come': 1,
'comes': 1,
'days': 1,
'fight': 1,
'hope': 3,
'is': 2,
'let': 1,
'light': 1,
'my': 1,
'of': 1,
'one': 2,
'our': 1,
'rays': 1,
'road': 1,
'shining': 1,
'sun': 1,
'team': 2,
'the': 4,
'there': 1,
'to': 1,
'together': 1,
'we': 1,
'with': 1,
'yet': 1,
'your': 1}

顺便说一句,here.get() 的“加载”用法,我编写它是为了解决经典的 FizzBu​​zz 问题。我目前正在为类似的情况编写代码,在这种情况下,我将使用模数和字典,但使用拆分字符串作为输入。