如何从列表中删除所有重复项?

时间:2015-09-03 12:01:50

标签: list delphi unique delphi-xe7 spring4d

考虑这个测试应用程序:

function RemoveDuplicates(const Input: IEnumerable<Integer>): IEnumerable<Integer>;
begin
  // How to implement this function?
end;

var
  Enumerable: IEnumerable<Integer>;
  UniqueEnumerable: IEnumerable<Integer>;
begin
  Enumerable := TCollections.CreateList<Integer>([1, 1, 2, 3, 3, 3, 4]);
  UniqueEnumerable := RemoveDuplicates(Enumerable);
  UniqueEnumerable.ForEach(
    procedure(const I: Integer)
    begin
      WriteLn(I);
    end);
  ReadLn;
end.

如何实现RemoveDuplicates函数(在Haskell中称为nub)?

4 个答案:

答案 0 :(得分:12)

使用已存在的内容:

uses
  Spring.Collections,
  Spring.collections.Extensions;

function RemoveDuplicates(const Input: IEnumerable<Integer>): IEnumerable<Integer>;
begin
  Result := TDistinctIterator<Integer>.Create(Input, nil);
end;

这支持延迟评估(意味着在获得处理结果可枚举之前,不会处理输入)。它在内部使用一个hashset(当前实现为Dictionary)来跟踪已经找到的项目(这发生在枚举器中)。

为什么这很重要?因为任何执行完整枚举的操作可能会导致不必要的性能影响,如果Input涉及其他昂贵的操作,这可能远远超过其他删除重复项的方法的任何好处(例如将其放入列表并对其进行排序)。 IEnumerable也不保证是有限的。

如果在调用此函数和枚举结果之间Input被更改,则更改会影响枚举的结果,而如果您不支持延迟评估则不会出现这种情况。如果您多次枚举,每次结果可能会有所不同(即最新)。

答案 1 :(得分:4)

Jens的解决方案可行,但它的运行时间相当慢(n 2 )。

如果你有一个很长的清单,一个更好的选择是 - 对列表进行排序
- 将每个项目与其后继项目进行比较。

对于搜索总运行时间为O(n log n)的快速排序+ O(n),其运行时间为O(n log n)。

请参阅以下代码(现在无法访问Delphi)。

function RemoveDuplicates(const Input: IEnumerable<Integer>): IEnumerable<Integer>;
var
  List: IList<Integer>;
  i: integer;
begin
  List := TCollections.CreateList<Integer>;
  List.Assign(Input); //Copy input list to output.
  List.Sort;
  for i:= List.Count-1 downto 1 do begin
    if List[i] = List[i-1] then List.delete(i); 
    //if Comparer<T>.Equals(List[i], List[i-1]) then ....
  end; {for i}
end;

<强>问题
这种方法的问题是输出(可能)与输入的顺序不同。这可能是也可能不是问题。

好处(或字典糟糕的原因)
如果分拣是一种廉价的操作,这将是最快的方法 字典的使用对散列的成本很高 即使散列操作是O(1),对于大键也会变得非常昂贵,因为散列将始终处理整个键,而排序比较将在检测到差异时立即停止。 进一步注意,与简单的比较相比,散列操作要昂贵得多(大约慢30倍到100倍)!

只有当列表很大时,才会有更好的渐近运行时间。

答案 2 :(得分:3)

出于性能原因,我建议使用排序列表字典。

function RemoveDuplicates(const Input: IEnumerable<Integer>): IEnumerable<Integer>;
var
  Dictionary: IDictionary<integer, integer>;
  Item: integer;
begin
  Dictionary := TCollections.CreateDictionary<integer,integer>;
  for Item in Input do
    Dictionary.AddOrSetValue(Item, 0);     

  Result := Dictionary.Keys;
end;

答案 3 :(得分:0)

使用中间列表:

function RemoveDuplicates(const Input: IEnumerable<Integer>): IEnumerable<Integer>;
var
  List: IList<Integer>;
begin
  List := TCollections.CreateList<Integer>;
  Input.ForEach(
    procedure(const I: Integer)
    begin
      if not List.Contains(I) then
        List.Add(I);
    end);
  Result := List;
end;

这显然不是性能最佳的解决方案,请参阅其他答案以获得更好的替代方案。