0

I'm trying to split a list with ints into sublists with ascending values.

as an example: [1;2;3;3;4] should go into [[1;2;3;3;4]] and a list [1;2;3;2;4;3;5] -> [[1;2;3],[2;4],[3;5]].

I was trying to do it through tail recursion but I can't figure out how to do it.

kaefer
  • 5,491
  • 1
  • 15
  • 20

2 Answers2

0

You can use 2 accumulators.

let  splitToSorted lst =
let rec loop lst sub acc = 
    match lst with
    | [] -> (sub |> List.rev)::acc |> List.rev
    | h::t -> match sub with
              | [] -> loop t [h] acc
              | x::xs -> if (x < h)
                         then loop t (h::sub) acc
                         else loop t [x] ((sub |> List.rev)::acc)
loop lst [] []
gileCAD
  • 2,295
  • 1
  • 10
  • 10
0

If you would show what you have tried, then we would be able to help you over that specific point you were stuck at.

Also, try to generalize your problem in order to find possible approaches that have been already offered, for example "I have a partially sorted list and want to split it by predicate, comparing two adjacent values". That may have led you to answers in existence, e.g. F# how to Window a sequence based on predicate rather than fixed length

If these do not fit your requirements, have a go at this one. It's a nested recursive function with accumulator and argument combined into a tuple, where the accumulator and its sub-lists need reversing after having used up all arguments.

let splitWhen p xs =
    let rec aux = function
    | ((y::_)::_) as yss, x::xs when p x y -> aux ([x]::yss, xs)
    | ys::yss, x::xs -> aux ((x::ys)::yss, xs)
    | [], x::xs -> aux ([[x]], xs)
    | yss, [] -> List.rev <| List.map List.rev yss
    aux ([], xs)

splitWhen (<) [1;2;3;3;4]
// val it : int list list = [[1; 2; 3; 3; 4]]
splitWhen (<) [1;2;3;2;4;3;5]
// val it : int list list = [[1; 2; 3]; [2; 4]; [3; 5]]

Edit

Okay, let's take a step back. The recursive function should be tail recursive, that is, you should not have unfinished business when calling the function recursively; it may overflow the stack. That in turn requires an additional argument to accumulate the intermediate values you have computed so far, which is conventionally called the accumulator.

At the same time, the accumulator is a convenient place to find the previous value in, to compare it with the current one. Except when the accumulator is empty -- that is why we need to match both against the accumulator and the arguments, to handle the special cases when one of them is empty.

It is easily seen how the predicate test can be factored out which will produce the version above.

let rec splitList<'T when 'T: comparison> (acc : 'T list list) (list: 'T list) : 'T list list =
    match acc, list with
    | (y::_) as ys::yss, x::xs -> 
        if x >= y then splitList ((x::ys)::yss) xs
        else splitList ([x]::acc) xs
    | _, x::xs -> splitList [[x]] xs
    | _ -> List.rev (List.map List.rev acc)

splitList [] [1;2;3;3;4]
// val it : int list list = [[1; 2; 3; 3; 4]]
splitList [] [1;2;3;2;4;3;5]
// val it : int list list = [[1; 2; 3]; [2; 4]; [3; 5]]

Edit2

You can also use no accumulator, but a list comprehension (aka list sequence expression) instead. This is kind of cheating, since it needs a helper function which does use an accumulator; it will produce the signature val splitList : list:'a list -> 'a list list when 'a : comparison.

let rec partitionWhile p = function
| x::xs, y::ys when p x y -> partitionWhile p (y::x::xs, ys)
| xss, yss -> List.rev xss, yss

let rec splitList list = [
    if not <| List.isEmpty list then
        let acc, rest = partitionWhile (<=) ([list.Head], list.Tail)
        if not <| List.isEmpty acc then
            yield acc
        yield! splitList rest ]

splitList [1;2;3;3;4]
// val it : int list list = [[1; 2; 3; 3; 4]]
splitList [1;2;3;2;4;3;5]
// val it : int list list = [[1; 2; 3]; [2; 4]; [3; 5]]
kaefer
  • 5,491
  • 1
  • 15
  • 20