更优雅的功能解决方案:
let duplicates xs =
Seq.scan (fun xs x -> Set.add x xs) Set.empty xs
|> Seq.zip xs
|> Seq.choose (fun (x, xs) -> if Set.contains x xs then Some x else None)
使用scan 来累积到目前为止看到的所有元素的集合。然后使用zip 将每个元素与其之前的元素集组合起来。最后,使用choose 过滤掉之前见过的元素集合中的元素,即重复项。
编辑
其实我原来的答案是完全错误的。首先,您不希望输出中有重复项。其次,您需要性能。
这是一个纯函数式解决方案,可实现您所追求的算法:
let duplicates xs =
(Map.empty, xs)
||> Seq.scan (fun xs x ->
match Map.tryFind x xs with
| None -> Map.add x false xs
| Some false -> Map.add x true xs
| Some true -> xs)
|> Seq.zip xs
|> Seq.choose (fun (x, xs) ->
match Map.tryFind x xs with
| Some false -> Some x
| None | Some true -> None)
这使用一个映射来跟踪每个元素之前是否曾被看到过一次或多次,然后如果该元素之前只被看到过一次,即第一次被复制,则发出该元素。
这是一个更快的命令式版本:
let duplicates (xs: _ seq) =
seq { let d = System.Collections.Generic.Dictionary(HashIdentity.Structural)
let e = xs.GetEnumerator()
while e.MoveNext() do
let x = e.Current
let mutable seen = false
if d.TryGetValue(x, &seen) then
if not seen then
d.[x] <- true
yield x
else
d.[x] <- false }
这比您的任何其他答案快 2 倍左右(在撰写本文时)。
使用for x in xs do 循环枚举序列中的元素比直接使用GetEnumerator 慢得多,但生成自己的Enumerator 并不比使用yield 的计算表达式快得多。
请注意,Dictionary 的 TryGetValue 成员允许我通过改变堆栈分配的值来避免内部循环中的分配,而 F# 提供的 TryGetValue 扩展成员(并由 kvb 在他/她的回答中使用)分配它的返回元组。