我在hubFS上找到了this question,但它根据各个元素处理了拆分条件。我想根据相邻元素的比较进行拆分,因此类型如下所示:
val split = ('T -> 'T -> bool) -> 'T list -> 'T list list
目前,我正试图从Don的必要解决方案开始,但我无法弄清楚如何初始化和使用'prev'值进行比较。折叠是一种更好的方式吗?
//Don's solution for single criteria, copied from hubFS
let SequencesStartingWith n (s:seq<_>) =
seq { use ie = s.GetEnumerator()
let acc = new ResizeArray<_>()
while ie.MoveNext() do
let x = ie.Current
if x = n && acc.Count > 0 then
yield ResizeArray.to_list acc
acc.Clear()
acc.Add x
if acc.Count > 0 then
yield ResizeArray.to_list acc }
答案 0 :(得分:8)
这是一个有趣的问题!我需要在最近为我的article about grouping在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
现在我们可以使用splitAt
在输入列表上重复应用foldUntilEmpty
(将一些谓词指定为第一个参数),这为我们提供了我们想要的功能:
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#核心库中的函数那样通用。
答案 1 :(得分:6)
怎么样:
let splitOn test lst =
List.foldBack (fun el lst ->
match lst with
| [] -> [[el]]
| (x::xs)::ys when not (test el x) -> (el::(x::xs))::ys
| _ -> [el]::lst
) lst []
foldBack无需反转列表。
答案 2 :(得分:2)
进一步思考这个问题后,我想出了这个解决方案。我不确定它是否具有可读性(除了编写它的我)。
更新基于Tomas答案中更好的匹配示例,这是一个改进的版本,它消除了“代码味道”(请参阅以前版本的编辑),并且稍微更具可读性(我说)。
由于可怕的value restriction error,它仍会在此(splitOn (<>) []
)上中断,但我认为这可能是不可避免的。
(编辑:更正了Johan Kullbom发现的错误,现在正好适用于[1; 1; 2; 3]。问题是在第一场比赛中直接吃了两个元素,这意味着我错过了比较/检查。)
//Function for splitting list into list of lists based on comparison of adjacent elements
let splitOn test lst =
let rec loop lst inner outer = //inner=current sublist, outer=list of sublists
match lst with
| x::y::ys when test x y -> loop (y::ys) [] (List.rev (x::inner) :: outer)
| x::xs -> loop xs (x::inner) outer
| _ -> List.rev ((List.rev inner) :: outer)
loop lst [] []
splitOn (fun a b -> b - a > 1) [1]
> val it : [[1]]
splitOn (fun a b -> b - a > 1) [1;3]
> val it : [[1]; [3]]
splitOn (fun a b -> b - a > 1) [1;2;3;4;6;7;8;9;11;12;13;14;15;16;18;19;21]
> val it : [[1; 2; 3; 4]; [6; 7; 8; 9]; [11; 12; 13; 14; 15; 16]; [18; 19]; [21]]
对此有任何想法,或者我的问题中的部分解决方案?
答案 3 :(得分:1)
我更希望使用List.fold
而不是显式递归。
let splitOn pred = function
| [] -> []
| hd :: tl ->
let (outer, inner, _) =
List.fold (fun (outer, inner, prev) curr ->
if pred prev curr
then (List.rev inner) :: outer, [curr], curr
else outer, curr :: inner, curr)
([], [hd], hd)
tl
List.rev ((List.rev inner) :: outer)
答案 4 :(得分:1)
“毗邻”立刻让我想到了Seq.pairwise。
let splitAt pred xs =
if Seq.isEmpty xs then
[]
else
xs
|> Seq.pairwise
|> Seq.fold (fun (curr :: rest as lists) (i, j) -> if pred i j then [j] :: lists else (j :: curr) :: rest) [[Seq.head xs]]
|> List.rev
|> List.map List.rev
示例:
[1;1;2;3;3;3;2;1;2;2]
|> splitAt (>)
给出:
[[1; 1; 2; 3; 3; 3]; [2]; [1; 2; 2]]
答案 5 :(得分:0)
我喜欢@Joh和@Johan提供的答案,因为这些解决方案似乎是最惯用和直截了当的。我也喜欢@Shooton提出的想法。但是,每种解决方案都有其自身的缺点 我试图避免:
match
说明Seq.pairwise
似乎也是多余的Unchecked.defaultof<_>
以下这是我的版本:
let splitWhen f src =
if List.isEmpty src then [] else
src
|> List.foldBack
(fun el (prev, current, rest) ->
if f el prev
then el , [el] , current :: rest
else el , el :: current , rest
)
<| (List.head src, [], []) // Initial value does not matter, dislike using Unchecked.defaultof<_>
|> fun (_, current, rest) -> current :: rest // Merge temporary lists
|> List.filter (not << List.isEmpty) // Drop tail element