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?

user2023861
  • 8,030
  • 9
  • 57
  • 86

2 Answers2

2

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
Community
  • 1
  • 1
Daniel
  • 47,404
  • 11
  • 101
  • 179
  • Thanks. It's going to take me a while to figure out how this works. – user2023861 Dec 10 '14 at 19:23
  • @Danial, can you explain why you have the `xs` in your solution? I removed the `, xs` from the places it appears in your solution, and also the `|> fst` and the code still works the same. – user2023861 Dec 10 '14 at 20:47
  • 1
    @user2023861: That was a mistake. I copied/tweaked the code from the referenced answer and missed that. It's fixed. Good catch. – Daniel Dec 10 '14 at 21:08
1

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])
kaefer
  • 5,491
  • 1
  • 15
  • 20