【问题标题】:Split seq in F#F# 中的拆分序列
【发布时间】:2011-07-18 16:57:26
【问题描述】:

我应该通过元素的属性将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}
}

【问题讨论】:

标签: f# split seq


【解决方案1】:

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

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

【讨论】:

  • +1,非常整洁!并且如果您想要可能难以破译但令人满意的简洁函数组合,您可以使用以下内容作为splitBy 的最后一行:|> Seq.map (snd>>Seq.map snd)
  • 其实你根本不需要第一个maplet splitBy f input = let i = ref 0 in input |> Seq.groupBy (fun x -> (if f x then incr i) ; !i) |> Seq.map snd
  • @Stephen:很好!作为一名角色保护主义者,我有过短暂的失误。
  • @Stephen:希望你不介意,我将你的改进纳入我的回答中。
【解决方案2】:

不幸的是,编写使用序列(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&lt;'T&gt;,但这不是标准的。想玩的可以find it here

【讨论】:

  • input 序列的第一个元素等于special 时,splitUsing 开始时会产生一个空序列(我相信这是一个错误)。
【解决方案3】:

以下是不纯的实现,但会延迟生成不可变序列:

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)

【讨论】:

    【解决方案4】:

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

    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 -> seq>。我没有在很多输入上对其进行测试,但它似乎有效。

    【讨论】:

      【解决方案5】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-11-22
        • 2016-05-06
        • 2018-07-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-07-25
        • 2017-12-27
        相关资源
        最近更新 更多