Ответ 1
Используйте тип LazyList в PowerPack. Я думаю, что, возможно, у меня даже есть этот точный код, дайте мне посмотреть...
ИЗМЕНИТЬ
не совсем это, но close: http://cs.hubfs.net/forums/thread/8136.aspx
Вот проблема, с которой я действительно боролся. Мне нужно объединить две отсортированные последовательности в одну отсортированную последовательность. В идеале алгоритм должен быть ленивым и не требует кэширования более чем одного элемента из каждой последовательности. Это не очень трудная проблема для решения, и я смог разработать ряд решений в F #. К сожалению, у каждого решения Ive есть одна из нескольких проблем.
Рекурсивные вызовы генераторам подпоследовательностей с использованием yield!. Это создает элегантно выглядящие решения, но создание подпоследовательности для каждого элемента является убийцей производительности.
Действительно тайный и незаметный код с глубоко уложенными совпадающими переключателями, несколькими почти идентичными блоками кода и т.д.
Код, который заставляет F # в чисто процедурный режим (много изменяемых значений и т.д.).
И все онлайн-примеры я смог найти основателя на тех же косяках.
Я пропустил что-то очевидное: как это действительно просто или, очевидно, невозможно? Кто-нибудь знает о действительно элегантном решении, которое также эффективно и в основном функционально? (Это не должно быть чисто функциональным.) Если нет, я могу закончить кеширование подпоследовательностей и использование списков или массивов.
Используйте тип LazyList в PowerPack. Я думаю, что, возможно, у меня даже есть этот точный код, дайте мне посмотреть...
ИЗМЕНИТЬ
не совсем это, но close: http://cs.hubfs.net/forums/thread/8136.aspx
В идеале алгоритм должен лениться... создание подпоследовательности для каждого элемента - убийца производительности
Lazy означает медленный, но вот решение, использующее ленивые списки:
let (++) = LazyList.consDelayed
let rec merge xs ys () =
match xs, ys with
| Cons(x, xs'), Cons(y, _) when x<y -> x ++ merge xs' ys
| Cons(x, _), Cons(y, ys') -> y ++ merge xs ys'
| Nil, xs | xs, Nil -> xs
Я думаю, что "ленивая оценка" означает, что вы хотите, чтобы полученный результат был сгенерирован по запросу, поэтому вы также можете использовать:
let rec merge xs ys = seq {
match xs, ys with
| x::xs, y::_ when x<y ->
yield x
yield! merge xs ys
| x::_, y::ys ->
yield y
yield! merge xs ys
| [], xs | xs, [] -> yield! xs
}
Как вы говорите, это очень неэффективно. Однако решение на основе seq
не должно быть медленным. Здесь Seq.unfold
является вашим другом и может сделать это более чем на 4 раза быстрее по моим измерениям:
let merge xs ys =
let rec gen = function
| x::xs, (y::_ as ys) when x<y -> Some(x, (xs, ys))
| xs, y::ys -> Some(y, (xs, ys))
| [], x::xs | x::xs, [] -> Some(x, ([], xs))
| [], [] | [], [] -> None
Seq.unfold gen (xs, ys)
Последовательности на самом деле не соответствуют хорошему.
К счастью, одно из преимуществ F # заключается в том, что вы можете отказаться от императивного кода, когда вам это нужно, и я думаю, что все же идиоматично использовать изменяемое состояние внутри, пока функция по-прежнему чиста для клиентов, потребляющих эту функцию. Я думаю, что этот стиль действительно распространен в исходном коде F # везде, где задействованы последовательности.
Его не очень, но это работает:
open System.Collections.Generic
let merge (a : #seq<'a>) (b : #seq<'a>) =
seq {
use a = a.GetEnumerator()
use b = b.GetEnumerator()
let aNext = ref <| a.MoveNext()
let bNext = ref <| b.MoveNext()
let inc (enumerator : IEnumerator<'a>) flag = // '
let temp = enumerator.Current
flag := enumerator.MoveNext()
temp
let incA() = inc a aNext
let incB() = inc b bNext
while !aNext || !bNext do
match !aNext, !bNext with
| true, true ->
if a.Current > b.Current then yield incB()
elif a.Current < b.Current then yield incA()
else yield incA(); yield incB()
| true, false -> yield incA()
| false, true -> yield incB()
| false, false -> ()
}