假设我有这样一个类:
class Spam(object):
def __init__(self, a):
self.a = a
现在我有了这些对象:
s1 = Spam((1, 1, 1, 4))
s2 = Spam((1, 2, 1, 4))
s3 = Spam((1, 2, 1, 4))
s4 = Spam((2, 2, 1, 4))
s5 = Spam((2, 1, 1, 8))
s6 = Spam((2, 1, 1, 8))
objects = [s1, s2, s3, s4, s5, s6]
所以在运行某种方法后,我需要有两个列表,其中一个列表中的对象具有相同的a
属性值,另一个列表中的对象具有唯一的a
属性值。
:
dups = [s2, s3, s5, s6]
normal = [s1, s4]
所以这有点像获得重复,但除此之外,它还应该添加甚至第一次出现的对象共享相同的a
属性值。
我已经写了这个方法,它似乎是工作的,但在我看来它是相当丑陋的(可能不是很理想)。
def eggs(objects):
vals = []
dups = []
normal = []
for obj in objects:
if obj.a in vals:
dups.append(obj)
else:
normal.append(obj)
vals.append(obj.a)
dups_vals = [o.a for o in dups]
# separate again
new_normal = []
for n in normal:
if n.a in dups_vals:
dups.append(n)
else:
new_normal.append(n)
return dups, new_normal
有谁能写出更合适的python方法来解决这个问题吗?
我将把字典中的对象组合在一起,使用a
属性作为键。然后我将它们按组的大小分开。
import collections
def separate_dupes(seq, key_func):
d = collections.defaultdict(list)
for item in seq:
d[key_func(item)].append(item)
dupes = [item for v in d.values() for item in v if len(v) > 1]
uniques = [item for v in d.values() for item in v if len(v) == 1]
return dupes, uniques
class Spam(object):
def __init__(self, a):
self.a = a
#this method is not necessary for the solution, just for displaying the results nicely
def __repr__(self):
return "Spam({})".format(self.a)
s1 = Spam((1, 1, 1, 4))
s2 = Spam((1, 2, 1, 4))
s3 = Spam((1, 2, 1, 4))
s4 = Spam((2, 2, 1, 4))
s5 = Spam((2, 1, 1, 8))
s6 = Spam((2, 1, 1, 8))
objects = [s1, s2, s3, s4, s5, s6]
dupes, uniques = separate_dupes(objects, lambda item: item.a)
print(dupes)
print(uniques)
结果:[Spam((2, 1, 1, 8)), Spam((2, 1, 1, 8)), Spam((1, 2, 1, 4)), Spam((1, 2, 1, 4))]
[Spam((1, 1, 1, 4)), Spam((2, 2, 1, 4))]
如果将__eq__
方法添加到Spam
中,则定义为
def __eq__(self, other):
return self.a == other.a
那么你可以很简单地使用
# you can inline this if you want, just wanted to give it a name
def except_at(elems, ind):
return elems[:ind] + elems[ind+1:]
dups = [obj for (i, obj) in enumerate(objects) if obj in except_at(objects, i)]
normal = [obj for (i, obj) in enumerate(objects) if obj not in except_at(objects, i)]
使用collections.Counter
,这些键是多个键共用的:
import collections
common = [k for (k, v) in collections.Counter([o.a for o in objects]).items() if v > 1]
你的两个列表,现在是
[o for o in objects if o.a in common], [o for o in objects if o.a not in common]
如果对象列表不是太大,一种方法是对对象列表进行排序,然后对其应用groupby
以获得重复项。为了对列表进行排序,我们提供了一个key函数,该函数提取对象的.a
属性的值。
from operator import attrgetter
from itertools import groupby
class Spam(object):
def __init__(self, a):
self.a = a
def __repr__(self):
return 'Spam({})'.format(self.a)
s1 = Spam((1, 1, 1, 4))
s2 = Spam((1, 2, 1, 4))
s3 = Spam((1, 2, 1, 4))
s4 = Spam((2, 2, 1, 4))
s5 = Spam((2, 1, 1, 8))
s6 = Spam((2, 1, 1, 8))
objects = [s1, s2, s3, s4, s5, s6]
keyfunc = attrgetter('a')
dupe, unique = [], []
for k, g in groupby(sorted(objects, key=keyfunc), key=keyfunc):
g = list(g)
target = unique if len(g) == 1 else dupe
target.extend(g)
print('dupe', dupe)
print('unique', unique)
dupe [Spam((1, 2, 1, 4)), Spam((1, 2, 1, 4)), Spam((2, 1, 1, 8)), Spam((2, 1, 1, 8))]
unique [Spam((1, 1, 1, 4)), Spam((2, 2, 1, 4))]