Eine elegantere funktionale Lösung:
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)
Verwendet scan
, um Sets aller bisher gesehenen Elemente zu akkumulieren. Verwendet dann zip
, um jedes Element mit dem Satz von Elementen vor ihm zu kombinieren. Schließlich verwendet choose
, um die Elemente herauszufiltern, die im Satz der zuvor gesehenen Elemente enthalten sind, d.h. die Duplikate.
BEARBEITEN
Eigentlich war meine ursprüngliche Antwort völlig falsch. Erstens möchten Sie keine Duplikate in Ihren Ausgaben haben. Zweitens möchten Sie Leistung.
Hier ist eine rein funktionale Lösung, die den von Ihnen gewünschten Algorithmus implementiert:
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)
Das verwendet eine Map, um zu verfolgen, ob jedes Element schon einmal gesehen wurde, einmal oder mehrmals, und gibt dann das Element aus, wenn es gesehen wird und vorher nur einmal gesehen wurde, d.h. das erste Mal, dass es dupliziert wird.
Hier ist eine schnellere imperativ Lösung:
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 }
Dies ist etwa 2× schneller als alle anderen Antworten (zum Zeitpunkt des Schreibens).
Die Verwendung einer for x in xs do
-Schleife zur Aufzählung der Elemente in einer Sequenz ist wesentlich langsamer als die Verwendung von GetEnumerator
direkt, aber die Erstellung des eigenen Enumerator
ist nicht signifikant schneller als die Verwendung eines Ausdrucks mit yield
.
Beachten Sie, dass das TryGetValue
-Element der Dictionary
mir ermöglicht, in der Innen Schleife eine Zuweisung an einen stapelallokierten Wert zu vermeiden, während das von F# angebotene TryGetValue
-Erweiterungselement (von kvb in seiner Antwort verwendet) sein Rückgabetupel alloziert.