How can I Implement my own List.partition using tail-recursion?

240 Views Asked by At

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?

2

There are 2 best solutions below

3
On BEST ANSWER

Here's a CPS version, but List.rev is the way to go (see this related answer).

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
0
On

Although already answered, this question deserves an attempt at an explanation. The accumulator-based, tail-recursive version is basically fold, left-to-right and hence in need of reversal.

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])

The signature and functionality of fold is now exactly that of List.fold from the standard library.

In contrast, the version in continuation-passing style is equivalent to foldBack (cf. List.foldBack). It iterates recursively from right-to-left (last element first), and thus obtains the desired order right away.

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])