Split list into two equal lists in F#

后端 未结 7 418
遇见更好的自我
遇见更好的自我 2021-01-12 18:21

I\'m really new to F#, and I need a bit of help with an F# problem.

I need to implement a cut function that splits a list in half so that the output would be...

相关标签:
7条回答
  • 2021-01-12 19:01

    Since your list has an even length, and you're cutting it cleanly in half, I recommend the following (psuedocode first):

    • Start with two pointers: slow and fast.
    • slow steps through the list one element at a time, fast steps two elements at a time.
    • slow adds each element to an accumulator variable, while fast moves foward.
    • When the fast pointer reaches the end of the list, the slow pointer will have only stepped half the number of elements, so its in the middle of the array.
    • Return the elements slow stepped over + the elements remaining. This should be two lists cut neatly in half.

    The process above requires one traversal over the list and runs in O(n) time.

    Since this is homework, I won't give a complete answer, but just to get you partway started, here's what it takes to cut the list cleanly in half:

    let cut l =
        let rec cut = function
            | xs, ([] | [_]) -> xs
            | [], _ -> []
            | x::xs, y::y'::ys -> cut (xs, ys)
        cut (l, l)
    

    Note x::xs steps 1 element, y::y'::ys steps two.

    This function returns the second half of the list. It is very easy to modify it so it returns the first half of the list as well.

    0 讨论(0)
  • 2021-01-12 19:01

    check this one out:

    let gencut s xs =  
        ([for i in 0 .. s - 1 -> List.nth xs i], [for i in s .. (List.length xs) - 1 -> List.nth xs i])
    

    the you just call

    let cut xs =                            
        gencut ((List.length xs) / 2) xs
    

    with n durationn only one iteration split in two

    0 讨论(0)
  • 2021-01-12 19:02

    Here's yet another way to do it using inbuilt library functions, which may or may not be easier to understand than some of the other answers. This solution also only requires one traversal across the input. My first thought after I looked at your problem was that you want something along the lines of List.partition, which splits a list into two lists based on a given predicate. However, in your case this predicate would be based on the index of the current element, which partition cannot handle, short of looking up the index for each element.

    We can accomplish creating our own equivalent of this behavior using a fold or foldBack. I will use foldBack here as it means you won't have to reverse the lists afterward (see Stephens excellent answer). What we are going to do here is use the fold to provide our own index, along with the two output lists, all as the accumulator. Here is the generic function that will split your list into two lists based on n index:

    let gencut n input =
        //calculate the length of the list first so we can work out the index
        let inputLength = input |> List.length 
        let results =
            List.foldBack( fun elem acc-> 
                                let a,b,index = acc     //decompose accumulator
                                if (inputLength - index) <= n then (elem::a,b,index+1)
                                else (a,elem::b,index+1)  ) input ([],[],0)
        let a,b,c = results
        (a,b) //dump the index, leaving the two lists as output.
    

    So here you see we start the foldBack with an initial accumulator value of ([],[],0). However, because we are starting at the end of the list, the 0 representing the current index needs to be subtracted from the total length of the list to get the actual index of the current element.

    Then we simply check if the current index falls within the range of n. If it does, we update the accumulator by adding the current element to list a, leave list b alone, and increase the index by 1 : (elem::a,b,index+1). In all other cases, we do exactly the same but add the element to list b instead: (a,elem::b,index+1).

    Now you can easily create your function that splits a list in half by creating another function over this one like so:

    let cut input = 
        let half = (input |> List.length) / 2
        input |> gencut half
    

    I hope that can help you somewhat!

    > cut data;;
    val it : int list * int list = ([1; 2; 3], [4; 5; 6])
    
    > gencut 5 data;;
    val it : int list * int list = ([1; 2; 3; 4; 5], [6])
    

    EDIT: you could avoid the index negation by supplying the length as the initial accumulator value and negating it on each cycle instead of increasing it - probably simpler that way :)

    let gencut n input =
        let results =
            List.foldBack( fun elem acc-> 
                                let a,b,index = acc     //decompose accumulator
                                if index <= n then (elem::a,b,index-1)
                                else (a,elem::b,index-1)  ) input ([],[],List.length input)
        let a,b,c = results
        (a,b) //dump the index, leaving the two lists as output.
    
    0 讨论(0)
  • 2021-01-12 19:05

    (I didn't like my previous answer so I deleted it)

    The first place to start when attacking list problems is to look at the List module which is filled with higher order functions which generalize many common problems and can give you succinct solutions. If you can't find anything suitable there, then you can look at the Seq module for solutions like @BrokenGlass demonstrated (but you can run into performance issues there). Next you'll want to consider recursion and pattern matching. There are two kinds of recursion you'll have to consider when processing lists: tail and non-tail. There are trade-offs. Tail-recursive solutions involve using an accumulator to pass state around, allowing you to place the recursive call in the tail position and avoid stack-overflows with large lists. But then you'll typically end up with a reversed list! For example,

    Tail-recursive gencut solution:

    let gencutTailRecursive n input =
        let rec gencut cur acc = function
            | hd::tl when cur < n ->
                gencut (cur+1) (hd::acc) tl
            | rest -> (List.rev acc), rest //need to reverse accumulator!
        gencut 0 [] input
    

    Non-tail-recursive gencut solution:

    let gencutNonTailRecursive n input =
        let rec gencut cur = function
            | hd::tl when cur < n ->
                let x, y = gencut (cur+1) tl //stackoverflow with big lists!
                hd::x, y
            | rest -> [], rest
        gencut 0 input
    

    Once you have your gencut solution, it's really easy to define cut:

    let cut input = gencut ((List.length input)/2) input
    
    0 讨论(0)
  • 2021-01-12 19:14

    I have the same Homework, this was my solution. I'm just a student and new in F#

    let rec gencut(n, listb) = 
        let rec cut  n (lista : int list)  (listb : int list) =
            match (n , listb ) with
            | 0, _   ->  lista, listb
            | _, []  -> lista, listb
            | _, b :: listb -> cut  (n - 1) (List.rev (b :: lista ))  listb
        cut n [] listb
    
    let cut xs = gencut((List.length xs) / 2, xs)  
    

    Probably is not the best recursive solution, but it works. I think

    0 讨论(0)
  • 2021-01-12 19:15

    You are looking for list slicing in F#. There was a great answer by @Juliet in this SO Thread: Slice like functionality from a List in F#

    Basically it comes down to - this is not built in since there is no constant time index access in F# lists, but you can work around this as detailed. Her approach applied to your problem would yield a (not so efficient but working) solution:

    let gencut(n, list) = 
        let firstList = list |> Seq.take n |> Seq.toList
        let secondList = list |> Seq.skip n |> Seq.toList
        (firstList, secondList)
    
    0 讨论(0)
提交回复
热议问题