F# split sequence into sub lists on every nth element

后端 未结 8 889
时光说笑
时光说笑 2020-12-06 11:23

Say I have a sequence of 100 elements. Every 10th element I want a new list of the previous 10 elements. In this case I will end up with a list of 10 sublists.

Seq.t

8条回答
  •  渐次进展
    2020-12-06 11:47

    I have an evolution of three solutions. None of them preserves the ordering of the input elements, which is hopefully OK.

    My first solution is quite ugly (making use of ref cells):

    //[[4; 3; 2; 1; 0]; [9; 8; 7; 6; 5]; [14; 13; 12; 11; 10]; [17; 16; 15]]
    let solution1 =
        let split s n =
            let i = ref 0
            let lst = ref []
            seq {
                for item in s do
                    if !i = n then
                        yield !lst
                        lst := [item]
                        i := 1
                    else
                        lst := item::(!lst)
                        i := !i+1
                yield !lst
            } |> Seq.toList
        split {0..17} 5
    

    My second solution factors out the use of ref cells in the first solution, but consequently forces the use of direct IEnumerator access (push in one side, pop out the other)!

    //[[17; 16; 15]; [14; 13; 12; 11; 10]; [9; 8; 7; 6; 5]; [4; 3; 2; 1; 0]]
    let solution2 =
        let split (s:seq<_>) n =
            let e = s.GetEnumerator()
            let rec each lstlst lst i =
                if e.MoveNext() |> not then
                    lst::lstlst
                elif i = n then
                    each (lst::lstlst) [e.Current] 1
                else 
                    each lstlst ((e.Current)::lst) (i+1)
            each [] [] 0
        split {0..17} 5
    

    My third solution is based on the second solution except it "cheats" by taking a list as input instead of a seq, which enables the most elegant solution using pattern matching as Tomas points out is lacking with seq (which is why we were forced to use direct IEnumerator access).

    //[[17; 16; 15]; [14; 13; 12; 11; 10]; [9; 8; 7; 6; 5]; [4; 3; 2; 1; 0]]
    let solution3 =
        let split inputList n =
            let rec each inputList lstlst lst i =
                match inputList with
                | [] -> (lst::lstlst)
                | cur::inputList ->
                    if i = n then
                        each inputList (lst::lstlst) [cur] 1    
                    else
                        each inputList lstlst (cur::lst) (i+1)
            each inputList [] [] 0
        split [0..17] 5
    

    If preserving the ordering of the elements is important, you can use List.rev for this purpose. For example, in solution2, change the last line of the split function to:

    each [] [] 0 |> List.rev |> List.map List.rev
    

提交回复
热议问题