У OP уже есть принятый ответ, но я подумал, что предлагаю несколько вариантов.
Задача запрашивает работающий агрегат (набор) для входных значений, в то же время позволяя досрочно выйти, когда набор имеетзаявить, что мы не можем добавить к нему число, потому что мы его уже видели.
Обычно мы fold
собираем состояние, но fold
не позволяет нам выйти рано.Вот почему было предложено использовать scan
, который является потоковым fold
+ pick
, который позволяет досрочно завершить работу.
Альтернативой является кодирование fold
, которое позволяет создавать ярлыки, когда состояниедостигнуто: val foldAndCheck: (a' -> 'b -> CheckResult<'a, 'c>) -> 'a -> 'b seq -> 'c option
.fold
похож на цикл for, который агрегирует все значения, foldAndCheck
похож на цикл for, который агрегирует значения до некоторой точки и затем возвращает результат.
Тогда он может выглядеть примерно так:
type [<Struct>] CheckResult<'T, 'U> =
| Continue of c:'T
| Done of d:'U
// val foldAndCheck: (a' -> 'b -> CheckResult<'a, 'c>) -> 'a -> 'b seq -> 'c option
let foldAndCheck f z (s : _ seq) =
let f = OptimizedClosures.FSharpFunc<_, _, _>.Adapt f
use e = s.GetEnumerator ()
let rec loop s =
if e.MoveNext () then
match f.Invoke (s, e.Current) with
| Continue ss -> loop ss
| Done rr -> Some rr
else
None
loop z
let cycle xs = seq { while true do yield! xs }
let run (input : string) =
let folder s v = if Set.contains v s then Done v else Continue (Set.add v s)
input.Trim().Split('\n')
|> Seq.map int
|> cycle
|> Seq.scan (+) 0
|> foldAndCheck folder Set.empty
При запуске на моем компьютере я получаю такие числа:
Result: Some 448
Took : 280 ms
CC : (31, 2, 1)
(CC - сборщик мусора в поколениях 0, 1 и 2)
Затем я создалпрограмма на F #, которая, на мой взгляд, эквивалентна программе на Python, так как использует изменяемый набор и функцию mapWhile:
let addAndReturn (set : HashSet<_>) =
fun v ->
set.Add v |> ignore
v
let mapWhile func pred (s : _ seq) =
seq {
// F# for v in s ->
// doesn't support short-cutting. So therefore the use while
use e = s.GetEnumerator ()
let mutable cont = true
while cont && e.MoveNext () do
let v = e.Current
if not (pred v) then
cont <- false
yield func v
else
yield func v
}
let cycle xs = seq { while true do yield! xs }
let accumulate xs = Seq.scan (+) 0 xs
let last xs = Seq.last xs
let run (input : string) =
let data = input.Trim().Split('\n') |> Seq.map int
let s = HashSet<int> ()
data
|> cycle
|> accumulate
|> mapWhile (addAndReturn s) (fun x -> s.Contains x |> not)
|> last
Числа производительности:
Result: 448
Took : 50 ms
CC : (1, 1, 1)
Если мы говорим, что мы разрешаемmutation + seq решение может выглядеть следующим образом:
let cycle xs = seq { while true do yield! xs }
let run (input : string) =
let s = HashSet<int> ()
input.Trim().Split('\n')
|> Seq.map int
|> cycle
|> Seq.scan (+) 0
|> Seq.find (fun v -> s.Add v |> not)
, который работает следующим образом:
Result: 448
Took : 40 ms
CC : (1, 1, 1)
Существуют и другие приемы охлаждения, которые можно применить, чтобы еще больше повысить производительность поиска, ноэто не будет стоить усилий, так как большая часть затрат заключается в разборе целых чисел на этом этапе.