Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.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
List 如何用给定的组大小对列表进行分区?_List_F# - Fatal编程技术网

List 如何用给定的组大小对列表进行分区?

List 如何用给定的组大小对列表进行分区?,list,f#,List,F#,我正在寻找对列表(或seq)进行分区的最佳方法,以便组具有给定的大小。 例如,假设我想分组为2号(但可以是任何其他号码): 实现partitionBySize的明显方法是将位置添加到输入列表中的每个元组中,使其成为 [(0,a,b,c), (1,a,b,d), (2,y,z,y), (3,w,y,z), (4,n,y,z)] 然后使用GroupBy和 xs |> Seq.ofList |> Seq.GroupBy (function | (i,_,_,_) -> i - (i

我正在寻找对列表(或seq)进行分区的最佳方法,以便组具有给定的大小。 例如,假设我想分组为2号(但可以是任何其他号码):

实现partitionBySize的明显方法是将位置添加到输入列表中的每个元组中,使其成为

[(0,a,b,c), (1,a,b,d), (2,y,z,y), (3,w,y,z), (4,n,y,z)]
然后使用GroupBy和

xs |> Seq.ofList |> Seq.GroupBy (function | (i,_,_,_) -> i - (i % n))
然而,这个解决方案在我看来并不优雅。
有没有更好的方法来实现这个函数(可能是内置函数)?

递归方法呢只需要一次通过

let rec partitionBySize length inp dummy = 
    match inp with
    |h::t ->
        if dummy |> List.length < length then
            partitionBySize length t (h::dummy)
        else dummy::(partitionBySize length t (h::[]))
    |[] -> dummy::[]
让rec partitionBySize length inp dummy=
将inp与
|h::t->
如果dummy |>List.length虚拟::[]

然后用
partitionBySize 2 xs[]

调用它。这似乎是一个重复模式,F#core库中的任何函数都无法捕捉到。在前面解决类似问题时,我定义了一个函数
Seq.groupWhen
(请参阅),该函数将序列转换为组。当谓词保持时,将启动一个新组

您可以使用
Seq.groupWhen
解决问题,类似于
Seq.group
(通过在偶数索引处启动新组)。与
Seq.group
不同,这是有效的,因为
Seq.groupWhen
只在输入序列上迭代一次:

[3;3;2;4;1;2;8] 
|> Seq.mapi (fun i v -> i, v) // Add indices to the values (as first tuple element)
|> Seq.groupWhen (fun (i, v) -> i%2 = 0) // Start new group after every 2nd element
|> Seq.map (Seq.map snd) // Remove indices from the values
直接使用递归实现函数可能更容易-John的解决方案正是您所需要的-但是如果您想看到更通用的方法,那么
Seq.groupWhen
可能会很有趣

let partitionBySize size xs =
  let sq = ref (seq xs)
  seq {
    while (Seq.length !sq >= size) do
      yield Seq.take size !sq
      sq := Seq.skip size !sq
    if not (Seq.isEmpty !sq) then yield !sq
  }
  // result to list, if you want
  |> Seq.map (Seq.toList)
  |> Seq.toList
更新

let partitionBySize size (sq:seq<_>) =
  seq {
    let e = sq.GetEnumerator()
    let empty = ref true;
    while !empty do
      yield seq { for i = 1 to size do
                    empty := e.MoveNext()
                    if !empty then yield e.Current
            }
  }

嗯,我参加晚会迟到了。下面的代码是使用
列表上的高阶函数的尾部递归版本:

let partitionBySize size xs =
    let i = size - (List.length xs - 1) % size
    let xss, _, _ =
        List.foldBack( fun x (acc, ls, j) -> 
                                          if j = size then ((x::ls)::acc, [], 1) 
                                          else (acc, x::ls, j+1)
                       ) xs ([], [], i)
    xss
我做了和丹尼尔一样的基准测试。这个功能是有效的,而它是2倍的速度比他的方法在我的机器上。我还将其与阵列/循环版本进行了比较,它们在性能方面具有可比性


此外,与John的答案不同,这个版本保留了内部列表中元素的顺序。

这里有一个尾部递归函数,它遍历列表一次

let chunksOf n items =
  let rec loop i acc items = 
    seq {
      match i, items, acc with
      //exit if chunk size is zero or input list is empty
      | _, [], [] | 0, _, [] -> ()
      //counter=0 so yield group and continue looping
      | 0, _, _::_ -> yield List.rev acc; yield! loop n [] items 
      //decrement counter, add head to group, and loop through tail
      | _, h::t, _ -> yield! loop (i-1) (h::acc) t
      //reached the end of input list, yield accumulated elements
      //handles items.Length % n <> 0
      | _, [], _ -> yield List.rev acc
    }
  loop n [] items
我喜欢Tomas方法的优雅,但我使用1000万个元素的输入列表对这两个函数进行了基准测试。这一次是9秒对22秒。当然,正如他承认的那样,最有效的方法可能涉及数组/循环。

(帽子提示:)现在可以使用了,并且完全符合您所说的。它似乎是F#4.0的新版本


Seq.chunkBySize
Array.chunkBySize
现在也可以使用。

在您的示例中,列表中的组(注释行)被建模为元组。我猜你希望它是列表,即列表中的列表,而不是元组,因为使用Ankur的静态typingAgree是不可能的。更重要的是,您期望的结果不是有效的F#值,因为最后一个元组与其余元组不同。列表中的所有项目都应该有相同的类型。是的,对不起,这就是我的意思,谢谢你指出错误。我据此编辑了这个问题。结果:[(1,2,4);(1,2,3)];[(5,6,7);(6,7,6)],失败data@BLUEPIXY-忘了返回累加器-应该修复得很好,唯一的缺点是它不是尾部递归的,并且具有足够大的输入(碰巧我有:-)有趣。到目前为止最有效的解决方案。@FrancescoDeVittori如果您正在寻找最有效的解决方案,那么我认为将输入转换为数组并在
for
循环中使用slice
arr.[I..I+n]
将是最有效的选择。我真的不明白为什么,但这对于我的大型数据集来说相当缓慢(比Tomas groupWhen慢得多)这很慢,因为
Seq.skip
效率很低,特别是当你对一个已经是
Seq.skip
结果的序列调用它时。返回的序列从一开始就迭代原始序列,每次访问它。我想看看你的基准测试。在我对10万个元素的测试中,我的仍然快了一秒。我得到了初始迭代和后续迭代的结果不同。从冷启动开始,我的速度更快。在后续运行中,你的速度更快(大约3秒——仍然不是2倍)。不确定该归因于什么。这是在FSI中。你知道为什么这个函数从新的FSI会话的10秒下降到后续运行的~6秒吗?我的函数几乎保持不变(~9秒)。我只是想知道会有什么不同。我得到的结果来自于有1M个元素的基准测试。我不知道为什么冷启动和后续运行之间会有一些差距。也许在实现列表的背后有一些技巧。折回?使用1M和我的10M解释了这种差距。差距会随着输入的增加而缩小,最终会向里倾斜我的好意,因为你的返回一个列表(结果在内存中累积),我的返回一个序列(提供恒定的内存使用)。我会选择这个,但其他的也很好。谢谢大家!
let partitionBySize size xs =
    let i = size - (List.length xs - 1) % size
    let xss, _, _ =
        List.foldBack( fun x (acc, ls, j) -> 
                                          if j = size then ((x::ls)::acc, [], 1) 
                                          else (acc, x::ls, j+1)
                       ) xs ([], [], i)
    xss
let chunksOf n items =
  let rec loop i acc items = 
    seq {
      match i, items, acc with
      //exit if chunk size is zero or input list is empty
      | _, [], [] | 0, _, [] -> ()
      //counter=0 so yield group and continue looping
      | 0, _, _::_ -> yield List.rev acc; yield! loop n [] items 
      //decrement counter, add head to group, and loop through tail
      | _, h::t, _ -> yield! loop (i-1) (h::acc) t
      //reached the end of input list, yield accumulated elements
      //handles items.Length % n <> 0
      | _, [], _ -> yield List.rev acc
    }
  loop n [] items
[1; 2; 3; 4; 5]
|> chunksOf 2 
|> Seq.toList //[[1; 2]; [3; 4]; [5]]
let grouped = [1..10] |> List.chunkBySize 3
// val grouped : int list list = 
//   [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]; [10]]