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())
而不是设置(..)
。这将空间需求减少到差异的基数,而您的解决方案使用的内存与集合大小之和成比例。