F# 中的分割序列

发布于 2024-11-25 06:56:48 字数 355 浏览 3 评论 0 原文

我应该通过元素的属性将 seq 拆分为 seq> 。如果此属性等于给定值,则必须在该点“拆分”。我怎样才能在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}
}

I should split seq<a> into seq<seq<a>> by an attribute of the elements. If this attribute equals by a given value it must be 'splitted' at that point. How can I do that in 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
{
seq{1,2,3,4}
seq{1,5,6,7}
seq{1,9}
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

帝王念 2024-12-02 06:56:48

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

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

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
暖树树初阳… 2024-12-02 06:56:48

不幸的是,编写使用序列(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> 一起使用,但这不是标准的。如果您想使用它,可以在这里找到它

Unfortunately, writing functions that work with sequences (the seq<'T> type) is a bit difficult. 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. 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 (e.g. 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).

Alternatively, you could write this using a computation builder for working with IEnumerator<'T>, but that's not standard. You can find it here, if you want to play with it.

乖乖兔^ω^ 2024-12-02 06:56:48

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

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 是用于测试元素是否应该是分割点的函数:

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

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:

[1;2;3;4;1;5;6;7;1;9] |> unflatten (fun item -> item = 1)
梦里寻她 2024-12-02 06:56:48

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

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> ->序列>。我还没有在很多输入上测试过它,但它似乎有效。

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>. I haven't tested it on many inputs, but it seems to work.

羅雙樹 2024-12-02 06:56:48

如果您正在寻找实际上像 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"

In case you are looking for something which actually works like split as an string split (i.e 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 :)

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"
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文