如何测试列表中多个值的成员资格
- 2025-01-10 08:47:00
- admin 原创
- 99
问题描述:
我想测试两个或多个值是否属于列表成员,但是得到了意外的结果:
>>> 'a','b' in ['b', 'a', 'foo', 'bar']
('a', True)
那么,Python 能否一次测试列表中多个值的成员资格?结果意味着什么?
另请参阅:如何查找列表交集?。检查列表中是否有任何指定的值,相当于检查交集是否非空。检查列表中是否包含所有值,相当于检查它们是否是子集。
解决方案 1:
这将满足您的要求,并且几乎在所有情况下都有效:
>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
True
该表达式'a','b' in ['b', 'a', 'foo', 'bar']
无法按预期工作,因为 Python 将其解释为元组:
>>> 'a', 'b'
('a', 'b')
>>> 'a', 5 + 2
('a', 7)
>>> 'a', 'x' in 'xerxes'
('a', True)
其他选项
还有其他方法可以执行此测试,但它们不适用于多种不同类型的输入。正如Kabie指出的那样,您可以使用集合来解决这个问题...
>>> 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
,但差异并不令人震惊——除非问题无关紧要,因为集合不是一种选择。仅仅为了进行这样的测试而将列表转换为集合并不总是值得的。而将生成器转换为集合有时会非常浪费,会使程序速度降低许多个数量级。
以下是一些基准测试,仅供参考。最大的区别在于当container
和都items
相对较小时。在这种情况下,子集方法的速度大约快一个数量级:
>>> 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 的快速c
支持实现set
,但两种情况下的基本算法是相同的。
如果您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
只会慢一点,并且不需要任何额外的存储空间。它也可以用于大型项目生成器,有时在这种情况下可以提供巨大的加速。
解决方案 2:
另一种方法:
>>> set(['a','b']).issubset( ['b','a','foo','bar'] )
True
解决方案 3:
如果你想检查所有输入匹配项,
>>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
如果你想检查至少一个匹配项,
>>> any(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
解决方案 4:
我很确定in
具有比更高的优先级,,
因此您的语句被解释为'a', ('b' in ['b' ...])
,然后计算为,'a', True
因为'b'
在数组中。
请参阅前面的答案以了解如何做你想做的事情。
解决方案 5:
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
解决方案 6:
我想说我们甚至可以省略那些方括号。
array = ['b', 'a', 'foo', 'bar']
all([i in array for i in 'a', 'b'])
解决方案 7:
[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']]
[]
解决方案 8:
这里给出的两个答案都不会处理重复元素。例如,如果您正在测试 [1,2,2] 是否是 [1,2,3,4] 的子列表,则两者都将返回 True。那可能是您想要做的,但我只是想澄清一下。如果您想对 [1,2,2] in [1,2,3,4] 返回 false,您需要对两个列表进行排序,并使用每个列表上的移动索引检查每个项目。只是一个稍微复杂一点的 for 循环。
解决方案 9:
任何
在 Python3 中,你可以使用集合的交集作为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
全部
利用issubset
:
>>> {'a','b'}.issubset(set(['b', 'a', 'foo', 'bar']))
True
>>> {'a','b'}.issubset(set(['b', 1, 'foo', 'bar']))
False
笔记
bool()
将集合转换为布尔值issubset()
寻找一个集合是另一个集合的完整子集&
可以用于集合的交集(任意)
示例的更简洁版本,使用变量:
# Variable setup:
test = {'a','b'}
values = set(['b', 'a', 'foo', 'bar'])
# Example: Any
test & values # {'a', 'b'}
bool(test & values) # True
# Example: All
test.issubset(values) # True
解决方案 10:
以下是我的做法:
A = ['a','b','c']
B = ['c']
logic = [(x in B) for x in A]
if True in logic:
do something
解决方案 11:
没有 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)
解决方案 12:
# 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}