如何在 F# 中对顺序值进行分组
how can I group sequential values, in F#
让我们考虑以下排序数据:
[1; 2; 3; 5; 6; 20; 21; 22; 23]
我想得到:
[ [1; 2; 3]; [5; 6]; [20; 21; 22; 23] ]
实现此目标的最佳方法是什么? (列表最多 1000 个条目)。
所以你是在暗示你想根据两个连续值之间的差应该是 1 的条件进行分组。或者从另一个角度来看,当差不是 1 时拆分。
可以找到按谓词对列表进行分组的示例 and here。考虑输入序列而不是列表,我们可能会从我们能够懒惰地使用它的事实中得到一些好处。
let splitWhen condition (source : seq<'T>) : seq<'T[]> = seq {
let stateref, acc = ref None, ResizeArray()
use ie = source.GetEnumerator()
while ie.MoveNext() do
let current = ie.Current
match !stateref with
| Some previous when condition previous current ->
yield acc.ToArray()
acc.Clear()
| _ -> ()
acc.Add current
stateref := Some current
if acc.Count > 0 then
yield acc.ToArray() }
[1; 2; 3; 5; 6; 20; 21; 22; 23]
|> splitWhen (fun x0 x1 -> x1 - x0 <> 1)
// val it : seq<int []> = seq [[|1; 2; 3|]; [|5; 6|]; [|20; 21; 22; 23|]]
let group ls =
[
let mutable last = 0
let mutable current = []
for x in ls do
if x > (last + 1) then
if not current.IsEmpty then
yield current |> List.rev
current <- [x]
else
current <- x :: current
last <- x
if not current.IsEmpty then
yield current |> List.rev
]
group [1; 2; 3; 5; 6; 20; 21; 22; 23]
val it : int list list = [[1; 2; 3]; [5; 6]; [20; 21; 22; 23]]
这里的版本可能更加地道,至少对我而言,更具可读性。它仅依赖于 List
模块中的标准函数 foldBack
并且完全避免了可变性。
let group ls =
(ls, [])
||> List.foldBack (fun l s ->
match s with
| [] | [[]] -> [[l]]
| (n::res)::ns ->
if n = l+1
then (l::n::res)::ns
else [l]::(n::res)::ns)
也可以重构为更通用的函数
let chunkBy cond ls =
(ls, [])
||> List.foldBack (fun l s ->
match s with
| [] | [[]] -> [[l]]
| (n::res)::ns ->
if cond l n
then (l::n::res)::ns
else [l]::(n::res)::ns)
let group = chunkBy (fun prev next -> prev + 1 = next)
请注意,这利用了仅在列表上实现的缺点运算符 ::
。 cons 运算符将一个项目添加到列表中,这就是该函数使用 foldBack
.
的原因
这是一个将输入列表折叠成新列表并根据需要分组的实现。
let group l =
let addNextInt (i : int) (l : List<List<int>>) =
match l with
| [] -> [[i]]
| (x::xs)::tail ->
if x - i = 1 then (i::x::xs)::tail
else [i]::(x::xs)::tail
| _ -> failwith "Unreachable"
List.foldBack addNextInt l []
// Input: [], Output: []
// Input: [1; 2], Output: [[1; 2]]
// Input: [1; 3], Output: [[1]; [3]]
// Input: [1; 3; 5; 6], Output: [[1]; [3]; [5; 6]]
// Input: [1; 2; 3; 5; 6; 20; 21; 22; 23], Output: [[1; 2; 3]; [5; 6]; [20; 21; 22; 23]]
让我们考虑以下排序数据:
[1; 2; 3; 5; 6; 20; 21; 22; 23]
我想得到:
[ [1; 2; 3]; [5; 6]; [20; 21; 22; 23] ]
实现此目标的最佳方法是什么? (列表最多 1000 个条目)。
所以你是在暗示你想根据两个连续值之间的差应该是 1 的条件进行分组。或者从另一个角度来看,当差不是 1 时拆分。
可以找到按谓词对列表进行分组的示例
let splitWhen condition (source : seq<'T>) : seq<'T[]> = seq {
let stateref, acc = ref None, ResizeArray()
use ie = source.GetEnumerator()
while ie.MoveNext() do
let current = ie.Current
match !stateref with
| Some previous when condition previous current ->
yield acc.ToArray()
acc.Clear()
| _ -> ()
acc.Add current
stateref := Some current
if acc.Count > 0 then
yield acc.ToArray() }
[1; 2; 3; 5; 6; 20; 21; 22; 23]
|> splitWhen (fun x0 x1 -> x1 - x0 <> 1)
// val it : seq<int []> = seq [[|1; 2; 3|]; [|5; 6|]; [|20; 21; 22; 23|]]
let group ls =
[
let mutable last = 0
let mutable current = []
for x in ls do
if x > (last + 1) then
if not current.IsEmpty then
yield current |> List.rev
current <- [x]
else
current <- x :: current
last <- x
if not current.IsEmpty then
yield current |> List.rev
]
group [1; 2; 3; 5; 6; 20; 21; 22; 23]
val it : int list list = [[1; 2; 3]; [5; 6]; [20; 21; 22; 23]]
这里的版本可能更加地道,至少对我而言,更具可读性。它仅依赖于 List
模块中的标准函数 foldBack
并且完全避免了可变性。
let group ls =
(ls, [])
||> List.foldBack (fun l s ->
match s with
| [] | [[]] -> [[l]]
| (n::res)::ns ->
if n = l+1
then (l::n::res)::ns
else [l]::(n::res)::ns)
也可以重构为更通用的函数
let chunkBy cond ls =
(ls, [])
||> List.foldBack (fun l s ->
match s with
| [] | [[]] -> [[l]]
| (n::res)::ns ->
if cond l n
then (l::n::res)::ns
else [l]::(n::res)::ns)
let group = chunkBy (fun prev next -> prev + 1 = next)
请注意,这利用了仅在列表上实现的缺点运算符 ::
。 cons 运算符将一个项目添加到列表中,这就是该函数使用 foldBack
.
这是一个将输入列表折叠成新列表并根据需要分组的实现。
let group l =
let addNextInt (i : int) (l : List<List<int>>) =
match l with
| [] -> [[i]]
| (x::xs)::tail ->
if x - i = 1 then (i::x::xs)::tail
else [i]::(x::xs)::tail
| _ -> failwith "Unreachable"
List.foldBack addNextInt l []
// Input: [], Output: []
// Input: [1; 2], Output: [[1; 2]]
// Input: [1; 3], Output: [[1]; [3]]
// Input: [1; 3; 5; 6], Output: [[1]; [3]; [5; 6]]
// Input: [1; 2; 3; 5; 6; 20; 21; 22; 23], Output: [[1; 2; 3]; [5; 6]; [20; 21; 22; 23]]