Python 在特定于列的列表比较中高效地获取唯一条目

Python 在特定于列的列表比较中高效地获取唯一条目,python,python-2.7,Python,Python 2.7,我有以下两个清单: lst1 = [('vr1', '635', '1'), ('vr1', '32', '1'), ('vr1', '784', '0.526'), ('vr1', '431', '1')] lst2 = [('vr1', '635', '3'), ('vr1', '784', '2.526'), ('vr1', '431', '2')] 目前,我使用以下代码确定lst1的唯一性。其中,比较基于每个条目前两列的内容 uniq = set([i[0:2] for i in l

我有以下两个清单:

lst1 = [('vr1', '635', '1'), ('vr1', '32', '1'), ('vr1', '784', '0.526'), ('vr1', '431', '1')]

lst2 = [('vr1', '635', '3'), ('vr1', '784', '2.526'), ('vr1', '431', '2')]
目前,我使用以下代码确定
lst1
的唯一性。其中,比较基于每个条目前两列的内容

uniq = set([i[0:2] for i in lst1]).difference([j[0:2] for j in lst2])
给予:

set([('vr1', '32')])
然后,我在
lst1
中搜索每个条目(如果它包含在
uniq
中),以获得完整的条目

uniq_full = [i for i in lst1 if i[0:2] in uniq]
以我想要的方式返回条目

[('vr1', '32', '1')]

我的问题是:有没有更快的方法获得完整条目?

目前,您正在通过
lst1
lst2
循环一次以生成
uniq
。然后再次循环通过
lst1
生成
uniq\u full

相反,您可以循环通过
lst2
一次来生成要删除的密钥, 然后循环通过
lst1
一次以过滤掉不需要的元素:

lst1 = [('vr1', '635', '1'), ('vr1', '32', '1'), ('vr1', '784', '0.526'), ('vr1', '431', '1')]    
lst2 = [('vr1', '635', '3'), ('vr1', '784', '2.526'), ('vr1', '431', '2')]

remove_keys = set([item[:2] for item in lst2])
unique = [item for item in lst1 if item[:2] not in remove_keys]
print(unique)
屈服

[('vr1', '32', '1')]

下面是一个timeit测试(使用),它显示速度更快:

def orig(lst1, lst2):
    uniq = set([i[0:2] for i in lst1]).difference([j[0:2] for j in lst2])
    uniq_full = [i for i in lst1 if i[0:2] in uniq]
    return uniq_full

def alt(lst1, lst2):
    remove_keys = set([item[:2] for item in lst2])
    unique = [item for item in lst1 if item[:2] not in remove_keys]
    return unique

In [4]: %timeit orig(lst1, lst2)
100000 loops, best of 3: 2.29 µs per loop

In [5]: %timeit alt(lst1, lst2)
1000000 loops, best of 3: 1.36 µs per loop
def using_dicts(lst1, lst2):
    d1 = {x[0:2]:x for x in lst1}
    d2 = {x[0:2]:x for x in lst2}
    return [d1[key] for key in set(d1) - set(d2)]

关于@otus的评论(如下):让我们看看在创建
remove\u键时使用生成器表达式是否可以提高速度:

def alt2(lst1, lst2):
    remove_keys = set(item[:2] for item in lst2)
    unique = [item for item in lst1 if item[:2] not in remove_keys]
    return unique

In [7]: %timeit alt2(lst1, lst2)
1000000 loops, best of 3: 1.54 µs per loop
以下是包含10**4项的列表的基准:

In [8]: lst1 = [('vr1', '635', '1'), ('vr1', '32', '1'), ('vr1', '784', '0.526'), ('vr1', '431', '1')]*10000

In [9]: lst2 = [('vr1', '635', '3'), ('vr1', '784', '2.526'), ('vr1', '431', '2')]*10000

In [10]: %timeit alt(lst1, lst2)
100 loops, best of 3: 9.34 ms per loop

In [11]: %timeit alt2(lst1, lst2)
100 loops, best of 3: 9.49 ms per loop

In [12]: %timeit orig(lst1, lst2)
100 loops, best of 3: 13.5 ms per loop
下面是另一个10**6项列表的基准:

In [19]: %timeit alt(lst1, lst2)
1 loops, best of 3: 972 ms per loop

In [20]: %timeit alt2(lst1, lst2)
1 loops, best of 3: 957 ms per loop
所以对于小型或中型列表,列表理解速度更快,但对于大型列表,使用生成器表达式更快。当然,大小取决于您的机器。您需要使用基准测试来了解什么最适合您

通常,当您有足够的内存时,如果需要遍历整个集合,那么使用列表理解似乎比使用生成器更快。当您没有足够的内存或不需要遍历整个集合时,生成器会更好


看看@thg435的解决方案也很有趣。在某些情况下,速度更快:

def orig(lst1, lst2):
    uniq = set([i[0:2] for i in lst1]).difference([j[0:2] for j in lst2])
    uniq_full = [i for i in lst1 if i[0:2] in uniq]
    return uniq_full

def alt(lst1, lst2):
    remove_keys = set([item[:2] for item in lst2])
    unique = [item for item in lst1 if item[:2] not in remove_keys]
    return unique

In [4]: %timeit orig(lst1, lst2)
100000 loops, best of 3: 2.29 µs per loop

In [5]: %timeit alt(lst1, lst2)
1000000 loops, best of 3: 1.36 µs per loop
def using_dicts(lst1, lst2):
    d1 = {x[0:2]:x for x in lst1}
    d2 = {x[0:2]:x for x in lst2}
    return [d1[key] for key in set(d1) - set(d2)]
如果列表中包含大量重复键:

lst1 = [('vr1', '635', '1'), ('vr1', '32', '1'), ('vr1', '784', '0.526'), ('vr1', '431', '1')]*10000

lst2 = [('vr1', '635', '3'), ('vr1', '784', '2.526'), ('vr1', '431', '2')]*10000
那么
使用dicts
alt
更快:

In [31]: %timeit alt(lst1, lst2)
100 loops, best of 3: 8.39 ms per loop

In [32]: %timeit using_dicts(lst1, lst2)
100 loops, best of 3: 7.98 ms per loop
我认为这是因为在上面的例子中,
lst1
lst2
包含了太多重复的
x[0:2]
s,以至于
d1
d2
都很小

如果您的
lst1
lst2
包含许多唯一键,例如

lst1 = [(i,i,i) for i in range(10**4+100)]
lst2 = [(i,i,i) for i in range(10**4)]
然后
alt
比使用dicts
更快:

In [34]: %timeit alt(lst1, lst2)
100 loops, best of 3: 3.12 ms per loop

In [35]: %timeit using_dicts(lst1, lst2)
100 loops, best of 3: 5.93 ms per loop

如果uniq中的i[:2]与lst2中的j中的i[:2]不在[j[0:2]中,则您的
相当于
。因此,您可以简单地将第二个列表转换为一个集合,并对其进行测试

second = set(i[:2] for i in lst2)
full = [i for i in lst1 if i[:2] not in second]

您无需使用代理模式进行最后一次搜索即可实现这一点:

class Wrapper:
    def __init__(self, tuple):
        self.tuple = tuple

    def __eq__(self, other):
        return self.tuple[0:2] == other.tuple[0:2]

    def __ne__(self, other):
        return not self.__eq__(other)

    def __hash__(self):
        return 0

    def __str__(self):
        return str(self.tuple)

    def __repr__(self):
        return repr(self.tuple)

lst1 = [Wrapper(('vr1', '635', '1')), Wrapper(('vr1', '32', '1')), Wrapper(('vr1', '784', '0.526')), Wrapper(('vr1', '431', '1'))]

lst2 = [Wrapper(('vr1', '635', '3')), Wrapper(('vr1', '784', '2.526')), Wrapper(('vr1', '431', '2'))]

uniq = set(lst1) - set(lst2)

print uniq
给出:

集合([('vr1','32','1'))


这要简单得多。我的唯一优点是,您可以在
包装器中配置比较。但不确定速度。

我只需使用
dict
s Not set:

lst1 = [('vr1', '635', '1'), ('vr1', '32', '1'), ('vr1', '784', '0.526'), ('vr1', '431', '1')]
lst2 = [('vr1', '635', '3'), ('vr1', '784', '2.526'), ('vr1', '431', '2')]

d1 = {x[0:2]:x for x in lst1}
d2 = {x[0:2]:x for x in lst2}

for key in set(d1) - set(d2):
    print d1[key]   # ('vr1', '32', '1')

不知道这是否更快,但看起来更透明。

哇。。。抱歉,有点过度工程化了?您可以通过使用生成器表达式作为
set()
参数来避免中间列表的生成,就像我的回答中那样。即,只需删除
[]
。我想知道这是否会改善计时?奥特斯:我在上面添加了一个timeit测试。谢谢。列表非常短,因此设置生成器时的所有额外工作可能会支配列表分配的开销。对于足够大的列表,情况可能正好相反。@otus:我使用的实际列表每个都包含大约60k个条目,这是否足够大以产生差异?@Mvsm:没有测试就无法知道。请注意,在python3中,您可以对d1.keys()-d2.keys()中的键执行
,而在python2中,您可以使用
viewkeys())
而不是
设置(..)
。这将空间需求减少到差异的基数,而您的解决方案使用的内存与集合大小之和成比例。