最常用的方法是在F#中编写批量大小的seq

myt*_*thz 11 linq f# functional-programming sequence

我正在尝试通过将一些C#算法重写为惯用的F#来学习F#.

我正在尝试重写的第一个函数之一是batchesOf,其中:

[1..17] |> batchesOf 5
Run Code Online (Sandbox Code Playgroud)

哪个会将序列分成多个批次,每个最多五个,即:

[[1; 2; 3; 4; 5]; [6; 7; 8; 9; 10]; [11; 12; 13; 14; 15]; [16; 17]]
Run Code Online (Sandbox Code Playgroud)

我第一次尝试这样做有点丑陋,我在尝试在闭包内部使用可变类型时遇到错误而使用了一个可变的ref对象.使用ref特别不愉快,因为要取消引用它你必须使用!在一个条件表达式内部的操作符可以与某些开发人员相反直观,这些开发人员将其视为逻辑不符合.我遇到的另一个问题是Seq.skip和Seq.take与他们的Linq别名不同,如果大小超过序列的大小,他们将抛出错误.

let batchesOf size (sequence: _ seq) : _ list seq =
    seq {
        let s = ref sequence
        while not (!s |> Seq.isEmpty)  do
            yield !s |> Seq.truncate size |> List.ofSeq
            s := System.Linq.Enumerable.Skip(!s, size)
    }
Run Code Online (Sandbox Code Playgroud)

无论如何,在F#中重写这个最优雅/惯用的方法是什么?保持原始行为但最好没有ref可变变量.

Tom*_*cek 15

使用seq<_>idiomatically类型实现此函数很困难 - 类型本质上是可变的,因此没有简单的好功能方式.您的版本效率很低,因为它Skip在序列上重复使用.更好的命令选项是使用GetEnumerator并使用迭代元素IEnumerator.您可以在此代码段中找到各种必要选项:http://fssnip.net/1o

如果您正在学习F#,那么最好尝试使用F#list类型编写函数.这样,您就可以使用惯用的功能风格.然后你可以batchesOf使用模式匹配来编写递归和累加器参数,如下所示:

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 [] []
Run Code Online (Sandbox Code Playgroud)

作为一个脚注,使用计算表达式可以使命令版本更好一些IEnumerator,但这不是标准的,它是非常高级的技巧(例如,见http://fssnip.net/37).


Ash*_*eyF 10

一段时间朋友问过我.这是一个回收的答案.这是有效的,纯粹的:

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

或者不纯的版本:

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

这些产生了seq<seq<'a>>.如果你'a list list的样本中必须有一个as,那么只需添加... |> Seq.map (List.ofSeq) |> List.ofSeq如下:

> [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]]
Run Code Online (Sandbox Code Playgroud)

希望有所帮助!