F #: a remoção de duplicatas de uma seq é lenta
Estou tentando escrever uma função que elimina duplicatas consecutivas, conforme determinado por uma determinada função de igualdade, de umseq<'a>
mas com um toque: eu preciso doúltimo duplicar de uma execução de duplicatas para torná-lo na sequência resultante. Por exemplo, se eu tiver uma sequência[("a", 1); ("b", 2); ("b", 3); ("b", 4); ("c", 5)]
e eu estou usandofun ((x1, y1),(x2, y2)) -> x1=x2
para verificar a igualdade, o resultado que eu quero ver é[("a", 1); ("b", 4); ("c", 5)]
. O ponto desta função é que tenho pontos de dados chegando, onde às vezes os pontos de dados legitimamente têm o mesmo registro de data e hora, mas eu me preocupo apenas com o último, portanto, quero descartar os anteriores com o mesmo registro de data e hora. A função que eu implementei é a seguinte:
let rec dedupeTakingLast equalityFn prev s = seq {
match ( Seq.isEmpty s ) with
| true -> match prev with
| None -> yield! s
| Some value -> yield value
| false ->
match prev with
| None -> yield! dedupeTakingLast equalityFn (Some (Seq.head s)) (Seq.tail s)
| Some prevValue ->
if not (equalityFn(prevValue, (Seq.head s))) then
yield prevValue
yield! dedupeTakingLast equalityFn (Some (Seq.head s)) (Seq.tail s)
}
Em termos de realmente fazer o trabalho, ele funciona:
> [("a", 1); ("b", 2); ("b", 3); ("b", 4); ("c", 5)]
|> dedupeTakingLast (fun ((x1, y1),(x2, y2)) -> x1=x2) None
|> List.ofSeq;;
val it : (string * int) list = [("a", 1); ("b", 4); ("c", 5)]
No entanto, em termos de desempenho, é um desastre:
> #time
List.init 1000 (fun _ -> 1)
|> dedupeTakingLast (fun (x,y) -> x = y) None
|> List.ofSeq
#time;;
--> Timing now on
Real: 00:00:09.958, CPU: 00:00:10.046, GC gen0: 54, gen1: 1, gen2: 1
val it : int list = [1]
--> Timing now off
Claramente, estou fazendo algo muito idiota aqui, mas não consigo ver o que é. De onde vem o impacto no desempenho? Percebo que existem implementações melhores, mas estou especificamente interessado em entender por queesta implementação é tão lenta.
EDIT: FYI, conseguiu uma implementação decente no estilo funcional que se baseia emSeq.
somente funções. O desempenho é bom, demorando cerca de 1,6x o tempo de implementação do estilo imperativo@gradbot abaixo que usa iteradores. Parece que a raiz do problema é o uso deSeq.head
eSeq.tail
em chamadas recursivas no meu esforço original.
let dedupeTakingLastSeq equalityFn s =
s
|> Seq.map Some
|> fun x -> Seq.append x [None]
|> Seq.pairwise
|> Seq.map (fun (x,y) ->
match (x,y) with
| (Some a, Some b) -> (if (equalityFn a b) then None else Some a)
| (_,None) -> x
| _ -> None )
|> Seq.choose id