如何测试列表中多个值的成员资格

我想测试一个列表中是否有两个或多个值具有成员资格,但我得到了一个意想不到的结果:

>>> 'a','b' in ['b', 'a', 'foo', 'bar']
('a', True)

那么,Python 能够一次测试列表中多个值的成员关系吗? 这个结果意味着什么?


参见: 如何找到列表的交点?。检查指定值的 任何是否在列表中,等效于检查交集是否为非空。检查 所有值是否在列表中,相当于检查它们是否是子集。

183157 次浏览

这就是你想要的,而且几乎在所有情况下都能奏效:

>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
True

表达式 'a','b' in ['b', 'a', 'foo', 'bar']不能像预期的那样工作,因为 Python 将其解释为 tuple:

>>> 'a', 'b'
('a', 'b')
>>> 'a', 5 + 2
('a', 7)
>>> 'a', 'x' in 'xerxes'
('a', True)

其他选择

还有其他方法来执行这个测试,但是它们不适用于许多不同类型的输入。正如 卡比指出的,你可以用集合来解决这个问题..。

>>> set(['a', 'b']).issubset(set(['a', 'b', 'foo', 'bar']))
True
>>> {'a', 'b'} <= {'a', 'b', 'foo', 'bar'}
True

有时候:

>>> {'a', ['b']} <= {'a', ['b'], 'foo', 'bar'}
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'

集合只能用散列元素创建。但是生成器表达式 all(x in container for x in items)几乎可以处理任何容器类型。唯一的要求是 container是可重复的(即不是生成器)。items可以是任何可迭代的。

>>> container = [['b'], 'a', 'foo', 'bar']
>>> items = (i for i in ('a', ['b']))
>>> all(x in [['b'], 'a', 'foo', 'bar'] for x in items)
True

车速测试

在许多情况下,子集测试会比 all更快,但是这种差异并不令人震惊——除非问题是不相关的,因为集合不是一个选项。仅仅为了这样的测试而将列表转换为集合并不总是值得的。将发电机组转换成发电机组有时会造成难以置信的浪费,从而使程序运行速度大大降低数量级。

下面是一些用于说明的基准。当 containeritems都相对较小时,差异最大。在这种情况下,子集方法大约比数量级方法快一点:

>>> smallset = set(range(10))
>>> smallsubset = set(range(5))
>>> %timeit smallset >= smallsubset
110 ns ± 0.702 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
>>> %timeit all(x in smallset for x in smallsubset)
951 ns ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

这看起来差别很大。但是,只要 container是一个集合,all仍然是完全可用的在大得多的规模:

>>> bigset = set(range(100000))
>>> bigsubset = set(range(50000))
>>> %timeit bigset >= bigsubset
1.14 ms ± 13.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
>>> %timeit all(x in bigset for x in bigsubset)
5.96 ms ± 37 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

使用子集测试仍然更快,但是在这种规模下只有大约5倍。速度的提升归功于 Python 支持 set的快速 c实现,但是两种情况下的基本算法是相同的。

如果出于其他原因,您的 items已经存储在一个列表中,那么在使用子集测试方法之前,您必须将它们转换为一个集合。然后加速下降到大约2.5倍:

>>> %timeit bigset >= set(bigsubseq)
2.1 ms ± 49.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

如果你的 container是一个序列,需要先转换,那么加速就更小了:

>>> %timeit set(bigseq) >= set(bigsubseq)
4.36 ms ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

唯一一次我们得到灾难性的缓慢结果是当我们按顺序离开 container:

>>> %timeit all(x in bigseq for x in bigsubseq)
184 ms ± 994 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

当然,只有在必要的时候我们才会这么做。如果 bigseq中的所有项目都是散列的,那么我们将这样做:

>>> %timeit bigset = set(bigseq); all(x in bigset for x in bigsubseq)
7.24 ms ± 78 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

这只比另一种方法快了1.66倍(set(bigseq) >= set(bigsubseq),计时高于4.36)。

因此,子集测试通常更快,但不是难以置信的快。另一方面,让我们看看 all什么时候更快。如果 items是一千万个长度的值,并且可能有不在 container中的值,那么该怎么办?

>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); set(bigset) >= set(hugeiter)
13.1 s ± 167 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
>>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); all(x in bigset for x in hugeiter)
2.33 ms ± 65.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

在这种情况下,将发电机转换成一台发电机组实在是难以置信的浪费。set构造函数必须使用整个生成器。但是 all的短路行为确保了只需要使用发生器的一小部分,因此它比 数量级的子集测试更快。

不可否认,这是一个极端的例子。但是,正如它所显示的,您不能假定在所有情况下,某种方法或另一种方法都会更快。

结局

大多数情况下,将 container转换为一个集合是值得的,至少如果它的所有元素都是散列的。这是因为集合的 in是 O (1) ,而序列的 in是 O (n)。

另一方面,使用子集测试可能只是有时候值得。如果您的测试项目已经存储在一个集合中,那么一定要这样做。否则,all只是稍微慢一点,不需要任何额外的存储空间。它还可以用于大型项目生成器,并且在这种情况下有时提供大量的加速。

我很确定 in的优先级高于 ,,因此您的语句被解释为 'a', ('b' in ['b' ...]),然后计算为 'a', True,因为 'b'在数组中。

有关如何做您想要做的事情,请参见前面的答案。

Python 解析器将该语句作为元组进行计算,其中第一个值是 'a',第二个值是表达式 'b' in ['b', 'a', 'foo', 'bar'](计算结果为 True)。

不过,你可以编写一个简单的函数:

def all_in(candidates, sequence):
for element in candidates:
if element not in sequence:
return False
return True

就像这样:

>>> all_in(('a', 'b'), ['b', 'a', 'foo', 'bar'])
True

另一种方法是:

>>> set(['a','b']).issubset( ['b','a','foo','bar'] )
True

这里给出的两个答案都不会处理重复的元素。例如,如果您正在测试[1,2,2]是否是[1,2,3,4]的子列表,则两者都将返回 True。也许你是这个意思,但我只是想澄清一下。 如果要在[1,2,3,4]中为[1,2,2]返回 false,则需要对两个列表进行排序,并用每个列表上的移动索引检查每个项。只是 for 循环稍微复杂一点。

没有 lambdas 你怎么能成为 pythonic! . . 不要当真. . 但这种方式也行得通:

orig_array = [ ..... ]
test_array = [ ... ]


filter(lambda x:x in test_array, orig_array) == test_array

如果要测试值的 任何是否在数组中,请省略结束部分:

filter(lambda x:x in test_array, orig_array)

我觉得我们甚至可以省略那些方括号。

array = ['b', 'a', 'foo', 'bar']
all([i in array for i in 'a', 'b'])
[x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]

我认为这比选择的答案更好的原因是,您实际上不需要调用‘ all ()’函数。在 IF 语句中,空列表的计算结果为 False,而非空列表的计算结果为 True。

if [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]:
...Do something...

例如:

>>> [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]
['a', 'b']
>>> [x for x in ['G','F'] if x in ['b', 'a', 'foo', 'bar']]
[]

如果你想检查 所有的输入匹配,

>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])

如果你想检查 至少有一个匹配,

>>> any(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])

我是这么做的:

A = ['a','b','c']
B = ['c']
logic = [(x in B) for x in A]
if True in logic:
do something
# This is to extract all count of all combinations inside list of
# list
import itertools


l = [[1,2,3],[6,5,4,3,7,2],[4,3,2,9],[6,7],[5,1,0],[6,3,2,7]]
els = list(set(b for a in l for b in a))
sol = {}


def valid(p):
for s in l:
if set(p).issubset(set(s)):
if p in sol.keys():
sol[p] += 1
else:
sol[p] = 1


for c in itertools.combinations(els, 2):
valid(c)
# {(0, 1): 1,
# (0, 5): 1,
# (1, 2): 1,
# (1, 3): 1,
# (1, 5): 1,
# (2, 3): 4,
# (2, 4): 2,
# (2, 5): 1,
# (2, 6): 2,
# (2, 7): 2,
# (2, 9): 1,
# (3, 4): 2,
# (3, 5): 1,
# (3, 6): 2,
# (3, 7): 2,
# (3, 9): 1,
# (4, 5): 1,
# (4, 6): 1,
# (4, 7): 1,
# (4, 9): 1,
# (5, 6): 1,
# (5, 7): 1,
# (6, 7): 3}

任何

在 Python 3中,您可以使用集的交集作为 any:

>>> {'a','b'} & set(['b', 'a', 'foo', 'bar'])
{'a', 'b'}


>>> {'a','b'} & set(['b', 1, 'foo', 'bar'])
{'b'}

当然,你可以将结果包装成一个 True/False值的集合:

>>> bool({'a','b'} & set(['b', 1, 'foo', 'bar']))
True


>>> bool({'c'} & set(['b', 1, 'foo', 'bar']))
False

全部

利用‘的子集:

>>> {'a','b'}.issubset(set(['b', 'a', 'foo', 'bar']))
True


>>> {'a','b'}.issubset(set(['b', 1, 'foo', 'bar']))
False

笔记

  • bool()将设置转换为布尔值
  • issubset()寻找一个集合作为另一个集合的完整子集
  • &可以用于集合的求交(任意)

例子可以通过使用变量来清除:

test = {'a','b'}
values = set(['b', 'a', 'foo', 'bar'])


# Any
test & values         # {'a', 'b'}
bool(test & values)   # True


# All
test.issubset(values) # True