F#基于相邻元素的比较将列表拆分为子列表

Ben*_*jol 10 f# list

我在hubFS上发现了这个问题,但它根据各个元素处理分裂标准.我想根据相邻元素的比较进行拆分,因此类型如下所示:

val split = ('T -> 'T -> bool) -> 'T list -> 'T list list
Run Code Online (Sandbox Code Playgroud)

目前,我试图从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 }
Run Code Online (Sandbox Code Playgroud)

Tom*_*cek 8

这是一个有趣的问题!我最近需要在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
Run Code Online (Sandbox Code Playgroud)

这与我们想要实现的类似,但它仅将列表拆分为两部分(这比将列表多次拆分更简单).然后我们需要重复此操作,可以使用此函数完成:

// 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
Run Code Online (Sandbox Code Playgroud)

现在我们可以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]]
Run Code Online (Sandbox Code Playgroud)

我认为最后一步非常好:-).前两个函数非常简单,可能对其他东西很有用,尽管它们不像F#核心库中的函数那样通用.


小智 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 [] 
Run Code Online (Sandbox Code Playgroud)

foldBack消除了反转列表的需要.