繁体   English   中英

F# 中的拆分序列

[英]Split seq in F#

我应该通过元素的属性将seq<a>拆分为seq<seq<a>> 如果此属性等于给定值,则必须在该点“拆分”。 我怎样才能在FSharp 中做到这一点?

如果必须在该项目处拆分或不拆分,则向其传递一个返回布尔值的“函数”应该很好。

示例: 输入序列: seq: {1,2,3,4,1,5,6,7,1,9}当它等于 1 时应该在每个项目处拆分,所以结果应该是:

seq
{
seq{1,2,3,4}
seq{1,5,6,7}
seq{1,9}
}

你真正要做的就是分组——每次遇到一个值时创建一个新组。

let splitBy f input =
  let i = ref 0
  input 
  |> Seq.map  (fun x -> 
    if f x then incr i
    !i, x)
  |> Seq.groupBy fst
  |> Seq.map (fun (_, b) -> Seq.map snd b)

例子

let items = seq [1;2;3;4;1;5;6;7;1;9]
items |> splitBy ((=) 1)

再一次,更短,斯蒂芬的不错的改进:

let splitBy f input =
  let i = ref 0
  input
  |> Seq.groupBy (fun x ->
    if f x then incr i
    !i)
  |> Seq.map snd

不幸的是,编写使用序列( seq<'T>类型)的函数有点困难。 它们不能很好地处理列表上的模式匹配等功能概念。 相反,您必须使用GetEnumerator方法和生成的IEnumerator<'T>类型。 这通常使代码非常必要。 在这种情况下,我会写以下内容:

let splitUsing special (input:seq<_>) = seq { 
  use en = input.GetEnumerator()
  let finished = ref false
  let start = ref true
  let rec taking () = seq {
    if not (en.MoveNext()) then finished := true
    elif en.Current = special then start := true
    else 
      yield en.Current
      yield! taking() }

  yield taking()
  while not (!finished) do
    yield Seq.concat [ Seq.singleton special; taking()] }

我不推荐使用函数式风格(例如使用Seq.skipSeq.head ),因为这非常低效 - 它创建了一个序列链,从其他序列中获取值并返回它(所以通常有 O( N^2) 复杂性)。

或者,您可以使用计算生成器来编写此代码以使用IEnumerator<'T> ,但这不是标准的。 如果你想玩它,你可以在这里找到它。

以下是一个不纯的实现,但会懒惰地产生不可变的序列:

let unflatten f s = seq {
    let buffer = ResizeArray()

    let flush() = seq { 
        if buffer.Count > 0 then 
            yield Seq.readonly (buffer.ToArray())
            buffer.Clear() }

    for item in s do
        if f item then yield! flush()
        buffer.Add(item)

    yield! flush() }

f是 function 用于测试一个元素是否应该是一个分割点:

[1;2;3;4;1;5;6;7;1;9] |> unflatten (fun item -> item = 1)

可能不是最有效的解决方案,但这有效:

let takeAndSkipWhile f s = Seq.takeWhile f s, Seq.skipWhile f s

let takeAndSkipUntil f = takeAndSkipWhile (f >> not)

let rec splitOn f s =
    if Seq.isEmpty s then
        Seq.empty
    else
        let pre, post =
            if f (Seq.head s) then
                takeAndSkipUntil f (Seq.skip 1 s)
                |> fun (a, b) ->
                    Seq.append [Seq.head s] a, b
            else
                takeAndSkipUntil f s
        if Seq.isEmpty pre then
            Seq.singleton post
        else
            Seq.append [pre] (splitOn f post)

splitOn ((=) 1) [1;2;3;4;1;5;6;7;1;9] // int list is compatible with seq<int>

splitOn 的类型是 ('a -> bool) -> seq<'a> -> seq>。 我没有在很多输入上测试过它,但它似乎有效。

如果您正在寻找实际上像 split 作为字符串拆分一样工作的东西(即,谓词返回 true 的项目不包括在内),下面是我想出的.. 试图尽可能地发挥作用:)

let fromEnum (input : 'a IEnumerator) = 
    seq {
        while input.MoveNext() do
            yield input.Current
    }

let getMore (input : 'a IEnumerator) = 
    if input.MoveNext() = false then None
    else Some ((input |> fromEnum) |> Seq.append [input.Current])

let splitBy (f : 'a -> bool) (input : 'a seq)  = 
    use s = input.GetEnumerator()
    let rec loop (acc : 'a seq seq) = 
        match s |> getMore with 
        | None -> acc
        | Some x ->[x |> Seq.takeWhile (f >> not) |> Seq.toList |> List.toSeq]
                   |> Seq.append acc
                   |> loop
    loop Seq.empty |> Seq.filter (Seq.isEmpty >> not)

seq [1;2;3;4;1;5;6;7;1;9;5;5;1]
|> splitBy ( (=) 1) |> printfn "%A"

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM