简体   繁体   中英

F# Splitting a list

I am new to F# & tuples and I am trying to split a list into three lists of tuples using recursion and matching.

For example, a list of [1; 2; 3] would return:

l1 = [1] 
l2 = [2]
l3 = [3]

or

[1;2;3;4;5;6;7]:

l1 = [1;2;3]
l2 = [4; 5]
l3 = [6; 7]

So far my code starts out as

let rec split x =
    match x with
    | _ -> [], [], []

I'm not sure where to start when inserting elements into each list.

The most basic approach would be to walk over the list, process the rest of it recursively and then append the current element to one of the three returned lists. You will need to add an extra parameters i to the function to keep track of how far in the list you are (and then use this to determine where should the current elemnt go). The general structure in the most basic form is:

let split l =
  let length = List.length l
  let rec loop i l = 
    match l with 
    | [] -> 
        // Empty list just becomes a triple of empty lists
        [], [], []
    | x::xs ->
        // Process the rest of the list recursively. This 
        // gives us three lists containing the values from 'xs'
        let l1, l2, l3 = loop (i + 1) xs
        // Now comes the tricky bit. Here you need to figure out
        // whether 'x' should go into 'l1', 'l2' or 'l3'. 
        // Then you can append it to one of them using something like:
        l1, x::l2, l3
  // Walk over the list, starting with index 'i=0'
  loop 0 l

What to do about the tricky bit? I do not have a solution that works exactly as you wanted, but the following is close - it simply looks whether i is greater than 1/3 of the length or 2/3 of the length:

let split l =
  let length = List.length l
  let rec loop i l = 
    match l with 
    | [] -> [], [], []
    | x::xs ->
        let l1, l2, l3 = loop (i + 1) xs
        if i >= length / 3 * 2 then l1, l2, x::l3
        elif i >= length / 3 then l1, x::l2, l3
        else x::l1, l2, l3
  loop 0 l

This will always create groups of length / 3 and put remaining elements in the last list:

split [1..3] // [1], [2], [3]
split [1..4] // [1], [2], [3; 4]
split [1..5] // [1], [2], [3; 4; 5]
split [1..6] // [1; 2], [3; 4], [5; 6] 

You should be able to adapt this to the behaviour you need - there is some fiddly calculation that you need to do to figure out exactly where the cut-off points are, but that's a matter of getting the +/-1s right!

There is a function for that in the List module. You can test it easily in F# interactive (fsi).

let input = [1;2;3];;
let output = List.splitInto 3 input;;

output;;
val it: int list list = [[1]; [2]; [3]]

So it returns a list of lists.

If you want to do it by hand, you can still use other list functions (which might be good exercise in itself):

let manualSplitInto count list =
    let l = List.length list
    let n = l / count
    let r = l % count
    List.append
        [(List.take (n+r) list)]
        (List.unfold (fun rest ->
                      match rest with
                      | [] -> None
                      | _ ->  let taken = min n (List.length rest)
                              Some (List.take taken rest, List.skip taken rest))
                     (List.skip (n+r) list))

Here, List.unfold does the iteration (recursing) part for you. So, if you really want to train working with recursive functions, you will end up writing your own List.unfold replacement or something more tailored to your concrete use case.

let pedestrianSplitInto count list =
    let l = List.length list
    let n = l / count
    let r = l % count
    let rec step rest acc =
        match rest with
        | [] -> acc
        | _ ->
            let taken = min n (List.length rest)
            step (List.skip taken rest) ((List.take taken rest) :: acc)
    List.rev (step (List.skip (n+r) list) [List.take (n+r) list])

Please observe how similar the implementation of function step is to the lambda given to List.unfold in manualSplitInto .

If you also do not want to use functions like List.take or List.skip , you will have to go even lower level and do element wise operations, such as:

let rec splitAtIndex index front rear =
    match index with
    | 0 -> (List.rev front, rear)
    | _ -> splitAtIndex (index - 1) ((List.head rear) :: front) (List.tail rear)
    
let stillLivingOnTreesSplitInto count list =
    let l = List.length list
    let n = l / count
    let r = l % count
    let rec collect result (front,rear) =
        match rear with
        | [] -> (front :: result)
        | _ -> collect (front :: result) (splitAtIndex n [] rear)
    let x = splitAtIndex (n+r) [] list
    collect [] x |> List.rev

If you know it will always be triplets then this should work.

let xs = [1..7]
let n = List.length xs
let y = List.mapi (fun i x -> (x, 3 * i / n)) xs
List.foldBack (fun (x, i) (a,b,c) -> match i with 0 -> (x::a,b,c) | 1 -> (a,x::b,c) | 2 -> (a,b,x::c)) y (([],[],[]))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM