Linq 在F中编写大小顺序的批处理的最惯用方法#
我试图通过将一些C算法改写成惯用的F来学习F 我尝试重写的第一个函数之一是batchesOf,其中:Linq 在F中编写大小顺序的批处理的最惯用方法#,linq,f#,functional-programming,sequence,Linq,F#,Functional Programming,Sequence,我试图通过将一些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尤其令人不快,因为要取消引用它,必须使用运算
[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可变变量。按照惯用方式使用
seq
类型实现此函数是困难的-类型本身是可变的,因此没有简单好的函数方式。您的版本效率很低,因为它在序列上重复使用Skip
。更好的命令式选项是使用GetEnumerator
,只需使用IEnumerator
迭代元素即可。您可以在此代码段中找到各种命令选项:
如果您正在学习F#,那么最好尝试使用F#列表类型编写函数。这样,您就可以使用惯用的功能性样式。然后,您可以使用模式匹配和递归以及累加器参数编写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 [] []
让batchesOf size输入=
//完成实际工作的内部功能。
//“input”是列表的剩余部分,“num”是元素数
//在当前批次中,存储在“批次”中。最后,“acc”是一个
//批次(按相反顺序)
让rec循环输入num batch acc=
匹配输入
| [] ->
//我们已到达终点-将当前批次添加到所有批次的列表中
//批次(如果不为空)并返回批次(按正确顺序)
如果批次[],则(List.rev batch)::acc else acc
|>List.rev
|当num=size-1->
//我们已经到达批处理的末尾-添加最后一个元素
//并将批次添加到批次列表中。
循环xs 0[](List.rev(x::batch))::acc)
|x::xs->
//从输入中提取一个元素并将其添加到当前批处理中
循环xs(num+1)(x::batch)acc
循环输入0[][]
作为一个脚注,使用计算表达式处理
IEnumerator
,命令式版本可以变得更好一些,但这不是标准的,而且是非常高级的技巧(例如,请参见)。如果需要,这可以在不使用递归的情况下完成
[0..20]
|> Seq.mapi (fun i elem -> (i/size),elem)
|> Seq.groupBy (fun (a,_) -> a)
|> Seq.map (fun (_,se) -> se |> Seq.map (snd));;
val it : seq<seq<int>> =
seq
[seq [0; 1; 2; 3; ...]; seq [5; 6; 7; 8; ...]; seq [10; 11; 12; 13; ...];
seq [15; 16; 17; 18; ...]; ...]
[0..20]
|>Seq.mapi(乐趣元素->(元素/大小),元素)
|>Seq.groupBy(乐趣(a,)->a)
|>Seq.map(fun(124;,se)->se |>Seq.map(snd));;
val it:seq=
序号
[seq[0;1;2;3;…];seq[5;6;7;8;…];seq[10;11;12;13;…];
序号[15;16;17;18;…];…]
这取决于你认为这可能更容易理解。Tomas的解决方案可能更为惯用,尽管这可能不是惯用的,但它是有效的:
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
让n-l的批处理=
让u,u,temp',res'=List.fold(乐趣(i,n,temp,res)hd->
如果我List.rev
下面是一个简单的序列实现:
let chunks size (items:seq<_>) =
use e = items.GetEnumerator()
let rec loop i acc =
seq {
if i = size then
yield (List.rev acc)
yield! loop 0 []
elif e.MoveNext() then
yield! loop (i+1) (e.Current::acc)
else
yield (List.rev acc)
}
if size = 0 then invalidArg "size" "must be greater than zero"
if Seq.isEmpty items then Seq.empty else loop 0 []
let s = Seq.init 10 id
chunks 3 s
//output: seq [[0; 1; 2]; [3; 4; 5]; [6; 7; 8]; [9]]
let chunks size(项目:seq)=
使用e=items.GetEnumerator()
让rec循环i acc=
序号{
如果i=大小,则
收益率(列表版本acc)
屈服!循环0[]
elif e.MoveNext()那么
屈服!回路(i+1)(即电流::acc)
其他的
收益率(列表版本acc)
}
如果大小=0,则invalidArg“大小”必须大于零
如果Seq.isEmpty items,则Seq.empty else循环0[]
设s=Seq.init 10 id
块3
//输出:seq[[0;1;2];[3;4;5];[6;7;8];[9]]
不久前一位朋友问我这个问题。这里有一个循环的答案。这是有效的,也是纯粹的:
let batchesOf n =
Seq.mapi (fun i v -> i / n, v) >>
Seq.groupBy fst >>
Seq.map snd >>
Seq.map (Seq.map snd)
或不纯的版本:
let batchesOf n =
let i = ref -1
Seq.groupBy (fun _ -> i := !i + 1; !i / n) >> Seq.map snd
它们生成一个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]]
希望有帮助 您可以通过以下模拟解决您的任务: 通过使用
n
和step
,您可以使用它对重叠批次进行切片,即滑动窗口,甚至可以应用于无限序列,如下所示:
Seq.initInfinite(fun x -> x) |> partition 4 1;;
val it : seq<seq<int>> =
seq
[seq [0; 1; 2; 3]; seq [1; 2; 3; 4]; seq [2; 3; 4; 5]; seq [3; 4; 5; 6];
...]
Seq.initInfinite(funx->x)|>partition41;;
val it:seq=
序号
[seq[0;1;2;3];seq[1;2;3;4];seq[2;3;4;5];seq[3;4;5;6];
...]
仅将其视为原型,因为它对源序列进行了许多冗余评估,不太可能适合生产目的。此版本通过了我所能想到的所有测试,包括惰性评估和单序列评估测试:
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))
我对F#还很陌生,所以如果我遗漏了什么,请纠正我,我们将不胜感激 我的方法包括将列表转换为数组并递归地对数组进行分块:
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]]
我发现这是一个非常简洁的解决方案:
let partition n (stream:seq<_>) = seq {
let enum = stream.GetEnumerator()
let rec collect n partition =
if n = 1 || not (enum.MoveNext()) then
partition
else
collect (n-1) (partition @ [enum.Current])
while enum.MoveNext() do
yield collect n [enum.Current]
}
让分区n(流:seq)=seq{
让enum=stream.GetEnumerator()
让rec收集n个分区=
如果n=1 | | not(enum.MoveNext()),则
隔断
其他的
收集(n-1)(分区@[enum.Current
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]]
let partition n (stream:seq<_>) = seq {
let enum = stream.GetEnumerator()
let rec collect n partition =
if n = 1 || not (enum.MoveNext()) then
partition
else
collect (n-1) (partition @ [enum.Current])
while enum.MoveNext() do
yield collect n [enum.Current]
}