【问题标题】:Common elements comparison between 2 lists2个列表之间的共同元素比较
【发布时间】:2011-02-21 07:58:22
【问题描述】:
def common_elements(list1, list2):
    """
    Return a list containing the elements which are in both list1 and list2

    >>> common_elements([1,2,3,4,5,6], [3,5,7,9])
    [3, 5]
    >>> common_elements(['this','this','n','that'],['this','not','that','that'])
    ['this', 'that']
    """
    for element in list1:
        if element in list2:
            return list(element)

到目前为止,但似乎无法让它发挥作用!

有什么想法吗?

【问题讨论】:

标签: python list


【解决方案1】:

这里有一些解决方案在 O(l1+l2) 中完成,不计算重复项,而慢速解决方案(至少 O(l1*l2),但可能更昂贵)考虑重复项。

所以我想我应该添加一个 O(l1*log(l1)+l2*(log(l2)) 解决方案。如果列表已经排序,这将特别有用。

def common_elems_with_repeats(first_list, second_list):
    first_list = sorted(first_list)
    second_list = sorted(second_list)
    marker_first = 0
    marker_second = 0
    common = []
    while marker_first < len(first_list) and marker_second < len(second_list):
        if(first_list[marker_first] == second_list[marker_second]):
            common.append(first_list[marker_first])
            marker_first +=1
            marker_second +=1
        elif first_list[marker_first] > second_list[marker_second]:
            marker_second += 1
        else:
            marker_first += 1
    return common

另一个更快的解决方案包括从 list1 制作 item->count 映射,并迭代 list2,同时更新映射和计数重复。不需要排序。需要额外的内存,但从技术上讲是 O(l1+l2)。

【讨论】:

    【解决方案2】:

    使用 Python 的set intersection:

    >>> list1 = [1,2,3,4,5,6]
    >>> list2 = [3, 5, 7, 9]
    >>> list(set(list1).intersection(list2))
    [3, 5]
    

    【讨论】:

    • +1 但我个人使用 freezeset 因为它是不可变的,因此可以用作字典键等
    • 这将返回 /unique/ 常见元素,但不会返回任何可能存在的重复元素。
    • @SilentGhost。如何从两个列表中获取匹配元素的数量。在这种情况下,它是 2。
    • @Poka len(list(set(list1).intersection(list2)))
    • 仅供参考。这绝对比 Tamás 提出的解决方案要快,但是对于我最终在此页面上看到的用例,保留后过滤元素的元素的原始顺序很重要。此方法丢失了顺序,而列表理解方法保留了顺序。如果有人需要考虑这一点,这很重要。谢谢。
    【解决方案3】:

    使用生成器:

    common = (x for x in list1 if x in list2)
    

    这里的优点是即使使用巨大的列表或其他巨大的可迭代对象,它也会以恒定的时间(几乎是瞬间)返回。

    例如,

    list1 =  list(range(0,10000000))
    list2=list(range(1000,20000000))
    common = (x for x in list1 if x in list2)
    

    对于 list1 和 list2 的这些值,此处的所有其他答案都需要很长时间。

    然后您可以使用

    迭代答案
    for i in common: print(i)
    

    【讨论】:

    • 这不会产生答案。结果是一个生成器,而不是常见元素的列表。
    • 正确,它创建了一个生成器,这是一个答案。问题是以某种方式获得这两个列表的共同元素,这个生成器就是这样做的。只需像这样迭代生成器:for i in common: print(i)。生成器是经常用来代替其他可迭代对象(例如列表)的可迭代对象。
    • “这里的好处是它会在恒定时间内返回” - 所以呢?这将在恒定时间内返回生成器。如果您想要实际数据,您仍然需要转换为列表或迭代,这将像其他解决方案一样花费确切的时间......
    • @Tomerikoo,是的,我从未提出其他建议。优点是使用生成器。也许您不需要迭代整个列表。这完全取决于您的用例。如果将其转换为列表,那么显然没有任何优势。
    【解决方案4】:

    你可以使用numpy解决这个问题:

    import numpy as np
    
    list1 = [1, 2, 3, 4, 5, 6]
    list2 = [3, 5, 7, 9]
    
    common_elements = np.intersect1d(list1, list2)
    print(common_elements)
    

    common_elements 将是 numpy 数组:[3 5]

    【讨论】:

      【解决方案5】:

      我比较了每个答案提到的每种方法。目前我使用 python 3.6.3 来实现这个。这是我使用的代码:

      import time
      import random
      from decimal import Decimal
      
      
      def method1():
          common_elements = [x for x in li1_temp if x in li2_temp]
           print(len(common_elements))
      
      
      def method2():
          common_elements = (x for x in li1_temp if x in li2_temp)
          print(len(list(common_elements)))
      
      
      def method3():
          common_elements = set(li1_temp) & set(li2_temp)
          print(len(common_elements))
      
      
      def method4():
          common_elements = set(li1_temp).intersection(li2_temp)
          print(len(common_elements))
      
      
      if __name__ == "__main__":
          li1 = []
          li2 = []
          for i in range(100000):
              li1.append(random.randint(0, 10000))
              li2.append(random.randint(0, 10000))
      
          li1_temp = list(set(li1))
          li2_temp = list(set(li2))
      
          methods = [method1, method2, method3, method4]
          for m in methods:
              start = time.perf_counter()
              m()
              end = time.perf_counter()
              print(Decimal((end - start)))
      

      如果您运行此代码,您可以看到如果您使用列表或生成器(如果您迭代生成器,而不仅仅是使用它。我在强制生成器打印它的长度时这样做了),您将获得几乎相同的性能。但是如果你使用 set 你会得到更好的性能。此外,如果您使用交集方法,您将获得更好的性能。下面列出了我电脑中每种方法的结果:

      1. 方法1:0.8150673999999999974619413478649221360683441
      2. 方法2:0.8329545000000001531148541289439890533685684
      3. 方法3:0.0016547000000000089414697868051007390022277
      4. 方法4:0.0010262999999999244948867271887138485908508

      【讨论】:

        【解决方案6】:

        Set 是我们可以解决这个问题的另一种方法

        a = [3,2,4]
        b = [2,3,5]
        set(a)&set(b)
        {2, 3}
        

        【讨论】:

        • 您也可以将此解决方案框入 list() 以输出为列表:list(set(a)&amp;set(b))
        【解决方案7】:

        1) 方法1 保存 list1 是字典,然后迭代 list2 中的每个元素

        def findarrayhash(a,b):
            h1={k:1 for k in a}
            for val in b:
                if val in h1:
                    print("common found",val)
                    del h1[val]
                else:
                    print("different found",val)
            for key in h1.iterkeys():
                print ("different found",key)
        

        寻找共同和不同的元素:

        2) 方法2 使用集合

        def findarrayset(a,b):
            common = set(a)&set(b)
            diff=set(a)^set(b)
            print list(common)
            print list(diff) 
        

        【讨论】:

          【解决方案8】:

          您还可以使用集合并在一行中获得共性:从其中一个集合中减去包含差异的集合。

          A = [1,2,3,4]
          B = [2,4,7,8]
          commonalities = set(A) - (set(A) - set(B))
          

          【讨论】:

          • 这会将 A 转换为两次设置,不必要的浪费。
          【解决方案9】:

          前面的答案都可以找到唯一的共同元素,但无法解释列表中的重复项。如果您希望公共元素以与列表中相同的编号出现,您可以使用以下单行:

          l2, common = l2[:], [ e for e in l1 if e in l2 and (l2.pop(l2.index(e)) or True)]
          

          or True 部分仅在您希望任何元素评估为 False 时才是必需的。

          【讨论】:

          • 这应该是应该选择的答案!我假设它也适用于不相等的列表。此外,大多数解决方案都使用不稳定的set(即订单丢失)。
          • 这个解决方案的运行时间至少为 O(len(l1)*len(l2)) (这还没有考虑 pop 和 index (e) 的成本)
          • 在列表理解中使用可变(有副作用)语句不是一个好习惯。除了效率低下之外,this other answer 在可维护性和运行时方面都更好。
          • 环顾四周,已经有很好的方法来保留重复项。见python - Intersection of two lists including duplicates? - Stack Overflow
          • @user202729 是的,无论如何,我想说的规范方法是使用Counter,它可以充当多重集,实际上,甚至可以使用&amp; 运算符来实现这一点,所以你所需要的只是list((Counter(list1) &amp; Counter(list2)).elements()),即使这不是内置的,你也想使用基于哈希映射的方法......
          【解决方案10】:

          S.MarkSilentGhost 建议的解决方案通常会告诉您应该如何以 Python 方式完成,但我认为您也可能会从了解您的解决方案为什么不起作用中受益。问题是,一旦您在两个列表中找到第一个公共元素,您就只返回该单个元素。您可以通过创建result 列表并收集该列表中的常见元素来解决您的解决方案:

          def common_elements(list1, list2):
              result = []
              for element in list1:
                  if element in list2:
                      result.append(element)
              return result
          

          使用列表推导的更短版本:

          def common_elements(list1, list2):
              return [element for element in list1 if element in list2]
          

          但是,正如我所说,这是一种非常低效的方法——Python 的内置集合类型效率更高,因为它们是在内部用 C 实现的。

          【讨论】:

          • 两个提案都很棒
          • 注意:上述方法仅适用于相同大小的列表。如果您像我一样使用大小不等的列表,那么您需要在调用函数之前根据 len() 评估顺序:list1 = [2,2,2], list2[2,3] -> [2,2,2] list1 = [2,3], list2[2,2,2] -> [2]
          【解决方案11】:

          使用集合交集,set(list1) & set(list2)

          >>> def common_elements(list1, list2):
          ...     return list(set(list1) & set(list2))
          ...
          >>>
          >>> common_elements([1,2,3,4,5,6], [3,5,7,9])
          [3, 5]
          >>>
          >>> common_elements(['this','this','n','that'],['this','not','that','that'])
          ['this', 'that']
          >>>
          >>>
          

          请注意,结果列表可能与原始列表的顺序不同。

          【讨论】:

          • 很好的解决方案。有没有办法用这个来保持顺序?
          猜你喜欢
          • 2019-05-08
          • 1970-01-01
          • 1970-01-01
          • 2015-12-12
          • 1970-01-01
          • 2020-08-15
          • 1970-01-01
          • 2021-01-01
          • 2015-12-16
          相关资源
          最近更新 更多