这是一个有意思的问题!最近我需要在C#中实现这个功能,用于我的
关于分组的文章(因为该函数的类型签名与
groupBy
非常相似,所以可以在LINQ查询中作为
group by
子句使用)。不过,C#的实现方式相当丑陋。
无论如何,肯定有一种方法可以使用一些简单的基元表达此函数。似乎F#库没有提供任何适合此目的的函数。我能够想出两个函数,它们似乎是普遍有用的,并且可以结合在一起解决此问题,因此它们在此处:
// Splits a list into two lists using the specified function
// The list is split between two elements for which 'f' returns 'true'
let splitAt f list =
let rec splitAtAux acc list =
match list with
| x::y::ys when f x y -> List.rev (x::acc), y::ys
| x::xs -> splitAtAux (x::acc) xs
| [] -> (List.rev acc), []
splitAtAux [] list
val splitAt : ('a -> 'a -> bool) -> 'a list -> 'a list * 'a list
这与我们想要实现的类似,但它仅将列表分成两部分(这比多次分割列表更简单)。然后我们需要重复此操作,可以使用以下函数完成:
// Repeatedly uses 'f' to take several elements of the input list and
// aggregate them into value of type 'b until the remaining list
// (second value returned by 'f') is empty
let foldUntilEmpty f list =
let rec foldUntilEmptyAux acc list =
match f list with
| l, [] -> l::acc |> List.rev
| l, rest -> foldUntilEmptyAux (l::acc) rest
foldUntilEmptyAux [] list
val foldUntilEmpty : ('a list -> 'b * 'a list) -> 'a list -> 'b list
现在我们可以使用
foldUntilEmpty
反复应用
splitAt
(将某个谓词指定为第一个参数)到输入列表中,这样就得到了我们想要的函数。
let splitAtEvery f list = foldUntilEmpty (splitAt f) list
splitAtEvery (<>) [ 1; 1; 1; 2; 2; 3; 3; 3; 3 ];;
val it : int list list = [[1; 1; 1]; [2; 2]; [3; 3; 3; 3]]
我认为最后一步非常好 :-). 前两个函数非常直观,可能对其他事情有用,尽管它们不像来自F#核心库的函数那样通用。
pred = true
元素。 - Benjol