【问题标题】:How to check if a value is present in any of given sets如何检查任何给定集合中是否存在值
【发布时间】:2015-08-14 18:46:01
【问题描述】:

假设我有不同的集合(它们必须不同,我无法根据我正在使用的数据类型加入它们):

r = set([1,2,3])
s = set([4,5,6])
t = set([7,8,9])

检查给定变量是否存在于其中任何一个中的最佳方法是什么?

我正在使用:

if myvar in r \
   or myvar in s \
   or myvar in t:

但我想知道是否可以通过使用set 的属性(例如union)以某种方式减少这种情况。

以下工作,但我找不到定义多个联合的方法:

if myvar in r.union(s)
   or myvar in t:

我也想知道这个联合是否会以某种方式影响性能,因为我猜会即时创建一个临时的set

【问题讨论】:

  • 我知道标题“如何检查任何给定集合中是否存在值”听起来有点奇怪。如果有人看到更好的编写方式,请随时编辑!
  • @ukemi 感谢您的更正。有趣的是,有两个以上元素的事实使得使用 any 是必要的,而这个特定的关键是我从 Padraic 得到的答案的主要部分:)

标签: python set


【解决方案1】:

随便用一个:

if any(myvar in x for x in  (r,s,t))

集合查找是0(1),因此完全没有必要创建一个联合来检查变量是否在任何集合中,而不是简单地使用inany 进行检查,一旦找到匹配项就会短路并且确实如此不创建新集合。

我也想知道这个联合是否会以某种方式影响性能

是的,合并集合当然会影响性能,它会增加复杂性,您每次都在创建一个新集合,即 O(len(r)+len(s)+len(t)),因此您可以告别使用高效查找集合的真正意义。

所以底线是您想要保持高效的查找,您必须将集合合并一次并将它们保存在内存中创建一个新变量,然后使用它来查找myvar,因此初始创建将是0(n) 之后查找将是 0(1)

如果您不是每次都想先创建联合查找,您将获得长度为r+s+t -> set.union(*(r, s, t)) 的线性解决方案,而不是最坏的三个常量(平均)查找。这也意味着始终从 r,st 添加/删除的新联合集中添加或删除任何元素。

在中等大小的场景中,一些现实的时间安排正好显示了差异:

In [1]: r = set(range(10000))

In [2]: s = set(range(10001,20000))

In [3]: t = set(range(20001,30000))

In [4]: timeit any(29000 in st for st in (r,s,t))
1000000 loops, best of 3: 869 ns per loop  

In [5]: timeit 29000 in r | s | t
1000 loops, best of 3: 956 µs per loop

In [6]: timeit 29000 in reduce(lambda x,y :x.union(y),[r,s,t])
1000 loops, best of 3: 961 µs per loop

In [7]: timeit 29000 in r.union(s).union(t)
1000 loops, best of 3: 953 µs per loop

为联合计时表明几乎所有时间都花在了联合调用上:

In [8]: timeit r.union(s).union(t)
1000 loops, best of 3: 952 µs per loop

使用更大的集合并获取最后一个集合中的元素:

In [15]: r = set(range(1000000))

In [16]: s = set(range(1000001,2000000))

In [17]: t = set(range(2000001,3000000))


In [18]: timeit any(2999999 in st for st in (r,s,t))
1000000 loops, best of 3: 878 ns per loop

In [19]: timeit 2999999 in reduce(lambda x,y :x.union(y),[r,s,t])
1 loops, best of 3: 161 ms per loop

In [20]: timeit 2999999 in r | s | t
10 loops, best of 3: 157 ms per loop

无论使用any 的集合有多大,实际上都没有区别,但是随着集合大小的增加,使用联合的运行时间也会增加。

让它更快的唯一方法是坚持使用or,但我们会花费几百纳秒的差异,这是创建生成器表达式和函数调用的成本:

In [22]: timeit 2999999 in r or 2999999 in s or 2999999 in t
10000000 loops, best of 3: 152 ns per loop

联合集 set.union(*(r, s, t)) 也是最快的,因为您不构建中间集:

In [47]: timeit 2999999 in set.union(*(r,s,t))
10 loops, best of 3: 108 ms per loop
In [49]: r | s | t  == set.union(*(r,s,t))
Out[49]: True

【讨论】:

  • 所以如果我使用union性能很差,而使用any()会更快?
  • @fedorqui,如果你创建了一个集合,那么0(n) n 显然是你的集合的长度。如果您每次要查找 myvar 时都必须这样做,那么您现在必须创建一个集合,该集合涉及在您进行查找之前超过 r+s+t 的长度,所以现在使用 any 可能是三个常量查找线性运算。
  • @fedorqui 使用 any 是一个很好的方法,但在这种情况下 value in r|s|t 更快!查看我的基准答案!
  • @Kasra,您的测试毫无意义,我怀疑 OP 或其他任何人担心三个 3 元素集所需的 tmie
  • @PadraicCunningham 确实就像我在回答中所说的那样,它仅适用于 这种情况,无论如何我必须说对于大型集any 更好。我将其添加到我的答案中。也感谢您指出!
【解决方案2】:

你可以使用内置的any:

r = set([1,2,3])
s = set([4,5,6])
t = set([7,8,9])
if any(myvar in x for x in [r,s,t]):
    print "I'm in one of them"

any 将在返回True 的第一个条件下短路,因此您可以绕过构建一个可能很大的union 或检查可能包含的大量集合。​​

我也想知道这个联合是否会以某种方式影响性能,因为我猜会动态创建一个临时集。

根据wiki.python.com s|tO(len(s)+len(t)) 而查找是O(1)

对于每个带有l 元素的n 集合,迭代地执行union 来构造集合将导致:

a.union(b).union(c).union(d) .... .union(n)

对于a.union(b)O(2l+2l+l) a.union(b).union(c) 等价于O(l+l) 等,总和为O(n*(n+1)/2)*l)

O(n^2*l) 是二次的,并且会抵消使用集合的性能优势。

使用any 在n 个集合中查找将在O(n) 处执行

【讨论】:

  • 我也非常感谢您的回答。很遗憾同一个问题出现了这么多好的答案,我想全部接受:)
  • FWIW, union 是可变参数,可以简化链(和渐近时间)。
【解决方案3】:

|是python中sets的联合运算符。您可以使用| 将多个集合定义为:

>>> r = set([1,2,3])
>>> s = set([4,5,6])
>>> t = set([7,8,9])
>>> r | s | t
set([1, 2, 3, 4, 5, 6, 7, 8, 9])

【讨论】:

  • 所以r | s | t 是将多个union 放在一起的方式。谢谢!
【解决方案4】:

您可以使用reduce 函数将两个参数的函数累积应用于可迭代项

>>> r = set([1,2,3])
>>> s = set([4,5,6])
>>> t = set([7,8,9])
>>> 
>>> reduce(lambda x,y :x.union(y),[r,s,t])
set([1, 2, 3, 4, 5, 6, 7, 8, 9])

为了检查其中任何一个的成员资格,您可以在any 中使用生成器表达式,这在这里更有效,因为python 使用hash table 来存储集合并检查成员船在此类数据中是否有 O(1)像字典或frozenset这样的结构。也可以使用all检查所有集合中的成员资格。

if any(i in item for item in [r,s,t]):
    #do stuff

但在这种情况下(不适用于大型集合)使用or 运算符更快。

value in r|s|t 

这是所有方面的基准:

~$ python -m timeit "r = set([1,2,3]);s = set([4,5,6]);t = set([7,8,9]);3 in reduce(lambda x,y :x.union(y),[r,s,t])"
1000000 loops, best of 3: 1.55 usec per loop
~$ python -m timeit "r = set([1,2,3]);s = set([4,5,6]);t = set([7,8,9]);3 in r|s|t"
1000000 loops, best of 3: 1.11 usec per loop
~$ python -m timeit "r = set([1,2,3]);s = set([4,5,6]);t = set([7,8,9]);any(3 in item for item in [r,s,t])"
1000000 loops, best of 3: 1.24 usec per loop
~$ python -m timeit "r = set([1,2,3]);s = set([4,5,6]);t = set([7,8,9]);3 in r.union(s).union(t)"
1000000 loops, best of 3: 1.19 usec per loop

请注意,正如@Padraic Cunningham 提到的对于大型集合,使用any 非常有效

【讨论】:

  • 创建生成器表达式和函数调用可能需要更多时间,测试三个 3 元素集并不能准确描述联合的低效程度。 ——
  • 感谢您向我展示reduce 功能,我不知道。将记住它以供将来参考:)
  • @PadraicCunningham 确实就像我在回答中所说的那样,它仅适用于 这种情况,无论如何我必须说对于大型集any 更好。我将其添加到我的答案中。也感谢您指出!
  • "reduce(lambda x,y :x.union(y),[r,s,t])" → ...或者只是set().union(r, s, t)。 FWIW 我投了反对票,因为 - 尽管在技术上是正确的 - 这不是一个好主意。
  • @Veedrac 这只是一个建议,将函数累积应用于可迭代的项目。之后我还解释了其他食谱的好处!
【解决方案5】:

你可以这样做

if myvar in r.union(s).union(t)

您不必担心这里的性能。是的,它会即时创建一个临时集,但由于它没有被存储,所以会被垃圾收集。

【讨论】:

  • 有趣!所以一旦被检查就会被垃圾回收。
  • @fedorqui 是的,只要没有对创建对象的引用,它就会被垃圾收集。在这种情况下,由于它 union 没有存储在任何变量中,因此在语句调用之后没有对它的引用。
猜你喜欢
  • 1970-01-01
  • 2014-02-17
  • 1970-01-01
  • 1970-01-01
  • 2019-01-21
  • 2012-03-19
  • 2018-02-27
  • 1970-01-01
  • 2021-01-21
相关资源
最近更新 更多