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

fma*_*ark 115 python intersection list

我想检查一个列表中的任何项目是否存在于另一个列表中.我可以使用下面的代码简单地完成它,但我怀疑可能有一个库函数来执行此操作.如果没有,是否有更多的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
   ....: 
Run Code Online (Sandbox Code Playgroud)

Sor*_*vux 278

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

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

bool(set(a) & set(b))
Run Code Online (Sandbox Code Playgroud)

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

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

any(i in a for i in b)
Run Code Online (Sandbox Code Playgroud)

这允许就地搜索,因此不为中间变量分配新内存.它还拯救了第一个发现.但是in运营商总是O(n)在列表上(见这里).

另一个提议的选项是hybrid来迭代列表中的一个,转换集合中的另一个并测试该集合的成员资格,如下所示:

a = set(a); any(i in a for i in b)
Run Code Online (Sandbox Code Playgroud)

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

not set(a).isdisjoint(b)
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

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

请注意,两个轴都是对数的.这代表了生成器表达式的最佳情况.可以看出,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
Run Code Online (Sandbox Code Playgroud)

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

有趣的是,对于较大的列表大小,生成器表达式会慢一些.这仅重复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()方法是最好的方法,因为生成器表达式执行需要更长的时间,因为当没有共享元素时效率非常低.

  • 那里有一些有用的数据,表明大O分析不是全部,并且结束了关于运行时间的所有推理. (8认同)
  • 感谢@RobM提供的信息.我已经更新了我的答案以反映这一点,并考虑到该线程中提出的其他技术. (2认同)

Joh*_*hin 25

def lists_overlap3(a, b):
    return bool(set(a) & set(b))
Run Code Online (Sandbox Code Playgroud)

注意:以上假设您需要布尔值作为答案.如果您只需要在if语句中使用的表达式,那么只需使用if set(a) & set(b):

  • 这是最坏情况的O(n + m).然而,缺点是它创建了一个新集合,并且在早期发现公共元素时不会挽救. (5认同)
  • 是的,我知道。另外,我只是读了您指向我的参考资料,其中记载了非随机哈希函数(例如内置函数)的更多魔力。我认为它像Java一样需要随机性,这会导致类似http://stackoverflow.com/questions/2634690/good-hash-function-for-a-2d-index的麻烦。我需要不断提醒自己,“ Python不是Java”(感谢神!)。 (2认同)

Mat*_*hen 10

def lists_overlap(a, b):
  sb = set(b)
  return any(el in sb for el in a)
Run Code Online (Sandbox Code Playgroud)

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

例如:

lists_overlap([3,4,5], [1,2,3])
Run Code Online (Sandbox Code Playgroud)

一旦到达,它将立即返回True 3 in sb

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

def lists_overlap(a, b):
  sb = set(b)
  return any(itertools.imap(sb.__contains__, a))
Run Code Online (Sandbox Code Playgroud)

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

  • 相交法中的循环全部用C代码编写;您的方法中有一个循环包含 Python 代码。最大的未知数是是否有可能出现空的交叉路口。 (2认同)
  • 你也可以使用`any(itertools.imap(sb .__ contains _,a))`这应该更快,因为它避免使用lambda函数. (2认同)

小智 6

any您还可以与列表理解一起使用:

any([item in a for item in b])
Run Code Online (Sandbox Code Playgroud)

  • 可以,但时间为 O(n * m),而集合交集方法的时间为 O(n + m)。您也可以在不使用列表理解的情况下执行此操作(丢失“[]”),并且它会运行得更快并且使用更少的内存,但时间仍然是 O(n * m)。 (6认同)
  • 构建“哈希表”的时间复杂度为 O(n)。 (2认同)