我需要比较存储在唯一列表中的数百个对象来查找重复项:
object_list = {Object_01, Object_02, Object_03, Object_04, Object_05, ...}
我编写了一个自定义函数,如果对象相等则返回True
,如果不是,则返回False
:
object_01.compare(object_02)
>>> True
比较方法运行良好,但每次执行需要花费大量时间。我目前正在使用itertools.combinations(x, 2)
遍历所有组合。我认为使用dict存储已经比较的对象并动态创建新集合是个好主意:
dct = {'Compared': {}}
dct['Compared'] = set()
import itertools
for a, b in itertools.combinations(x, 2):
if b.name not in dct['Compared']:
if compare(a,b) == True:
#print (a,b)
key = a.name
value = b.name
if key not in dct:
dct[key] = set()
dct[key].add(value)
else:
dct[key].add(value)
dct[key].add(key)
dct['Compared'].add(b)
当前输出:
Compared: {'Object_02', 'Object_01', 'Object_03', 'Object_04', 'Object_05'}
Object_01: {'Object_02', 'Object_03', 'Object_01'}
Object_04: {'Object_05', 'Object_04'}
Object_05: {'Object_04'}
...
我想知道:是否有更快的方式来遍历所有组合以及如何打破/阻止对象的迭代,已经分配给重复列表?
期望输出:
Compared: {'Object_02', 'Object_01', 'Object_03', 'Object_04', 'Object_05'}
Object_01: {'Object_02', 'Object_03', 'Object_01'}
Object_04: {'Object_05', 'Object_04'}
...
注意:比较方法是一个c-wrapper。要求是找到围绕它的算法。
答案 0 :(得分:2)
您不需要计算所有组合,只需检查给定项目是否重复:
for i, a in enumerate(x):
if any(a.compare(b) for b in x[:i]):
# a is a duplicate of an already seen item, so do something
这在技术上仍然是O(n ^ 2),但是你已经削减了至少一半所需的检查,并且应该更快一点。
简而言之,x[:i]
会在索引i
之前返回列表中的所有项目。如果项目x[i]
出现在该列表中,您就会知道它是重复的。如果没有,在列表中之后可能会有一个重复的,但是当你到达那里时你会担心。
使用any
在这里也很重要:如果它找到任何真项,它将立即停止,而不检查迭代的其余部分。
您还可以通过从您要检查的列表中删除已知重复项来改进支票数量:
x_copy = x[:]
removed = 0
for i, a in enumerate(x):
if any(a.compare(b) for b in x_copy[:i-removed]):
del x_copy[i-removed]
removed += 1
# a is a duplicate of an already seen item, so do something
请注意,我们使用副本,以避免更改我们重复的序列,并且我们需要考虑使用索引时已删除的项目数。
接下来,我们只需要弄清楚如何构建字典。
这可能有点复杂。第一步是弄清楚哪个元素是重复的。这可以通过实现any
只是for
循环的包装来完成:
def any(iterable):
for item in iterable:
if item: return True
return False
然后我们可以做一个小改动,并传递一个函数:
def first(iterable, fn):
for item in iterable:
if fn(item): return item
return None
现在,我们按如下方式更改重复的查找器:
d = collections.defaultdict(list)
x_copy = x[:]
removed = 0
for i, a in enumerate(x):
b = first(x_copy[:i-removed], a.compare):
if b is not None:
# b is the first occurring duplicate of a
del x_copy[i-removed]
removed += 1
d[b.name].append(a)
else:
# we've not seen a yet, but might see it later
d[a.name].append(a)
这会将列表中的每个元素都放入dict(-like)。如果您只想要重复项,那么只需要获取长度大于1的所有条目。
答案 1 :(得分:1)
如果要查找按属性分组的副本
,请按名称对对象进行分组class Foo:
def __init__(self,i,j):
self.i = i
self.j = j
object_list = {Foo(1,2),Foo(3,4),Foo(1,2),Foo(3,4),Foo(5,6)}
from collections import defaultdict
d = defaultdict(list)
for obj in object_list:
d[(obj.i,obj.j)].append(obj)
print(d)
defaultdict(<type 'list'>, {(1, 2): [<__main__.Foo instance at 0x7fa44ee7d098>, <__main__.Foo instance at 0x7fa44ee7d128>],
(5, 6): [<__main__.Foo instance at 0x7fa44ee7d1b8>],
(3, 4): [<__main__.Foo instance at 0x7fa44ee7d0e0>, <__main__.Foo instance at 0x7fa44ee7d170>]})
如果不是名称,则使用元组存储用于检查比较的所有属性。
或者按重要的属性对列表进行排序,并使用groupby进行分组:
class Foo:
def __init__(self,i,j):
self.i = i
self.j = j
object_list = {Foo(1,2),Foo(3,4),Foo(1,2),Foo(3,4),Foo(5,6)}
from itertools import groupby
from operator import attrgetter
groups = [list(v) for k,v in groupby(sorted(object_list, key=attrgetter("i","j")),key=attrgetter("i","j"))]
print(groups)
[[<__main__.Foo instance at 0x7f794a944d40>, <__main__.Foo instance at 0x7f794a944dd0>], [<__main__.Foo instance at 0x7f794a944d88>, <__main__.Foo instance at 0x7f794a944e18>], [<__main__.Foo instance at 0x7f794a944e60>]]
你也可以实现lt,eq和hash来使你的对象可以排序和散列:
class Foo(object):
def __init__(self,i,j):
self.i = i
self.j = j
def __lt__(self, other):
return (self.i, self.j) < (other.i, other.j)
def __hash__(self):
return hash((self.i,self.j))
def __eq__(self, other):
return (self.i, self.j) == (other.i, other.j)
print(set(object_list))
object_list.sort()
print(map(lambda x: (getattr(x,"i"),getattr(x,"j")),object_list))
set([<__main__.Foo object at 0x7fdff2fc08d0>, <__main__.Foo object at 0x7fdff2fc09d0>, <__main__.Foo object at 0x7fdff2fc0810>])
[(1, 2), (1, 2), (3, 4), (3, 4), (5, 6)]
显然属性需要是可以清除的,如果你有列表,你可以改成元组等。