【问题标题】:What is an elegant way to remove duplicate mutable objects in a list in Python?什么是删除 Python 列表中重复的可变对象的优雅方法?
【发布时间】:2020-12-30 13:22:17
【问题描述】:

只需保留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__ 中的一些自定义逻辑定义)?

【问题讨论】:

  • 你什么时候会认为两个可变对象是“相同的”?两个包含[1, 2] 的列表是否相同?或者,如果两者都引用完全相同的列表,您是否会认为它们相同?

标签: python immutability


【解决方案1】:

可以将可变对象存储在哈希表中,但您可能不应该因为如果您有一个指向对象的指针,您就将该对象存储在哈希表中,你改变了对象,然后你尝试通过指针查找它,你(几乎可以肯定)不会找到它,因为哈希现在已经改变了。但是,如果您的特定用例是像这样删除重复项,那么最有效的方法将是哈希。如果您没有使用多线程并且这种重复删除是自包含的,那么您可以安全地使用哈希表(像您拥有的 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) 操作,因此如果您要删除 lists 或 sets 或 dicts 的重复项,转换可能会更简洁它们(或它们的键)到 tuples 或 frozensets 是不可变的,可以在 set 中使用而无需担心(甚至适用于其他对象)。

如果由于某种原因存在中间立场,您认为通过哈希删除重复的可变数据是一个坏主意,但您仍然认为删除重复的可变数据没问题,那么下一个最有效的选择可能是对数据进行排序.这样,您可以在 O(n*log(n)) 时间内排序,然后迭代,而不是 O(n^2) 用于嵌套迭代的幼稚解决方案,只保留当前值不等于最后一个值。这将要求您实现__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

【讨论】:

    【解决方案2】:

    我不知道它是 优雅,但您通常可以将不可散列的项目转换为可散列的对象,例如 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}]
    

    【讨论】:

      【解决方案3】:

      你不需要字典。

      nums = [1, 1, 2, 3, 4, 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(4), X(5)]
      nodups = []
      
      for n in nums:
          if n not in nodups:
              nodups.append(n)
      
      print(nodups)
      

      【讨论】:

        【解决方案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, 2, 3, 4]
        
        lst2 = [{'a':1}, {'a':1}, {'c':3}, {'d':4, 'a':1}, {'d':4, 'a': 1}]
        remove_dups(lst2)
        // Output:  [{'a': 1}, {'c': 3}, {'a': 1, 'd': 4}]
        

        【讨论】:

          猜你喜欢
          • 2022-10-06
          • 1970-01-01
          • 1970-01-01
          • 2011-11-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-09-27
          • 1970-01-01
          相关资源
          最近更新 更多