0

I'm trying to write my own List.partition function for F# practice. Here's my first (naive) attempt:

let rec mypartition_naive func list =
    match list with
    | [] -> ([],[])
    | head::tail ->
        let (h1,h2) = mypartition_naive func tail
        if func head
        then (head::h1,h2)
        else (h1,head::h2)

This works, but it's not tail-recursive. I put together a second attempt that uses an accumulator to become tail-recursive:

let mypartition_accumulator func list =
    let rec helper acc listinner =
        match listinner with
        | head::tail ->
            let a,b = acc
            let newacc = if func head then (head::a,b) else (a,head::b)
            helper newacc tail
        | _ -> acc
    helper ([],[]) list

Strictly speaking, this works: it partitions the list. The problem is that this reverses the order of the lists. I get this:

let mylist = [1;2;3;4;5;6;7;8]
let partitioned = mypartition_accumulator (fun x -> x % 2 = 0) mynums
//partitioned is now ([8; 6; 4; 2], [7; 5; 3; 1])
//I want partitioned to be ([2; 4; 6; 8], [1; 3; 5; 7])

I think that I can use continuation passing to write a tail-recursive partition function that doesn't reverse the list elements, but I don't really understand continuation passing (and I've read a lot about it). How can I write partition using tail-recursive and keeping the list elements in order?

4

2 回答 2

2

这是 CPS 版本,但List.rev要走的路(请参阅此相关答案)。

let partition f list =
  let rec aux k = function
    | h::t -> aux (fun (a, b) -> 
        k (if f h then h::a, b else a, h::b)) t
    | [] -> k ([], [])
  aux id list
于 2014-12-10T18:15:06.247 回答
1

虽然已经回答,但这个问题值得尝试解释。基于累加器的尾递归版本基本上是fold从左到右的,因此需要反转。

let fold folder state list : 'State =
    let rec aux state = function
    | [] -> state
    | h:'T::t -> aux (folder state h) t
    aux state list
// val fold : folder:('State -> 'T -> 'State) -> state:'State -> list:'T list -> 'State

let partitionFold p = 
    fold (fun (a, b) h -> if p h then h::a, b else a, h::b) ([], [])
    >> fun (a, b) -> List.rev a, List.rev b

partitionFold (fun x -> x % 2 = 0) [0..10] 
// val it : int list * int list = ([0; 2; 4; 6; 8; 10], [1; 3; 5; 7; 9])

的签名和功能fold现在List.fold与标准库中的完全一样。

相反,延续传递风格的版本等价于foldBack(cf. List.foldBack)。它从右到左(最后一个元素在前)递归迭代,从而立即获得所需的顺序。

let foldBack folder list state : 'State =
    let rec aux k = function
    | [] -> k state
    | h:'T::t -> aux (folder h >> k) t
    aux id list
// val foldBack :
//     folder:('T -> 'State -> 'State) -> list:'T list -> state:'State -> 'State

let partitionFoldBack p list = 
    foldBack (fun h (a, b) -> if p h then h::a, b else a, h::b) list ([], [])

partitionFoldBack (fun x -> x % 2 = 0) [0..10] 
// val it : int list * int list = ([0; 2; 4; 6; 8; 10], [1; 3; 5; 7; 9])
于 2014-12-12T22:15:16.020 回答