问题:测试列表是否共享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
....:
回答 0
简短答案:使用not set(a).isdisjoint(b)
,通常是最快的。
有测试四种常见的方式,如果两个列表a
和b
共享任何项目。第一种选择是将两个都转换为集合并检查它们的交集,如下所示:
bool(set(a) & set(b))
由于集合是使用Python中的哈希表存储的,因此可以搜索它们O(1)
(有关Python中运算符复杂性的更多信息,请参见此处)。从理论上讲,这是O(n+m)
对平均n
和m
在列表中的对象a
和b
。但是1)它必须首先从列表中创建集合,这可能花费不可忽略的时间量; 2)它假定哈希冲突在您的数据中很少。
第二种方法是使用生成器表达式对列表执行迭代,例如:
any(i in a for i in b)
这允许就地搜索,因此不会为中间变量分配新的内存。它也可以在第一个发现上解决。但是in
操作员始终O(n)
在列表中(请参阅此处)。
另一个建议的选项是混合访问列表中的一个,转换另一个集合,然后测试该集合的成员资格,如下所示:
a = set(a); any(i in a for i in b)
第四种方法是利用isdisjoint()
(冻结)集合的方法(请参阅此处),例如:
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
这是此示例的执行时间与列表大小的关系图:
请注意,两个轴都是对数的。这代表了生成器表达式的最佳情况。可以看出,该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
有趣的是,对于较大的列表大小,生成器表达式要慢得多。这仅适用于1000次重复,而不是前一个数字的100000次。当没有共享任何元素时,此设置也很合适,并且是不相交和设置相交方法的最佳情况。
这是两个使用随机数的分析(而不是操纵设置以偏爱一种或多种技术):
分享的可能性很高:元素是从中随机抽取的[1, 2*len(a)]
。分享机会低:元素是从中随机抽取的[1, 1000*len(a)]
。
到目前为止,该分析假设两个列表的大小相同。如果有两个不同大小的列表,例如a
小得多,isdisjoint()
总是更快:
确保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))
。- 混合“遍历列表,按条件测试”
a = set(a); any(i in a for i in b)
通常比其他方法慢。 - 当涉及到不共享元素的列表时,生成器表达式和混合函数比其他两种方法要慢得多。
在大多数情况下,使用该isdisjoint()
方法是最好的方法,因为生成器表达式的执行时间会更长,因为在没有共享任何元素时效率非常低。
回答 1
def lists_overlap3(a, b):
return bool(set(a) & set(b))
注意:以上假设您想要布尔值作为答案。如果您只需要在if
语句中使用表达式,则只需使用if set(a) & set(b):
回答 2
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])
到达后将立即返回True 3 in sb
编辑:另一种变化(感谢Dave Kirby):
def lists_overlap(a, b):
sb = set(b)
return any(itertools.imap(sb.__contains__, a))
这依赖于imap
用C实现的迭代器,而不是生成器理解。它还sb.__contains__
用作映射功能。我不知道这会带来多少性能差异。它仍然会短路。
回答 3
您还可以将其any
与列表理解一起使用:
any([item in a for item in b])
回答 4
在python 2.6或更高版本中,您可以执行以下操作:
return not frozenset(a).isdisjoint(frozenset(b))
回答 5
您可以使用任何内置函数/ wa generator表达式:
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
这个问题已经很老了,但是我注意到当人们在参数集合与列表时,没有人想到将它们一起使用。按照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
因此,遍历一个列表以查看它是否在集合中比遍历两个列表更快,这是有意义的,因为检查数字是否在集合中需要固定的时间,而通过遍历列表进行检查所花费的时间与长度成正比。名单。
因此,我的结论是遍历一个列表,并检查它是否在set中。
回答 7
如果您不在乎重叠的元素是什么,则只需检查len
合并列表与合并为一组的列表的即可。如果有重叠的元素,则集合将更短:
len(set(a+b+c))==len(a+b+c)
如果没有重叠,则返回True。
回答 8
我将以一种功能性的编程风格来介绍另一个:
any(map(lambda x: x in a, b))
说明:
map(lambda x: x in a, b)
返回在其中的元素布尔值的列表b
中找到a
。然后any
将该列表传递给,该列表仅返回True
是否有任何元素True
。