测试列表是否共享python中的任何项目

时间:2010-07-03 02:15:16

标签: list python intersection

我想检查一个列表中的任何项是否存在于另一个列表中。我可以使用下面的代码简单地完成它,但我怀疑可能有一个库函数来执行此操作。如果没有,是否有更多的pythonic方法来实现相同的结果。

In [78]: a = [1, 2, 3, 4, 5]

In [79]: b = [8, 7, 6]

In [80]: c = [8, 7, 6, 5]

In [81]: def lists_overlap(a, b):
   ....:     for i in a:
   ....:         if i in b:
   ....:             return True
   ....:     return False
   ....: 

In [82]: lists_overlap(a, b)
Out[82]: False

In [83]: lists_overlap(a, c)
Out[83]: True

In [84]: def lists_overlap2(a, b):
   ....:     return len(set(a).intersection(set(b))) > 0
   ....: 

10 个答案:

答案 0 :(得分:244)

简短回答:使用not set(a).isdisjoint(b),一般来说最快。

有四种常用方法可以测试两个列表ab是否共享任何项目。第一个选项是将两者都转换为集合并检查它们的交集,如下:

bool(set(a) & set(b))

因为集合是使用Python中的哈希表存储的,所以搜索它们是O(1) (有关Python中运算符复杂性的更多信息,请参阅here)。从理论上讲,对于O(n+m)n列表中的ma个对象,平均b。但是1)它必须首先从列表中创建集合,这可能需要不可忽略的时间,2)它假设散列冲突在您的数据中是稀疏的。

第二种方法是使用生成器表达式对列表执行迭代,例如:

any(i in a for i in b)

这允许就地搜索,因此没有为中间变量分配新内存。它也拯救了第一个发现。 in运算符在列表上始终为O(n)(请参阅here)。

另一个提议的选项是混合迭代遍历列表之一,转换集合中的另一个并测试此集合的成员资格,如下所示:

a = set(a); any(i in a for i in b)

第四种方法是利用(冻结)集合的isdisjoint()方法(参见here),例如:

not set(a).isdisjoint(b)

如果您搜索的元素靠近数组的开头(例如,它已排序),则生成表达式是有利的,因为集合交集方法必须为中间变量分配新内存:

from timeit import timeit
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=list(range(1000))", number=100000)
26.077727576019242
>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=list(range(1000))", number=100000)
0.16220548999262974

以下是此示例在列表大小函数中执行时间的图表:

Element sharing test execution time when shared at the beginning

请注意,两个轴都是对数轴。这代表了生成器表达式的最佳情况。可以看出,isdisjoint()方法对于非常小的列表大小更好,而生成器表达式对于更大的列表大小更好。

另一方面,当搜索从混合和生成器表达式的开头开始时,如果共享元素系统地位于数组的末尾(或者两个列表都不共享任何值),则不相交和设置交集然后,方法比生成器表达式和混合方法更快。

>>> timeit('any(i in a for i in b)', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
13.739536046981812
>>> timeit('bool(set(a) & set(b))', setup="a=list(range(1000));b=[x+998 for x in range(999,0,-1)]", number=1000))
0.08102107048034668

Element sharing test execution time when shared at the end

值得注意的是,对于较大的列表大小,生成器表达式会慢一些。这仅重复1000次,而不是前一次的100000次。当没有共享元素时,此设置也很接近,并且是不相交和集合交集方法的最佳情况。

以下是使用随机数进行的两次分析(而不是将设置绑定到某种技术或其他技术):

Element sharing test execution time for randomly generated data with high chance of sharing Element sharing test execution time for randomly generated data with high chance of sharing

分享的可能性很高:元素是从[1, 2*len(a)]中随机抽取的。共享的可能性很低:元素是从[1, 1000*len(a)]中随机抽取的。

到目前为止,这种分析认为两个列表的大小相同。如果有两个不同大小的列表,例如a要小得多,isdisjoint()总是更快:

Element sharing test execution time on two differently sized lists when shared at the beginning Element sharing test execution time on two differently sized lists when shared at the end

确保a列表较小,否则性能会降低。在此实验中,a列表大小已设置为5

总结:

  • 如果列表非常小(<10个元素),not set(a).isdisjoint(b)总是最快的。
  • 如果列表中的元素已排序或具有您可以利用的常规结构,则生成器表达式any(i in a for i in b)在大型列表大小上最快;
  • 使用not set(a).isdisjoint(b)测试集合交集,总是快于bool(set(a) & set(b))
  • 杂交&#34;迭代列表,测试集合&#34; a = set(a); any(i in a for i in b)通常比其他方法慢。
  • 当涉及没有共享元素的列表时,生成器表达式和混合类型比其他两种方法慢得多。

在大多数情况下,使用isdisjoint()方法是最好的方法,因为生成器表达式执行需要更长的时间,因为当没有共享元素时效率非常低。

答案 1 :(得分:24)

def lists_overlap3(a, b):
    return bool(set(a) & set(b))

注意:上面假设你想要一个布尔值作为答案。如果您只需要在if语句中使用的表达式,只需使用if set(a) & set(b):

即可

答案 2 :(得分:10)

def lists_overlap(a, b):
  sb = set(b)
  return any(el in sb for el in a)

这是渐近最优的(最差情况为O(n + m)),并且由于any的短路可能比交叉方法更好。

E.g:

lists_overlap([3,4,5], [1,2,3])

一旦到达3 in sb

,就会返回True

编辑:另一种变化(感谢Dave Kirby):

def lists_overlap(a, b):
  sb = set(b)
  return any(itertools.imap(sb.__contains__, a))

这依赖于imap的迭代器,它是用C实现的,而不是生成器理解。它还使用sb.__contains__作为映射函数。我不知道这会带来多大的性能差异。它仍然会短路。

答案 3 :(得分:4)

您还可以将any与列表理解结合使用:

any([item in a for item in b])

答案 4 :(得分:3)

在python 2.6或更高版本中,你可以这样做:

return not frozenset(a).isdisjoint(frozenset(b))

答案 5 :(得分:2)

您可以使用任何内置函数/ w生成器表达式:

def list_overlap(a,b): 
     return any(i for i in a if i in b)

正如John和Lie所指出的那样,当两个列表共享的每个i bool(i)== False时,这会得到错误的结果。它应该是:

return any(i in b for i in a)

答案 6 :(得分:1)

这个问题很老了,但我注意到,虽然人们在争论集合与列表,但没有人想到将它们结合使用。按照Soravux的例子,

列表的最坏情况:

>>> timeit('bool(set(a) & set(b))',  setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
100.91506409645081
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
19.746716022491455
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=[x+9999 for x in range(10000)]", number=100000)
0.092626094818115234

列表的最佳案例:

>>> timeit('bool(set(a) & set(b))',  setup="a=list(range(10000)); b=list(range(10000))", number=100000)
154.69790101051331
>>> timeit('any(i in a for i in b)', setup="a=list(range(10000)); b=list(range(10000))", number=100000)
0.082653045654296875
>>> timeit('any(i in a for i in b)', setup="a= set(range(10000)); b=list(range(10000))", number=100000)
0.08434605598449707

因此,比迭代两个列表更快的是通过列表迭代以查看它是否在一个集合中,这是有道理的,因为检查一个数字是否在一个集合中需要恒定的时间,同时通过遍历列表进行检查需要的时间与列表的长度成正比。

因此,我的结论是遍历列表,并检查它是否在一组中。

答案 7 :(得分:1)

如果你不关心重叠元素是什么,你可以简单地检查组合列表的len与组合列表的列表。如果存在重叠元素,则该集合将更短:

如果没有重叠,

len(set(a+b+c))==len(a+b+c)将返回True。

答案 8 :(得分:1)

我将使用函数编程风格抛出另一个:

any(map(lambda x: x in a, b))

<强>解释

map(lambda x: x in a, b)

返回一个布尔列表,其中b中找到了a的元素。然后该列表会传递给any,如果任何元素为True,则会返回True

答案 9 :(得分:-1)

如果您想查看整个列表是否在列表内,您也可以这样做。

index = 0
for a in list_one:
    for _ in list_two: 
        if a == list_two[index]:
            print("match")
            index += 1
        else:
            print("this list did not match at index" + str(index))
            index += 1