如何使此列表功能更快?

时间:2011-07-18 04:49:56

标签: python algorithm list optimization dictionary

def removeDuplicatesFromList(seq): 
    # Not order preserving 
    keys = {}
    for e in seq:
        keys[e] = 1
    return keys.keys()

def countWordDistances(li):
    '''
    If li = ['that','sank','into','the','ocean']    
    This function would return: { that:1, sank:2, into:3, the:4, ocean:5 }
    However, if there is a duplicate term, take the average of their positions
    '''
    wordmap = {}
    unique_words = removeDuplicatesFromList(li)
    for w in unique_words:
        distances = [i+1 for i,x in enumerate(li) if x == w]
        wordmap[w] = float(sum(distances)) / float(len(distances)) #take average
    return wordmap

如何更快地完成此功能?

8 个答案:

答案 0 :(得分:15)

import collections
def countWordDistances(li):
    wordmap = collections.defaultdict(list)
    for i, w in enumerate(li, 1):
        wordmap[w].append(i)
    for k, v in wordmap.iteritems():
        wordmap[k] = sum(v)/float(len(v))

    return wordmap

这只会使一次通过列表,并将操作保持在最低限度。我在一个包含1.1M条目,29k个独特单词的单词列表上计时,它几乎是Patrick的答案的两倍。在10k字的列表中,2k是唯一的,它比OP的代码快300倍。

要使Python代码更快,请记住两条规则:使用最佳算法,并避免使用Python。

在算法方面,迭代列表一次而不是N + 1次(N =唯一字数)是加速这一点的主要因素。

在“避免Python”方面,我的意思是:你希望你的代码尽可能在C中执行。因此,使用defaultdict比明确检查密钥是否存在的字典更好。 defaultdict会对您进行检查,但是在C实现中,它会在Python实现中进行检查。 enumerate优于for i in range(len(li)),因为它的Python步骤较少。并且enumerate(li, 1)使计数从1开始,而不是必须在循环中的某处使用Python +1。

编辑:第三条规则:使用PyPy。我的代码在PyPy上的速度是2.7的两倍。

答案 1 :(得分:5)

基于@Ned Batchelder的解决方案,但没有创建虚拟列表:

import collections
def countWordDistances(li):
    wordmap = collections.defaultdict(lambda:[0.0, 0.0])
    for i, w in enumerate(li, 1):
        wordmap[w][0] += i
        wordmap[w][1] += 1.0
    for k, (t, n) in wordmap.iteritems():
        wordmap[k] = t / n
    return wordmap

答案 2 :(得分:3)

我不确定这是否比使用集更快,但它只需要通过列表一次:

def countWordDistances(li):
    wordmap = {}
    for i in range(len(li)):
        if li[i] in wordmap:
            avg, num = wordmap[li[i]]
            new_avg = avg*(num/(num+1.0)) + (1.0/(num+1.0))*i
            wordmap[li[i]] = new_avg, num+1
        else:
            wordmap[li[i]] = (i, 1)

    return wordmap

这将返回wordmap的修改版本,其中与每个键相关联的值是平均位置和出现次数的元组。您显然可以轻松地将其转换为原始输出的形式,但这需要一些时间。

代码在迭代列表时基本保持运行平均值,每次通过加权平均值重新计算。

答案 3 :(得分:1)

使用套装:

def countWordDistances(li):
    '''
    If li = ['that','sank','into','the','ocean']    
    This function would return: { that:1, sank:2, into:3, the:4, ocean:5 }
    However, if there is a duplicate term, take the average of their positions
    '''
    wordmap = {}
    unique_words = set(li)
    for w in unique_words:
        distances = [i+1 for i,x in enumerate(li) if x == w]
        wordmap[w] = float(sum(distances)) / float(len(distances)) #take average
    return wordmap

答案 4 :(得分:1)

首先想到的是使用一个集来删除重复的单词:

unique_words = set(li)

一般情况下,如果你担心速度,你需要分析功能以查看瓶颈在哪里,然后尝试减少这个瓶颈。

答案 5 :(得分:1)

使用frozenset代替dict,因为您没有对值进行任何操作:

def removeDuplicatesFromList(seq):
    return frozenset(seq)

答案 6 :(得分:0)

使用列表理解:

def countWordDistances(l):
    unique_words = set(l)
    idx = [[i for i,x in enumerate(l) if x==item]
            for item in unique_words]
    return {item:1.*sum(idx[i])/len(idx[i]) + 1.
            for i,item in enumerate(unique_words)}

li = ['that','sank','into','the','ocean']
countWordDistances(li)
# {'into': 3.0, 'ocean': 5.0, 'sank': 2.0, 'that': 1.0, 'the': 4.0}

li2 = ['that','sank','into','the','ocean', 'that']
countWordDistances(li2)
# {'into': 3.0, 'ocean': 5.0, 'sank': 2.0, 'that': 3.5, 'the': 4.0}

答案 7 :(得分:-1)

Oneliner -

from __future__ import division   # no need for this if using py3k

def countWordDistances(li):
    '''
    If li = ['that','sank','into','the','ocean']    
    This function would return: { that:1, sank:2, into:3, the:4, ocean:5 }
    However, if there is a duplicate term, take the average of their positions
    '''
    return {w:sum(dist)/len(dist) for w,dist in zip(set(li), ([i+1 for i,x in enumerate(li) if x==w] for w in set(li))) }

我在最后一行中所做的是字典理解,类似于列表理解。