大多数惯用的写批次的方式在F# [英] Most idiomatic way to write batchesOf size seq in F#

查看:107
本文介绍了大多数惯用的写批次的方式在F#的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图通过重写一些C#算法来学习F#,这些算法是我惯用的F#。



我想重写的第一个函数是batchesOf,其中:

  [1..17] |> batchesOf 5 

将序列分成最多5个批次,即: p>

  [[1; 2; 3; 4; 5]; [6; 7; 8; 9; 10]; [11; 12; 13; 14; 15]; [16; 17]] 

我第一次尝试这样做很丑陋,在试图在闭包中使用 mutable 类型时出现错误,这是可变的 ref 对象。使用 ref 尤其令人不愉快,因为要解除引用它,您必须使用运算符,该运算符在条件表达式内部对某些将读取为逻辑不。我遇到的另一个问题是Seq.skip和Seq.take与其Linq别名不同,因为如果 size 超过了序列的大小,它们会发出错误。

  let batchesOf size(sequence:_ seq):_ list seq = 
seq {
let s = ref序列
,而不是(!s |> Seq.isEmpty)do
yield!s |> Seq.truncate size |> List.ofSeq
s:= System.Linq.Enumerable.Skip(!s,size)
}

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

解决方案

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



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

pre $ code>让batchesOf size input =
//执行实际工作的内部函数。
//'input'是列表的其余部分,'num'是当前批次中元素的数量
//,它存储在'batch'中。最后,'acc'是
//批次的列表(以相反的顺序)
让rec循环输入num批量acc =
匹配输入与
| [] - >
//我们已经达到最后 - 如果不是空的,则将当前批次添加到所有
//批次的列表中,并返回批次(按照正确的顺序)
如果批次< > [] then(List.rev batch):: acc else acc
|> List.rev
|当num = size时,x :: xs - 1 - >
//我们已经到达批处理的最后 - 添加最后一个元素
//并将批处理添加到批处理列表中。
loop xs 0 []((List.rev(x :: batch)):: acc)
| x :: xs - >
//从输入中取一个元素并将其添加到当前批处理
循环xs(num + 1)(x :: batch)acc
循环输入0 [] []

作为脚注,使用计算表达式可以使命令式版本更好一些用于处理 IEnumerator ,但这不是标准的,它是相当先进的技巧(例如,参见 http://fssnip.net/37 )。


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:

[1..17] |> batchesOf 5

Which would split the sequence into batches with a max of five in each, i.e:

[[1; 2; 3; 4; 5]; [6; 7; 8; 9; 10]; [11; 12; 13; 14; 15]; [16; 17]]

My first attempt at doing this is kind of ugly where I've resorted to using a mutable ref object after running into errors trying to use mutable type inside the closure. Using ref is particularly unpleasant since to dereference it you have to use the ! operator which when inside a condition expression can be counter intuitive to some devs who will read it as logical not. Another problem I ran into is where Seq.skip and Seq.take are not like their Linq aliases in that they will throw an error if size exceeds the size of the sequence.

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)
    }

Anyway what would be the most elegant/idiomatic way to rewrite this in F#? Keeping the original behaviour but preferably without the ref mutable variable.

解决方案

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).

这篇关于大多数惯用的写批次的方式在F#的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆