如何在保留顺序的同时删除列表中的重复元素?

Posted

技术标签:

【中文标题】如何在保留顺序的同时删除列表中的重复元素?【英文标题】:How to remove duplicate elements in a list while preserving order? 【发布时间】:2013-01-17 22:10:15 【问题描述】:

我刚刚看到a short video from Seth Ladd on Collections

一个集合只有唯一的元素(没有排序),但有时我需要一个有序列表,我想删除所有重复项(第二次出现的元素,例如字符串应该从列表中删除)

列表的原始输入:A, B, C, B, D, A 应导致 A, B, C, D。我需要保持秩序。 B, A, D, C 这样的结果对我没有帮助。

【问题讨论】:

为什么没有这个功能?因为 Dart 团队做出了不提供的相同决定,就像他们之前的许多其他标准库的设计者一样。你想要这样的功能吗?然后你要么必须自己实现它,要么找一个库来为你做。这个问题的目标是什么?找出为什么这不是标准库的一部分,或者找出如何不管这个事实如何去做? 列表上的所有这些操作都是 O(n^2) 天真。为了获得更好的界限,内部使用排序或集合。这应该很容易使用 Sets 和探针为此创建一个辅助函数(如果不存在)。 (至于为什么这些没有在列表本身上定义 - 没有建设性。) How to delete duplicates in a dart List? list.distinct()?的可能重复 (请注意如何删除不相关的问题并更改标题使这个问题“具有建设性”。它也是重复的,但现在可能会有更少的反对票。) 我想知道缺少那个功能的原因是什么。 【参考方案1】:

使用toSet,然后使用toList

  var ids2 = ["A", "B", "C", "B", "D", "A"];
  var result = ids2.toSet().toList();

[A, B, C, D]

【讨论】:

如果我的列表有一个类的实例怎么办?喜欢[Instance of Foo], [Instance of Foo], [Instance of Foo]?是否可以在保留对象实例的同时删除重复项?【参考方案2】:

Justin Fagnani 已经给出了很好的答案。这是另一个:

Iterable distinct(Iterable i) 
  var map = new LinkedHashMap();
  i.forEach((x)  map[x] = true; );
  return map.keys;  // map.keys.toList() would free the map for GC.

【讨论】:

注意:LinkedHashMap 现在在 dart:collection 中,我们可能很快会添加一个 InsertionOrderedSet。【参考方案3】:

自行实现相当容易:

Iterable distinct(Iterable i) 
  var set = new Set();
  return i.where((e) 
    var isNew = !set.contains(e);
    set.add(e);
    return isNew;
  );

如果Set.add() 返回一个表明该集合是否被修改的布尔值,那就更好了:

Iterable distinct(Iterable i) 
  var set = new Set();
  return i.where((e) => set.add(e));

您当然可以提交功能请求错误。

编辑:正如 Florian 指出的那样,上述解决方案仅在返回的 Iterable 仅使用一次时才有效。后续使用将返回没有元素的 Iterators,因为在第一次使用时已经看到偶数元素。

为了解决这个问题,我们需要为从返回的Iterable 创建的每个Iterator 保留一个访问集,而不仅仅是为Iterable 创建一个访问集。我们可以通过创建IterableIterator 子类来做到这一点,例如WhereIterable/WhereIterator

Iterable distinct(Iterable i) => new DistinctIterable(i);

class DistinctIterable<E> extends Iterable<E> 
  final Iterable<E> _iterable;

  DistinctIterable(this._iterable);

  Iterator<E> get iterator 
    return new DistinctIterator<E>(_iterable.iterator);
  


class DistinctIterator<E> extends Iterator<E> 
  final Iterator<E> _iterator;
  final Set<E> _visited = new Set<E>();

  DistinctIterator(this._iterator);

  bool moveNext() 
    while (_iterator.moveNext()) 
      if (!_visited.contains(_iterator.current)) 
        _visited.add(_iterator.current);
        return true;
      
    
    return false;
  

  E get current => _iterator.current;

是的,这要长得多,但它可以与多次使用有限 Iterables 和一次性无限 Iterables 一起正常工作。无限可迭代用例很容易出现内存问题,这是不将其包含在核心库中的一个论据,并迫使开发人员就他们到底需要什么做出一些决定。

【讨论】:

感谢您的回答。我想我像 1 年前一样需要 distinct(),并且我编写了自己的 removeDuplicates()。我认为他们需要一些时间来实现“官方” dart-distinct()。现在我听说它仍然不可用。我只是想知道:为什么? 还有很多东西还没有。您是否提交了功能请求? 解决方案有效,但前提是迭代器仅使用一次。 不小心按了 然后在更新评论之前花了太多时间......它只工作一次的原因是,返回的迭代器每次使用时都会重做'where' .但是“设置”不会被重置。一种简单的解决方案是使用“.toList()”强制评估过滤器。 棘手,@FlorianLoitsch,感谢您指出问题。我在想这会保持懒惰,但我看到了这个问题。这就是为什么这些实用程序应该在核心库中的原因:) 我会尝试添加一个正确的版本。【参考方案4】:

使用泛型和生成器,您可以创建一个适用于任何类型的 Iterables 的函数

Iterable<T> distinct<T>(Iterable<T> elements) sync* 
  final visited = <T>;
  for (final el in elements) 
    if (visited.contains(el)) continue;
    yield el;
    visited.add(el);
  

distinct(["A", "B", "C", "B", "D", "A"])的用法

或者,如果您想将其包装到扩展中:

extension IterableDistinctExt<T> on Iterable<T> 
  Iterable<T> distinct() sync* 
    final visited = <T>;
    for (final el in this) 
      if (visited.contains(el)) continue;
      yield el;
      visited.add(el);
    
  

["A", "B", "C", "B", "D", "A"].distinct()的用法

【讨论】:

以上是关于如何在保留顺序的同时删除列表中的重复元素?的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 中,从列表中删除重复项以使所有元素都是唯一的*同时保留顺序*的最快算法是啥? [复制]

如何删除此列表中的重复元素?

在保留顺序函数的同时删除向量中的重复项的逻辑错误

python删除列表中的重复元素并保持相对顺序不变

从Ruby中的数组中删除重复元素

如何根据元组的索引值从列表中删除重复的元组,同时保持元组的顺序? [复制]