Python 比较有序列表和计数常用元素*包括*重复项的最快方法

Python 比较有序列表和计数常用元素*包括*重复项的最快方法,python,Python,我需要比较两个数字列表,并计算第一个列表中有多少元素在第二个列表中。比如说, a = [2, 3, 3, 4, 4, 5] b1 = [0, 2, 2, 3, 3, 4, 6, 8] 这里我应该得到4的结果:我应该计算'2'1次(在第一个列表中只发生一次),'3'-2次,'4'-1次(在第二个列表中只发生一次)。我使用了以下代码: def scoreIn(list1, list2): score=0 list2c=list(list2) for i in list1:

我需要比较两个数字列表,并计算第一个列表中有多少元素在第二个列表中。比如说,

a =  [2, 3, 3, 4, 4, 5]
b1 = [0, 2, 2, 3, 3, 4, 6, 8]
这里我应该得到4的结果:我应该计算'2'1次(在第一个列表中只发生一次),'3'-2次,'4'-1次(在第二个列表中只发生一次)。我使用了以下代码:

def scoreIn(list1, list2):
   score=0
   list2c=list(list2)
   for i in list1:
      if i in list2c:
         score+=1
         list2c.remove(i)
   return score
它工作正常,但对于我的情况来说太慢了(我称之为15000次)。我读到一条关于“浏览”排序列表的提示,该提示应该更快,所以我尝试这样做:

def scoreWalk(list1, list2):
   score=0
   i=0
   j=0
   len1=len(list1) # we assume that list2 is never shorter than list1
   while i<len1:
      if list1[i]==list2[j]:
         score+=1
         i+=1
         j+=1
      elif list1[i]>list2[j]:
         j+=1
      else:
         i+=1
   return score
def scoreWalk(列表1、列表2):
分数=0
i=0
j=0
len1=len(list1)#我们假设list2永远不会比list1短
而ilist2[j]:
j+=1
其他:
i+=1
回击得分

不幸的是,这段代码甚至更慢。有没有办法使它更有效率?在我的例子中,两个列表都是排序的,只包含整数,列表1的长度永远不会超过列表2。

您可以使用集合的交集功能。计数器以简单易读的方式解决问题:

>>> from collections import Counter
>>> intersection = Counter( [2,3,3,4,4,5] ) & Counter( [0, 2, 2, 3, 3, 4, 6, 8] )
>>> intersection
Counter({3: 2, 2: 1, 4: 1})
正如@Bakuriu在评论中所说的,要获得交集中的元素数量(包括重复元素),如
scoreIn
函数,您可以使用
sum(intersection.values())

但是,这样做实际上并没有利用数据已预排序这一事实,也没有利用(评论中提到的)在同一个列表中一遍又一遍地这样做的事实

这里有一个更详细的解决方案,它更适合您的问题。它对静态列表使用
计数器
,并直接使用排序后的动态列表。在我的机器上,它在随机生成的测试数据上以天真的
计数器
方法的43%运行时间运行

def common_elements( static_counter, dynamic_sorted_list ):
    last = None # previous element in the dynamic list
    count = 0 # count seen so far for this element in the dynamic list

    total_count = 0 # total common elements seen, eventually the return value

    for x in dynamic_sorted_list:
        # since the list is sorted, if there's more than one element they
        # will be consecutive.
        if x == last:
            # one more of the same as the previous  element

            # all we need to do is increase the count
            count += 1
        else:
            # this is a new element that we haven't seen before.

            # first "flush out" the current count we've been keeping.
            #   - count is the number of times it occurred in the dynamic list
            #   - static_counter[ last ] is the number of times it occurred in
            #       the static list (the Counter class counted this for us)
            # thus the number of occurrences the two have in common is the
            # smaller of these numbers. (Note that unlike a normal dictionary,
            # which would raise KeyError, a Counter will return zero if we try
            # to look up a key that isn't there at all.)
            total_count += min( static_counter[ last ], count )

            # now set count and last to the new element, starting a new run
            count = 1
            last = x

    if count > 0:
        # since we only "flushed" above once we'd iterated _past_ an element,
        # the last unique value hasn't been counted. count it now.
        total_count += min( static_counter[ last ], count )

    return total_count
这样做的想法是,当您创建
计数器
对象时,您可以预先做一些工作。完成这项工作后,可以使用
计数器
对象快速查找计数,就像在字典中查找值一样:
static\u Counter[x]
返回静态列表中发生的次数
x

由于静态列表每次都是相同的,因此可以执行一次,并使用生成的快速查找结构15000次

另一方面,为动态列表设置
计数器
对象可能无法从性能方面获得回报。创建
计数器
对象会有一点开销,我们只会使用每个动态列表
计数器
一次。如果我们能够避免构建对象,那么这样做是有意义的。正如我们在上面看到的,您实际上可以通过迭代动态列表并在另一个计数器中查找计数来实现所需的功能

您帖子中的
scoreWalk
函数不处理最大项目仅在静态列表中的情况,例如
scoreWalk([1,1,3],[1,1,2])
。然而,纠正这一点,对我来说,它实际上比任何
计数器
方法都要好,这与您报告的结果相反。您的数据与我的均匀分布测试数据的分布可能存在显著差异,但请仔细检查您的
scoreWalk
基准测试,以确保这一点


最后,考虑到你可能使用错误的工具来做这项工作。你追求的不是简短、优雅和可读性——你试图从一个相当简单的任务中挤出最后一点性能。CPython允许您执行以下操作。这方面的一个主要用例是实现高度优化的代码。它可能非常适合您的任务。

您可以通过
dict
理解:

>>> a =  [2, 3, 3, 4, 4, 5]
>>> b1 = [0, 2, 2, 3, 3, 4, 6, 8]
>>> {k: min(b1.count(k), a.count(k)) for k in set(a)}
{2: 1, 3: 2, 4: 1, 5: 0}

如果
set(a)
很小,这会快得多。如果
set(a)
超过40项,则基于
计数器的解决方案速度更快。

添加
总和(expr.values())
以获得公共元素的最终计数。(其中
expr
是答案中的
&
表达式)。谢谢,我不知道这个函数!但不幸的是,它在速度上没有任何提高:根据profiler,它所用的时间几乎与我的第一个算法完全相同。@VasilyA你能更详细地描述你的输入数据吗?您需要在不相关的列表对上执行15000次吗?列表是否与示例中的列表一样短,或者更长?
collections.Counter
方法没有利用数据已经排序的事实。好吧,基本上,我有一个给定的“光谱”(数百个整数的列表),和数千个对象(“肽”),每个对象都有自己的“光谱”(类似的数字列表),我们的目标是给每个肽分配一个分数,也就是说,每个光谱与给定的光谱有多接近。我当然准备详细解释,但如果你不是生物信息学家(这是基础生物信息学课程中的一项任务),描述可能会有点长。@VasilyA你是否尝试过先为每个光谱生成
计数器
对象,然后只对每对进行交集?我可以想象,与为每次比较生成新的
计数器
对象相比,这将大大加快速度。。。目前,我还不清楚如何使用这种方法获得所需答案,因为它不计算
a
中的重复项。这仍然不是我真正需要的:在“2”的情况下,它计算
b1
中的两次出现,而我只需要计算1(因为它在
a
中只出现一次),我忘记了更新输出,对于案例“2”,它只计算1。试试看。