testing列表是否共享Python中的任何项目

我想检查一个列表中的任何项目是否存在于另一个列表中。 我可以简单地用下面的代码来做,但我怀疑可能有一个库函数来做到这一点。 如果不是的话,是否有一个更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 ....: 

简短的回答not set(a).isdisjoint(b)使用not set(a).isdisjoint(b) ,通常是最快的。

有四种常见的方法来testing两个列表ab共享任何项目。 第一个选项是将两者都转换成集合并检查它们的交集,如下所示:

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

因为集合使用Python中的哈希表进行存储,所以search它们是O(1) (有关Python中操作符复杂性的更多信息,请参阅这里 )。 理论上,对于列表ab nm对象,这是O(n+m)的平均值。 但是1)它必须首先从列表中创build集合,这可能花费不可忽视的时间量; 2)它假设散列冲突在数据中是稀疏的。

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

 any(i in a for i in b) 

这允许就地search,所以没有新的内存被分配给中间variables。 它也会在第一次find时解救出来。 但是in运算符总是O(n)在列表上 (参见这里 )。

另一个build议的选项是通过列表中的一个进行迭代,将一个集合中的另一个转换成集合,并testing这个集合上的成员资格,如下所示:

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

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

 not set(a).isdisjoint(b) 

如果你search的元素接近数组的开始位置(例如,它被sorting),那么生成器expression式是有利的,因为sets交集方法必须为中间variables分配新的内存:

 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 

下面是列表大小函数的执行时间图:

元素在开始共享时共享测试执行时间

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

另一方面,当search从混合和发生器expression的开始处开始时,如果共享元素系统地位于数组的末尾(或者两个列表不共享任何值),那么不相交和设置交集方法比发生器expression式和混合方法快得多。

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

元素共享测试执行时间最后共享

有意思的是,对于更大的列表大小,生成器expression式要慢一些。 这只是1000次重复,而不是前一个数字100000。 当没有元素被共享时,这种设置也是近似的,并且是不相交和设置相交方法的最佳情况。

以下是使用随机数字的两个分析(而不是使用安装来支持某种技术):

元素共享测试的执行时间为随机生成的数据,共享的可能性很高 元素共享测试的执行时间为随机生成的数据,共享的可能性很高

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

到目前为止,这个分析假设两个列表是相同的大小。 在两个不同大小的列表的情况下,例如a更小, isdisjoint()总是更快:

在开始时共享两个不同大小的列表时,元素共享测试执行时间 元素在最后共享两个不同大小的列表时共享测试执行时间

确保列表更小,否则性能下降。 在这个实验中,列表大小被设置为5

综上所述:

  • 如果列表非常小(<10个元素), not set(a).isdisjoint(b)总是最快的。
  • 如果列表中的元素被sorting或者具有可以利用的规则结构,那么生成器expression式any(i in a for i in b)是大列表中最快的;
  • not set(a).isdisjoint(b)testing集合交集,总是比bool(set(a) & set(b))快。
  • 混合“遍历列表,testing集” a = set(a); any(i in a for i in b) a = set(a); any(i in a for i in b)一般比其他方法慢。
  • 在没有共享元素的情况下,生成器expression式和混合器比其他两种方法慢得多。

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

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

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

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

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

例如:

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

只要3 in sb达到3 in sb就会返回True

编辑:另一种变化(感谢戴夫·柯比):

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

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

你也可以使用any与列表理解:

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

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

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

您可以使用任何内置的函数/ wa生成器expression式:

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

正如约翰和李指出,这给了每个我由两个名单共享的错误结果bool(i)== False。 它应该是:

 return any(i in b for i in a) 

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

len(set(a+b+c))==len(a+b+c)返回True,如果没有重叠。

这个问题相当古老,但我注意到,虽然人们争论集合与列表,但没有人想到将它们一起使用。 以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 

因此,比遍历两个列表更快的迭代通过列表来检查它是否在一个集合中,这是有道理的,因为检查一个集合中的数字是否需要一个恒定的时间,而通过遍历一个列表来检查需要的时间与列表。

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

我会用一个函数式编程风格来抛出另一个:

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

说明:

 map(lambda x: x in a, b) 

返回b中元素的位置的布尔值列表。 然后将该列表传递给任何元素,如果任何元素为True ,则返回True