链接列表分区功能和反转结果

时间:2011-08-26 01:25:14

标签: algorithm list f#

我写了这个F#函数来将列表分区到某一点而不是更远 - 就像takeWhilepartition之间的交叉。

let partitionWhile c l =
    let rec aux accl accr =
        match accr with
        | [] -> (accl, [])
        | h::t ->
            if c h then
                aux (h::accl) t
            else
                (accl, accr)
    aux [] l

唯一的问题是“被采取”的项目是相反的:

> partitionWhile ((>=) 5) [1..10];;
val it : int list * int list = ([5; 4; 3; 2; 1], [6; 7; 8; 9; 10])

除了诉诸rev之外,是否有一种方法可以编写这个函数,使第一个列表的顺序正确?

5 个答案:

答案 0 :(得分:10)

这是一个基于延续的版本。它是尾递归的,并按原始顺序返回列表。

let partitionWhileCps c l =
  let rec aux f = function
    | h::t when c h -> aux (fun (acc, l) -> f ((h::acc), l)) t
    | l -> f ([], l)
  aux id l

以下是一些基准测试,以便在Brian的回答(以及累加器版本供参考)之后进行讨论:

let partitionWhileAcc c l =
  let rec aux acc = function
    | h::t when c h -> aux (h::acc) t
    | l -> (List.rev acc, l)
  aux [] l

let test = 
  let l = List.init 10000000 id
  (fun f ->
    let r = f ((>) 9999999) l
    printfn "%A" r)

test partitionWhileCps // Real: 00:00:06.912, CPU: 00:00:07.347, GC gen0: 78, gen1: 65, gen2: 1
test partitionWhileAcc // Real: 00:00:03.755, CPU: 00:00:03.790, GC gen0: 52, gen1: 50, gen2: 1

Cps平均约为7s,Acc ~4s。简而言之,延续不需要为此练习购买任何东西。

答案 1 :(得分:6)

我希望您可以使用延续,但最后调用List.rev是最好的方法。

答案 2 :(得分:1)

我通常更喜欢List而不是List,因为它们很懒,你有List.toSeqSeq.toList函数在它们之间进行转换。以下是使用序列的partitionWhile函数的实现。

let partitionWhile (c:'a -> bool) (l:'a list) = 
    let fromEnum (e:'a IEnumerator) = 
        seq { while e.MoveNext() do yield e.Current}
    use e = (l |> List.toSeq).GetEnumerator()
    (e |> fromEnum |> Seq.takeWhile c |> Seq.toList)
    ,(e |> fromEnum |> Seq.toList)

答案 3 :(得分:0)

您可以像这样重写函数:

let partitionWhile c l =
  let rec aux xs =
    match xs with
      | [] -> ([], [])
      | h :: t ->
          if c h then
            let (good, bad) = aux t in
              (h :: good, bad)
          else
            ([], h :: t)
  aux l

是的,正如Brian已经注意到它不再是尾递归,但它回答了所述的问题。顺便说一下,Haskell中的span在Hugs中的实现方式完全相同:

span p []       = ([],[])
span p xs@(x:xs')
    | p x       = (x:ys, zs)
    | otherwise = ([],xs)
    where (ys,zs) = span p xs'

在Haskell中更喜欢这个版本的一个很好的理由是懒惰:在第一个版本中,在列表反转之前访问所有好的元素。在第二个版本中,可以立即返回第一个好元素。

答案 4 :(得分:0)

我不认为我是唯一一个从丹尼尔的CPS解决方案中学到很多东西(挣扎)的人。在试图找出它时,它帮助我改变了几个潜在的(对初学者)模棱两可的列表引用,如下所示:

    let partitionWhileCps cond l1 =

        let rec aux f l2 = 
            match l2 with
            | h::t when cond h ->   aux  (fun (acc, l3) -> f (h::acc, l3))  t
            | l4 -> f ([], l4)

        aux id l1

(注意" []"在l4匹配中是初始的acc值。)我喜欢这个解决方案,因为它不需要使用List.rev感觉更少的kludgey,钻到最后第一个列表并向后构建第二个列表。我认为避免.rev的另一个主要方法是使用带有cons操作的尾递归。一些语言优化"尾部递归模式缺点"以与正确的尾递归相同的方式(但Don Syme已经表示这不会来到F#)。

所以这在F#中不是尾递归安全的,但是它使我的答案成为答案并避免使用List.rev(这是必须访问两个元组元素并且与cps方法更加平行)我想,就像我们只返回第一个列表一样):

    let partitionWhileTrmc cond l1 = 

        let rec aux acc l2 =  
            match l2 with 
            | h::t when cond h ->  ( h::fst(aux acc t), snd(aux acc t)) 
            | l3 -> (acc, l3)

        aux [] l1