简体   繁体   English

F# 中的拆分序列

[英]Split seq in F#

I should split seq<a> into seq<seq<a>> by an attribute of the elements.我应该通过元素的属性将seq<a>拆分为seq<seq<a>> If this attribute equals by a given value it must be 'splitted' at that point.如果此属性等于给定值,则必须在该点“拆分”。 How can I do that in FSharp?我怎样才能在FSharp 中做到这一点?

It should be nice to pass a 'function' to it that returns a bool if must be splitted at that item or no.如果必须在该项目处拆分或不拆分,则向其传递一个返回布尔值的“函数”应该很好。

Sample: Input sequence: seq: {1,2,3,4,1,5,6,7,1,9} It should be splitted at every items when it equals 1, so the result should be:示例: 输入序列: 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}
}

All you're really doing is grouping--creating a new group each time a value is encountered.你真正要做的就是分组——每次遇到一个值时创建一个新组。

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)

Example例子

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

Again, shorter, with Stephen's nice improvements:再一次,更短,斯蒂芬的不错的改进:

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

Unfortunately, writing functions that work with sequences (the seq<'T> type) is a bit difficult.不幸的是,编写使用序列( seq<'T>类型)的函数有点困难。 They do not nicely work with functional concepts like pattern matching on lists.它们不能很好地处理列表上的模式匹配等功能概念。 Instead, you have to use the GetEnumerator method and the resulting IEnumerator<'T> type.相反,您必须使用GetEnumerator方法和生成的IEnumerator<'T>类型。 This often makes the code quite imperative.这通常使代码非常必要。 In this case, I'd write the following:在这种情况下,我会写以下内容:

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()] }

I wouldn't recommend using the functional style (eg using Seq.skip and Seq.head ), because this is quite inefficient - it creates a chain of sequences that take value from other sequence and just return it (so there is usually O(N^2) complexity).我不推荐使用函数式风格(例如使用Seq.skipSeq.head ),因为这非常低效 - 它创建了一个序列链,从其他序列中获取值并返回它(所以通常有 O( N^2) 复杂性)。

Alternatively, you could write this using a computation builder for working with IEnumerator<'T> , but that's not standard.或者,您可以使用计算生成器来编写此代码以使用IEnumerator<'T> ,但这不是标准的。 You can find it here , if you want to play with it.如果你想玩它,你可以在这里找到它。

The following is an impure implementation but yields immutable sequences lazily:以下是一个不纯的实现,但会懒惰地产生不可变的序列:

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 is the function used to test whether an element should be a split point: f是 function 用于测试一个元素是否应该是一个分割点:

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

Probably no the most efficient solution, but this works:可能不是最有效的解决方案,但这有效:

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>

The type of splitOn is ('a -> bool) -> seq<'a> -> seq>. splitOn 的类型是 ('a -> bool) -> seq<'a> -> seq>。 I haven't tested it on many inputs, but it seems to work.我没有在很多输入上测试过它,但它似乎有效。

In case you are looking for something which actually works like split as an string split (ie the item is not included on which the predicate returns true) the below is what I came up with.. tried to be as functional as possible:)如果您正在寻找实际上像 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