我正在尝试通过将一些 C# 算法重写为惯用的 F# 来学习 F#。
我尝试重写的第一个函数是batchesOf,其中:
[1..17] |> batchesOf 5
它将序列分成批次,每个批次最多有五个,即:
[[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 sequence
while not (!s |> Seq.isEmpty) do
yield !s |> Seq.truncate size |> List.ofSeq
s := System.Linq.Enumerable.Skip(!s, size)
}
无论如何,用 F# 重写这个最优雅/最惯用的方法是什么?保持原始行为,但最好没有 ref 可变变量。
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.
发布评论
评论(10)
使用
seq<_>
类型惯用方式实现此函数很困难 - 该类型本质上是可变的,因此没有简单而良好的功能方法。您的版本效率很低,因为它在序列上重复使用Skip
。更好的命令选项是使用GetEnumerator
并使用IEnumerator
迭代元素。您可以在此代码段中找到各种命令式选项: http://fssnip.net/1o如果您正在学习 F# ,那么最好尝试使用 F# 列表类型编写函数。这样,您就可以使用惯用的函数式风格。然后,您可以使用带有递归和累加器参数的模式匹配来编写
batchesOf
,如下所示:作为脚注,使用计算表达式可以使命令式版本更好一点,以便与 < code>IEnumerator,但这不是标准的,而是相当高级的技巧(例如,请参见 http://fssnip.net/37)。
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 usesSkip
repeatedly on the sequence. A better imperative option would be to useGetEnumerator
and just iterate over elements usingIEnumerator
. You can find various imperative options in this snippet: http://fssnip.net/1oIf 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: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).前段时间有朋友问我这个问题。这是一个回收的答案。这有效并且是纯的:
或者是不纯的版本:
它们产生一个
seq>
。如果您确实必须有一个'a list list
(如示例中所示),那么只需添加... |>; Seq.map(List.ofSeq)|> List.ofSeq
如:希望有帮助!
A friend asked me this a while back. Here's a recycled answer. This works and is pure:
Or an impure version:
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:Hope that helps!
万岁,我们可以在 F# 4 中使用
List.chunkBySize
、Seq.chunkBySize
和Array.chunkBySize
,如 布拉德·柯林斯和斯科特·弗拉斯钦。Hurray, we can use
List.chunkBySize
,Seq.chunkBySize
andArray.chunkBySize
in F# 4, as mentioned by Brad Collins and Scott Wlaschin.如果您愿意,这可以在没有递归的情况下完成,
具体取决于您的想法,这可能更容易理解。不过,Tomas 的解决方案可能更惯用 F#
This can be done without recursion if you want
Depending on how you think this may be easier to understand. Tomas' solution is probably more idiomatic F# though
这可能不是惯用的,但它有效:
This isn't perhaps idiomatic but it works:
这是序列的简单实现:
Here's a simple implementation for sequences:
我的方法包括将列表转换为数组并递归地对数组进行分块:
My method involves converting the list to an array and recursively chunking the array:
我发现这是一个非常简洁的解决方案:
它作用于一个序列并生成一个序列。输出序列由输入序列中的 n 个元素的列表组成。
I found this to be a quite terse solution:
It works on a sequence and produces a sequence. The output sequence consists of lists of n elements from the input sequence.
您可以使用 Clojure
partition 的模拟来解决您的任务下面的
库函数:用作
partition 5 5
它将为您提供所需的batchesOf 5
功能:作为高级功能,可以使用
n
和step
您可以使用它来切片重叠批次,也称为滑动窗口,甚至适用于无限序列,如下所示:将其视为仅原型,因为它对源序列进行了许多冗余评估,并且不太适合生产目的。
You can solve your task with analog of Clojure
partition
library function below:Being used as
partition 5 5
it will provide you with soughtbatchesOf 5
functionality:As a premium by playing with
n
andstep
you can use it for slicing overlapping batches aka sliding windows, and even apply to infinite sequences, like below:Consider it as a prototype only as it does many redundant evaluations on the source sequence and not likely fit for production purposes.
这个版本通过了我能想到的所有测试,包括惰性求值和单序列求值的测试:
我对 F# 还很陌生,所以如果我遗漏了任何内容 - 请纠正我,我们将不胜感激。
This version passes all my tests I could think of including ones for lazy evaluation and single sequence evaluation:
I am still quite new to F# so if I'm missing anything - please do correct me, it will be greatly appreciated.