如何合并排序的序列?

时间:2010-08-14 17:10:23

标签: f#

这是我一直在努力解决的问题。我需要将两个排序的序列合并为一个排序的序列。理想情况下,算法应该是惰性求值的,并且不需要从每个序列中缓存多个项目。这不是一个非常难以解决的问题,我已经能够在F#中设计出许多解决方案。不幸的是,我提出的每个解决方案都存在一些问题。

  1. 使用yield!对子序列生成器的递归调用。这会产生优雅的解决方案,但为每个项目创建一个子序列是性能杀手。

  2. 真正神秘且无法维护的代码,包含深度堆叠的匹配开关,多个几乎完全相同的代码块等。

  3. 强制F#进入纯粹程序模式的代码(许多可变值等)。

  4. 我在相同的浅滩上找到了创始人的所有在线示例。

    我错过了一些明显的东西:比如它真的很简单还是显然不可能?有谁知道一个非常优雅的解决方案,也是高效的,大部分功能? (它不一定是纯粹的功能。)如果没有,我可能最终缓存子序列并使用列表或数组。

3 个答案:

答案 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类型。我想我甚至可能有这个确切的代码,让我看看......

修改

不完全是,但关闭:http://cs.hubfs.net/forums/thread/8136.aspx

答案 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 -> ()
    }