比较一个元素是否存在于两个列表中



检查一个元素是否存在于两个给定列表中,最简单、最优雅的方法是什么。例如,我有两个列表,如下所示?

>>>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

有更优雅的方法吗?

使用以下内容检查ab的元素:

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']

您不需要将两个lists都转换为sets,只需要一个即可。我认为跳过不必要的转换会使它更可读、更优雅。

所以,要么:

set(a).intersection(b)

或者:

s = set(a)
any(e in s for e in b)

后者的优点是一旦找到一个匹配就短路,更好地表达逻辑,并返回TrueFalse而不是非伪或伪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']

相关内容

最新更新