【问题标题】:How do I make this list function faster?如何使此列表功能更快?
【发布时间】:2011-10-07 09:47:55
【问题描述】:
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

如何让这个功能更快?

【问题讨论】:

    标签: python algorithm list optimization dictionary


    【解决方案1】:

    基于@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
    

    【讨论】:

    • 好的,这对于一个粗俗的黑客来说是怎样的:如果您只需要一个包含两个实数的列表,那么请改用一个复数!它可以减少 25% 的解决方案运行时间,但是太糟糕了!
    • @Ned:哈,是的!你试过lambda:numpy.zeros(2)吗?你会比我更了解,但是有一天,我希望有人编写一个很棒的 Python 优化器,这样我们就可以专注于算法(这就是我爱上 Python 的原因。)
    【解决方案2】:
    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
    

    这只会使列表通过一次,并将操作保持在最低限度。我在一个包含 110 万个条目、29k 个唯一单词的单词列表上对此进行了计时,它的速度几乎是 Patrick 答案的两倍。在 10k 个单词、2k 个唯一单词的列表中,它比 OP 的代码快 300 倍以上。

    要使 Python 代码运行得更快,需要牢记两条规则:使用最佳算法,避免使用 Python。

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

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

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

    【讨论】:

    • +1 进行很酷的优化。我对最好的算法有一个不错的想法,但没有你清楚地掌握的 Python 知识。
    • 为什么不积累足够的统计总数呢?我会添加一个答案。
    • @Neil G:干得好,你的速度比我的快 10%,并提出另一条规则:避免内存分配。
    • +1 表示“避免使用 Python [通过巧妙地使用 Python]” - 从您的推文中,我期待“避免使用 Python”与本机扩展或其他内容有关。
    • “使用 PyPy”和“避免 python”不能很好地结合在一起。最好是“使用 PyPy”和“使用 Python”?
    【解决方案3】:

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

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

    【讨论】:

    • python 2.7 中也提供了字典推导式。在此之前,可以通过使用生成器理解调用 dict 来使用相同的想法,例如,`dict((i, 2*i) for i in range(4))' 产生 '{0: 0, 1: 2 , 2:4, 3:6}'。
    • 这对你有用吗?我得到“未定义全局名称'w'”,因为“x == w”在定义 w 的循环内。
    【解决方案4】:

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

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

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

    【讨论】:

    • 只通过一次列表是关键。
    【解决方案5】:

    使用列表推导:

    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}
    

    【讨论】:

      【解决方案6】:

      使用frozenset 而不是dict,因为您没有对这些值做任何事情:

      def removeDuplicatesFromList(seq):
          return frozenset(seq)
      

      【讨论】:

      • 其他人都建议使用 set。使用frozenset有什么好处?
      • @user849364:主要区别在于set 是可变的,而frozenset 是不可变的。我相信没有性能优势,但它告诉您的代码的读者该集合不会被修改。有关详细信息,请参阅 Python 文档。
      【解决方案7】:

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

      unique_words = set(li)
      

      不过,一般来说,如果您担心速度,您需要分析函数以查看瓶颈在哪里,然后尝试减少该瓶颈。

      【讨论】:

        【解决方案8】:

        使用一组:

        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
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-11-14
          • 2015-01-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-02-03
          相关资源
          最近更新 更多