【问题标题】:Test if lists share any items in python测试列表是否共享python中的任何项目
【发布时间】:2011-03-11 08:21:28
【问题描述】:

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

【问题讨论】:

标签: list python intersection


【解决方案1】:

简答:使用not set(a).isdisjoint(b),一般是最快的。

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

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

因为 集合是使用 Python 中的哈希表存储的,所以搜索它们是 O(1)(有关 Python 中运算符复杂性的更多信息,请参阅 here)。理论上,对于列表ab 中的nm 对象,这是O(n+m) 的平均值。但是 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

这是此示例的执行时间图表,以列表大小为函数:

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

总结:

  • 如果列表非常小(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 分析并不是关于运行时间的全部推理。
  • 最坏的情况呢? any 在第一个非 False 值处退出。使用唯一匹配值位于末尾的列表,我们得到: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 次迭代。
  • 感谢@RobM 提供的信息。我已更新我的答案以反映这一点并考虑到此线程中提出的其他技术。
  • 测试两个列表是否共享一个成员应该是not set(a).isdisjoint(b)。如果两个列表共享一个成员,set(a).isdisjoint(b) 将返回 True。答案应该被编辑?
  • 感谢@Guillochon 的提醒,它已修复。
【解决方案2】:
def lists_overlap3(a, b):
    return bool(set(a) & set(b))

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

【讨论】:

  • 这是最坏情况 O(n + m)。然而,不利的一面是它会创建一个新集合,并且在早期发现一个共同元素时不会退出。
  • 我很好奇为什么这是O(n + m)。我的猜测是集合是使用哈希表实现的,因此in 运算符可以在O(1) 时间内工作(退化情况除外)。它是否正确?如果是这样,考虑到哈希表在最差情况下的查找性能为O(n),这是否意味着在最坏情况下它会具有O(n * m) 的性能?
  • @fmark:理论上,你是对的。实际上,没有人关心;读取 CPython 源代码中 Objects/dictobject.c 中的 cmets(集合只是只有键的字典,没有值),看看是否可以生成会导致 O(n) 查找性能的键列表。
  • 好的,感谢您的澄清,我想知道是否发生了一些魔法:)。虽然我同意实际上我不需要关心,但生成将导致O(n) 查找性能的键列表是微不足道的;),请参阅pastebin.com/Kn3kAW7u 仅适用于 lafs。
  • 是的,我知道。另外,我刚刚阅读了您指向我的源代码,其中记录了在非随机散列函数(例如内置函数)的情况下更加神奇。我认为它需要随机性,就像 Java 一样,这会导致像 stackoverflow.com/questions/2634690/… 这样的怪物。我需要不断提醒自己 Python 不是 Java(感谢上帝!)。
【解决方案3】:
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就会返回True

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

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

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

【讨论】:

  • 相交方法中的循环都是C代码;您的方法中有一个循环,其中包含 Python 代码。最大的未知数是空路口是否可能。
  • 您也可以使用any(itertools.imap(sb.__contains__, a)),因为它避免使用 lambda 函数,所以应该更快。
  • 谢谢,@Dave。 :) 我同意删除 lambda 是一个胜利。
【解决方案4】:

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

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

【讨论】:

  • 你可以,但是时间是 O(n * m),而设置交集方法的时间是 O(n + m)。你也可以不使用列表理解(丢失[]),它会运行得更快,使用更少的内存,但时间仍然是 O(n * m)。
  • 虽然您的大 O 分析是正确的,但我怀疑对于较小的 n 和 m 值,构建底层哈希表所需的时间将会发挥作用。 Big O 忽略了计算散列所花费的时间。
  • 建立一个“哈希表”的摊销时间为 O(n)。
  • 我明白了,但你扔掉的常数很大。对于较大的 n 值无关紧要,但对于较小的值则有关系。
【解决方案5】:

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

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

【讨论】:

  • 似乎不必提供 set 或 freezeset 作为第一个参数。我尝试了一个字符串并且它有效(即:任何可迭代都可以)。
【解决方案6】:

您可以使用任何内置函数 /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)

【讨论】:

  • Amplifying Lie Ryan 的评论:对于位于 bool(x) 为 False 的交点中的任何项目 x 都会给出错误的结果。在 Lie Ryan 的示例中,x 为 0。唯一的修复是 any(True for i in a if i in b),最好写成已经见过的 any(i in b for i in a)
  • 更正:当所有x在交集使得bool(x)False时会给出错误的结果。
【解决方案7】:

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

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

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

【讨论】:

  • 在@Toughy 指示的(冻结)集上使用isdisjoint() 方法会更好: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.00913715362548828
【解决方案8】:

如果您不关心重叠元素可能是什么,您可以简单地检查组合列表的len 与组合为一组的列表。如果有重叠的元素,集合会更短:

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

【讨论】:

  • 如果第一个值重叠,它仍然会将整个列表转换为一个集合,无论多大。
【解决方案9】:

我会用函数式编程风格再介绍一个:

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

说明:

map(lambda x: x in a, b)

返回一个布尔值列表,其中b 的元素在a 中找到。然后将该列表传递给any,如果有任何元素是True,它只会返回True

【讨论】:

    猜你喜欢
    • 2012-09-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-25
    • 1970-01-01
    • 2015-10-27
    相关资源
    最近更新 更多