List 如何用给定的组大小对列表进行分区?
我正在寻找对列表(或seq)进行分区的最佳方法,以便组具有给定的大小。 例如,假设我想分组为2号(但可以是任何其他号码): 实现partitionBySize的明显方法是将位置添加到输入列表中的每个元组中,使其成为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
[(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
循环中使用slicearr.[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]]