在Python中删除列表中重复的可变对象的一种优雅方法是什么?

问题描述

只需保留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__中的某些自定义逻辑定义)的一种优雅方法是什么?

解决方法

可以将可变对象存储在哈希表中,您可能不应该,因为如果您有指向对象的指针,则可以将该对象存储在哈希表中,您先对对象进行了变异,然后尝试通过指针查找它,但您(几乎可以肯定)找不到它,因为哈希值现已更改。但是,如果您的特定用例是删除这样的重复项,则最有效的方法将是哈希。如果您不使用多线程并且重复删除是自包含的,则可以安全地使用哈希表(像您一样使用dictset),因为数据实际上是不可变的。在这种情况下,唯一的风险是添加通常没有用的__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的重复项,将它们(或它们的密钥)转换为不可更改的tuplefrozenset可能更清洁,而它们可以在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)