这是我一直在努力解决的问题。我需要将两个排序的序列合并为一个排序的序列。理想情况下,算法应该是惰性求值的,并且不需要从每个序列中缓存多个项目。这不是一个非常难以解决的问题,我已经能够在F#中设计出许多解决方案。不幸的是,我提出的每个解决方案都存在一些问题。
使用yield!对子序列生成器的递归调用。这会产生优雅的解决方案,但为每个项目创建一个子序列是性能杀手。
真正神秘且无法维护的代码,包含深度堆叠的匹配开关,多个几乎完全相同的代码块等。
强制F#进入纯粹程序模式的代码(许多可变值等)。
我在相同的浅滩上找到了创始人的所有在线示例。
我错过了一些明显的东西:比如它真的很简单还是显然不可能?有谁知道一个非常优雅的解决方案,也是高效的,大部分功能? (它不一定是纯粹的功能。)如果没有,我可能最终缓存子序列并使用列表或数组。
答案 0 :(得分:12)
理想情况下,算法应该是懒惰评估...每个项目的子序列的创建都是性能杀手
懒惰意味着很慢,但这是一个使用懒惰列表的解决方案:
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)
答案 1 :(得分:7)
在PowerPack中使用LazyList类型。我想我甚至可能有这个确切的代码,让我看看......
修改
答案 2 :(得分:7)
序列并不能很好地匹配模式。
幸运的是,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 -> ()
}