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两个列表a
和b
共享任何项目。 第一个选项是将两者都转换成集合并检查它们的交集,如下所示:
bool(set(a) & set(b))
因为集合使用Python中的哈希表进行存储,所以search它们是O(1)
(有关Python中操作符复杂性的更多信息,请参阅这里 )。 理论上,对于列表a
和b
n
和m
对象,这是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
。