考虑这个测试应用程序:
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
)?
答案 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;
这显然不是性能最佳的解决方案,请参阅其他答案以获得更好的替代方案。