Most idiomatic way to write batchesOf size seq in F#

前端 未结 10 503
迷失自我
迷失自我 2020-12-16 18:42

I\'m trying to learn F# by rewriting some C# algorithms I have into idiomatic F#.

One of the first functions I\'m trying to rewrite is a batchesOf where:

         


        
相关标签:
10条回答
  • 2020-12-16 18:48

    My method involves converting the list to an array and recursively chunking the array:

        let batchesOf (sz:int) lt = 
            let arr = List.toArray lt
    
            let rec bite curr =
                if (curr + sz - 1 ) >= arr.Length then
                    [Array.toList arr.[ curr .. (arr.Length - 1)]]
                else
                    let curr1 = curr + sz 
                    (Array.toList (arr.[curr .. (curr + sz - 1)])) :: (bite curr1)   
    
            bite 0
    
        batchesOf 5 [1 .. 17]
    
        [[1; 2; 3; 4; 5]; [6; 7; 8; 9; 10]; [11; 12; 13; 14; 15]; [16; 17]]
    
    0 讨论(0)
  • 2020-12-16 18:49

    Hurray, we can use List.chunkBySize, Seq.chunkBySize and Array.chunkBySize in F# 4, as mentioned by Brad Collins and Scott Wlaschin.

    0 讨论(0)
  • 2020-12-16 18:52

    Implementing this function using the seq<_> type idiomatically is difficult - the type is inherently mutable, so there is no simple nice functional way. Your version is quite inefficient, because it uses Skip repeatedly on the sequence. A better imperative option would be to use GetEnumerator and just iterate over elements using IEnumerator. You can find various imperative options in this snippet: http://fssnip.net/1o

    If you're learning F#, then it is better to try writing the function using F# list type. This way, you can use idiomatic functional style. Then you can write batchesOf using pattern matching with recursion and accumulator argument like this:

    let batchesOf size input = 
      // Inner function that does the actual work.
      // 'input' is the remaining part of the list, 'num' is the number of elements
      // in a current batch, which is stored in 'batch'. Finally, 'acc' is a list of
      // batches (in a reverse order)
      let rec loop input num batch acc =
        match input with
        | [] -> 
            // We've reached the end - add current batch to the list of all
            // batches if it is not empty and return batch (in the right order)
            if batch <> [] then (List.rev batch)::acc else acc
            |> List.rev
        | x::xs when num = size - 1 ->
            // We've reached the end of the batch - add the last element
            // and add batch to the list of batches.
            loop xs 0 [] ((List.rev (x::batch))::acc)
        | x::xs ->
            // Take one element from the input and add it to the current batch
            loop xs (num + 1) (x::batch) acc
      loop input 0 [] []
    

    As a footnote, the imperative version can be made a bit nicer using computation expression for working with IEnumerator, but that's not standard and it is quite advanced trick (for example, see http://fssnip.net/37).

    0 讨论(0)
  • 2020-12-16 18:53

    This version passes all my tests I could think of including ones for lazy evaluation and single sequence evaluation:

    let batchIn batchLength sequence =
        let padding = seq { for i in 1 .. batchLength -> None } 
        let wrapped = sequence |> Seq.map Some
        Seq.concat [wrapped; padding]
        |> Seq.windowed batchLength 
        |> Seq.mapi (fun i el -> (i, el)) 
        |> Seq.filter (fun t -> fst t % batchLength = 0) 
        |> Seq.map snd
        |> Seq.map (Seq.choose id)
        |> Seq.filter (fun el -> not (Seq.isEmpty el))
    

    I am still quite new to F# so if I'm missing anything - please do correct me, it will be greatly appreciated.

    0 讨论(0)
  • 2020-12-16 18:54

    A friend asked me this a while back. Here's a recycled answer. This works and is pure:

    let batchesOf n =
        Seq.mapi (fun i v -> i / n, v) >>
        Seq.groupBy fst >>
        Seq.map snd >>
        Seq.map (Seq.map snd)
    

    Or an impure version:

    let batchesOf n =
        let i = ref -1
        Seq.groupBy (fun _ -> i := !i + 1; !i / n) >> Seq.map snd
    

    These produce a seq<seq<'a>>. If you really must have an 'a list list as in your sample then just add ... |> Seq.map (List.ofSeq) |> List.ofSeq as in:

    > [1..17] |> batchesOf 5 |> Seq.map (List.ofSeq) |> List.ofSeq;;
    val it : int list list = [[1; 2; 3; 4; 5]; [6; 7; 8; 9; 10]; [11; 12; 13; 14; 15]; [16; 17]]
    

    Hope that helps!

    0 讨论(0)
  • 2020-12-16 18:54

    This isn't perhaps idiomatic but it works:

    let batchesOf n l = 
        let _, _, temp', res' = List.fold (fun (i, n, temp, res) hd ->
                                               if i < n then
                                                 (i + 1, n, hd :: temp, res)
                                               else
                                                 (1, i, [hd], (List.rev temp) :: res)) 
                                           (0, n, [], []) l
        (List.rev temp') :: res' |> List.rev
    
    0 讨论(0)
提交回复
热议问题