我正在寻找分区列表(或seq)的最佳方法,以便组具有给定的大小。 对于前假设我想要分组大小为2(这可能是任何其他数字):
let xs = [(a,b,c); (a,b,d); (y,z,y); (w,y,z); (n,y,z)]
let grouped = partitionBySize 2 input
// => [[(a,b,c);(a,b,d)]; [(y,z,y);(w,y,z)]; [(n,y,z)]]
实现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))
然而,这个解决方案对我来说并不是很优雅。 有没有更好的方法来实现这个功能(可能有内置功能)?
答案 0 :(得分:9)
这似乎是F#核心库中任何函数都没有捕获的重复模式。在更早解决类似问题时,我定义了一个函数Seq.groupWhen
(参见F# snippets),它将序列转换为组。当谓词成立时,将启动一个新组。
您可以使用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
可能会很有趣。
答案 1 :(得分:5)
List.chunkBySize
(帽子提示:Scott Wlaschin)现已推出,正是您正在谈论的内容。它似乎是F#4.0的新功能。
let grouped = [1..10] |> List.chunkBySize 3
// val grouped : int list list =
// [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]; [10]]
Seq.chunkBySize
和Array.chunkBySize
现在也可用。
答案 2 :(得分:4)
这是一个遍历列表的尾递归函数。
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]]
我喜欢托马斯方法的优雅,但我使用1000万个元素的输入列表对我们的两个函数进行了基准测试。对于他来说,这个时间为9秒vs 22。当然,正如他所承认的,最有效的方法可能涉及数组/循环。
答案 3 :(得分:3)
递归方法怎么样? - 只需要一次通过
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::[]
然后使用partitionBySize 2 xs []
答案 4 :(得分:2)
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 xa = Array.ofList xs
let len = xa.Length
[
for i in 0..size..(len-1) do
yield ( if i + size >= len then xa.[i..] else xa.[i..(i+size-1)] ) |> Array.toList
]
答案 5 :(得分:1)
List
上的高阶函数的尾递归版本:
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的回答不同,此版本保留了内部列表中元素的顺序。