检查一个元素是否存在于两个给定列表中,最简单、最优雅的方法是什么。例如,我有两个列表,如下所示?
>>>a, b = ['a', 'b', 'g', 'r'], ['e', 'g', 'l', 1, 'w']
现在,在上面给出的列表中,我想检查两个列表中是否存在任何元素。目前我正在做如下
def elm_exists(list_a, list_b):
for elm in list_a:
if elm in list_b:
return True
return False
有更优雅的方法吗?
使用以下内容检查a
和b
的元素:
set(a).intersection(b)
示例:
In [44]: nk=set(a).intersection(b)
In [45]: for x in a:
...: if x in nk:
...: print x, 'present in b'
...: else:
...: print x, 'absent in b'
...:
a absent in b
b absent in b
g present in b
r absent in b
还有:
In [47]: timeit(set(a) & set(b))
1000000 loops, best of 3: 940 ns per loop
In [48]: timeit(set(a).intersection(b))
1000000 loops, best of 3: 854 ns per loop
In [49]: timeit([x for x in a if x in b])
1000000 loops, best of 3: 1 us per loop
因此使用CCD_ 3。
这是有效的:
>>> l1,l2=['a', 'b', 'g', 'r'], ['e', 'g', 'l', 1, 'w']
>>> list(set(l1)&set(l2))
['g']
您不需要将两个list
s都转换为set
s,只需要一个即可。我认为跳过不必要的转换会使它更可读、更优雅。
所以,要么:
set(a).intersection(b)
或者:
s = set(a)
any(e in s for e in b)
后者的优点是一旦找到一个匹配就短路,更好地表达逻辑,并返回True
或False
而不是非伪或伪set
,但如果这让你感到困扰的话,它是两行而不是一行。我不知道这种优势是否抵消了将循环放入生成器表达式而不是C函数中的成本。
list
如此之小的性能结果几乎毫无意义,所以让我们试试这个:
In [373]: a=[random.choice(string.ascii_lowercase) for _ in range(10000)]
In [374]: b=[random.choice(string.ascii_lowercase) for _ in range(10000)]
In [375]: %timeit(set(a))
10000 loops, best of 3: 180 us per loop
In [376]: s=set(a) # since all versions need to do this
In [391]: %timeit(s & set(b))
1000000 loops, best of 3: 178 us per loop
In [392]: %timeit(s.intersection(b))
1000000 loops, best of 3: 247 us per loop
In [393]: %timeit(discard(e in s for e in b))
1000000 loops, best of 3: 550 ns per loop
In [394]: %timeit(any(e in s for e in b))
1000000 loops, best of 3: 749 ns per loop
In [395]: %timeit(any(e in a for e in b))
1000000 loops, best of 3: 1.42 us per loop
要将所有数字都放在纳秒级,请加上除最后一个版本外所有版本所需的set(a)
的成本,并比较三个Python版本(苹果股票CPython 2.7.2、Python.org CPython 3.3.0、Homebrew PyPy 1.9.0/2.7.2、所有64位Mac构建(的相同测试:
CP272 CP330 PyPy
s & set(b) 358000 316000 180500
s.intersection(b) 427000 459000 180900
discard(genexp) 180550 157341 90094
any(genexp) 180749 157334 90208
any(list-genexp) 1420 686 960
现在我想一想,这完全有道理。在很早的时候发生碰撞的几率非常高,所以将整个事情转化为一组的成本决定了一切。
这意味着我们需要一个新的测试,有10000个唯一的值。让我们重复测试,用这个:
In [29]: a, b = list(range(10000)), list(range(10000))
In [30]: random.shuffle(a)
In [31]: random.shuffle(b)
CP272 CP330 PyPy
s & set(b) 1277000 1168000 1141000
s.intersection(b) 1165000 1117000 2520000
discard(genexp) 1699000 1271000 770000
any(genexp) 389800 344543 320807
any(list-genexp) 62000 10400 1520
这些都比较合理。它们仍然有意义。如果你在比较随机排列的10000个元素,你需要深入到每一个元素中去多远?这还不足以让set
对其中任何一个列表进行实例化的成本变得值得,更不用说两者了!
因此,让我们尝试一个没有匹配的情况:
In [43]: a=list(range(10000, 20000))
CP272 CP330 PyPy
s & set(b) 751000 770000 733000
s.intersection(b) 466000 530000 1920000
discard(genexp) 1246000 985000 749000
any(genexp) 1269000 966000 893000
any(list-genexp) 185000000 176000000 5870000
我不知道PyPy上一次怎么做得这么快,但除此之外,这里也没什么奇怪的。
那么,哪一个最好呢?
很明显,如果你期望有很多碰撞,你希望尽可能避免生成集合——但如果你期望很少的碰撞,你需要至少生成一个集合。如果你不知道的话,我认为最安全的选择是any(genexp)
——最坏的情况下,它的糟糕程度不到最好的3倍,如果有可能碰撞率很高,它会快得多。但你可以看看这些数字,自己看看。
当然,或者,更好的是,根据您期望遇到的真实测试数据对它们进行计时。
def elm_exists(lista, listb):
return bool(set(lista) & set(listb))
对于所有非空容器,bool
函数将返回True
,对于空容器,返回False
。集合交集(&
(将返回两个集合的公共元素的集合。请注意,集合将删除任何重复项。
或者,也可以在bool
函数中使用set(lista).intersection(listb)
。
>>> y = [1,23,3]
>>> z = [3,432]
>>> (3 in y) and (3 in z)
True
这是另一个解决方案,
>>> c = [filter(lambda x: x in b, sublist) for sublist in a]
>>> filter (lambda a: a != '', c)
['g']