问题描述
只需保留seen
个对象的备忘,即可删除不可变对象列表中的重复项。
nums = [1,1,2,3,4,4]
duplicates_removed
seen = dict()
for n in nums:
if n not in seen:
duplicates_removed.append(n)
seen[n] = True
但是对于可变对象,您不能将它们哈希到字典中。快速删除列表中重复项(由对象类的__eq__
中的某些自定义逻辑定义)的一种优雅方法是什么?
解决方法
您可以将可变对象存储在哈希表中,您可能不应该,因为如果您有指向对象的指针,则可以将该对象存储在哈希表中,您先对对象进行了变异,然后尝试通过指针查找它,但您(几乎可以肯定)找不到它,因为哈希值现已更改。但是,如果您的特定用例是删除这样的重复项,则最有效的方法将是哈希。如果您不使用多线程并且重复删除是自包含的,则可以安全地使用哈希表(像您一样使用dict
或set
),因为数据实际上是不可变的。在这种情况下,唯一的风险是添加通常没有用的__hash__
函数。一种选择是制作一个实现__hash__
的瘦包装器类,将数据包装在其中以删除重复项,然后将其拆开,如下所示:
class Wrapper(object):
__init__(self,wrapped):
self.wrapped = wrapped
__eq__(self,other):
if not isinstance(other,Wrapper):
return False
return self.wrapped == other.wrapped
__hash__(self):
# custom logic here,e.g.
return 31 * sum(hash(item) for item in self.wrapped)
def without_duplicate_lists(lists):
wrappers = (Wrapper(l) for l in lists)
result = []
seen = set()
for w in wrappers:
if w not in seen:
seen.add(w)
result.append(w)
return [w.wrapped for w in wrappers]
还有其他一些无需计算哈希即可完成此操作的方法,例如使用二进制搜索树(树图)。但是,哈希并不是将可变数据存储在地图中的固有问题,而是如果您以任何方式更改密钥,就会失去价值。对于哈希表,关键是哈希。对于二叉搜索树,它是由关键字本身定义的顺序。如果密钥本身发生更改,则无论使用哪种方法都无法查找。
此外,请注意,在我的示例中,计算哈希是O(n)操作,因此,如果要删除list
s或set
s或dict
s的重复项,将它们(或它们的密钥)转换为不可更改的tuple
或frozenset
可能更清洁,而它们可以在set
中使用而不必担心(甚至适用于其他对象)
如果出于某种原因,您认为通过散列删除可变数据的重复是一个坏主意,但您仍然认为删除可变数据的重复是可以的,那么下一个最有效的选择是对数据进行排序。这样,您可以对O(n * log(n))时间进行排序,然后进行迭代,仅保留当前值不等于最后的值。这将要求您实现__eq__
,__gt__
和__lt__
(或后者之一并使用@total_ordering
):
def without_duplicates_sorted(objs):
objs = sorted(objs)
last = objs[0]
result = [last]
for current in objs[1:]:
if current != last:
result.append(current)
last = current
return result
出于完整性考虑,天真的解决方案:
def without_duplicates_naive(objs):
result = []
for obj in objs:
if obj not in objs[i+1:]:
result.append(obj1)
return result
,
我不知道它是优雅,但是您经常可以将不可哈希的项目转换为可哈希对象,例如frozenset
或元组。例如,您可以像这样转换dict的items():
nums = [{'a':1},{'a':1},{'a':2,'c':3},{'a':3},{'c':3,'a':2},{'b':4},{'a':4}]
duplicates_removed = []
seen = set()
for n in nums:
n_s = frozenset(n.items())
if n_s not in seen:
duplicates_removed.append(n)
seen.add(n_s)
duplicates_removed
# [{'a': 1},{'a': 2,'c': 3},{'a': 3},{'b': 4},{'a': 4}]
,
我们可以将每个元素与上一个元素进行比较。如果它们不相同,则将其添加到包含唯一值的新列表中。
def remove_dups(nums):
duplicates_removed = []
duplicates_removed.append(nums[0])
for i in range(1,len(nums)):
if(nums[i]!=nums[i-1]):
duplicates_removed.append(nums[i])
lst1 = [1,1,2,3,4,4]
remove_dups(lst1)
// Output: [1,4]
lst2 = [{'a':1},{'c':3},{'d':4,'a':1},'a': 1}]
remove_dups(lst2)
// Output: [{'a': 1},{'c': 3},{'a': 1,'d': 4}]
,
您不需要字典。
nums = [1,4]
duplicates_removed = []
for n in nums:
if n not in duplicates_removed:
duplicates_removed.append(n)
自定义类的用法相同
class X:
def __init__(self,n):
self.n = n
def __eq__(self,cmp):
return cmp.n == self.n
def __ne__(self,cmp):
return cmp.n != self.n
def __repr__(self):
return f"X({self.n})"
nums = [X(1),X(1),X(2),X(4),X(5)]
nodups = []
for n in nums:
if n not in nodups:
nodups.append(n)
print(nodups)