Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/powerbi/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Linq 在F中编写大小顺序的批处理的最惯用方法#_Linq_F#_Functional Programming_Sequence - Fatal编程技术网

Linq 在F中编写大小顺序的批处理的最惯用方法#

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尤其令人不快,因为要取消引用它,必须使用运算

我试图通过将一些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可变变量。

按照惯用方式使用
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]
}