【问题标题】:Lazily grouping a flat sequence in F#在 F# 中对平面序列进行延迟分组
【发布时间】:2023-03-07 20:46:01
【问题描述】:

给定一系列项目如下:

[ ("a", 1); ("a", 2); ("a", 3); ("b", 1); ("c", 2); ("c", 3) ]

我怎样才能把这个懒惰地转换成:

{ ("a", { 1; 2; 3}); ("b", { 1 }); ("c", { 2; 3}) }

您可以假设输入数据源已经按分组键元素排序,例如"a" "b" 和 "c"。

我在那里使用 { } 表示它是一个惰性求值的项目序列。

我已经让它在源序列的 IEnumerator 上运行的两个 while 循环命令式地工作,但这涉及创建参考变量和突变等。我确信有更好的方法来做到这一点,也许与递归或使用 Seq 库中的一些操作,例如扫描还是展开?

【问题讨论】:

标签: recursion f# immutability


【解决方案1】:

如果你想在IEnumerable<'T> 上实现它(让它变得懒惰),那么它必然会有点势在必行,因为用于迭代输入的IEnumerator<'T> 类型是势在必行的。但其余的可以写成使用序列表达式的递归函数。

以下在第一级是惰性的(它惰性地产生每个组),但它不会惰性地产生组的元素(我认为这会有非常微妙的语义):

/// Group adjacent elements of 'input' according to the 
/// keys produced by the key selector function 'f'
let groupAdjacent f (input:seq<_>) = seq {
  use en = input.GetEnumerator()

  // Iterate over elements and keep the key of the current group
  // together with all the elements belonging to the group so far
  let rec loop key acc = seq { 
    if en.MoveNext() then 
      let nkey = f en.Current 
      if nkey = key then 
        // If the key matches, append to the group so far
        yield! loop key (en.Current::acc)
      else 
        // Otherwise, produce the group collected so far & start a new one
        yield List.rev acc
        yield! loop nkey [en.Current]
    else
      // At the end of the sequence, produce the last group
      yield List.rev acc
  }
  // Start with the first key & first value as the accumulator
  if en.MoveNext() then 
    yield! loop (f en.Current) [en.Current] }

不幸的是,这个(非常有用!)函数不包含在标准 F# 库中,所以如果你想对 相邻 元素(而不是使用 Seq.groupBy 列表中的任意元素)进行分组,你必须自己定义它......

【讨论】:

    【解决方案2】:
    let p = [("a", 1); ("a", 2); ("a", 3); ("b", 1); ("c", 2); ("c", 3)]
    let l = p |> Seq.groupBy fst |> Seq.map(fun x -> fst x, snd x |> Seq.map snd) 
    

    【讨论】:

    • 使用Seq.groupBy会忽略“您可以假设输入数据源已经按分组键元素排序”的事实,因此它无法生成组懒惰,这是 OP 所问的......所以这会产生正确的结果,但不是懒惰(这是问题的重点)。
    • 感谢 Tomas 解释为什么这不是我所追求的(但感谢 OP 的建议)
    【解决方案3】:

    F#+ 中有一个通用函数chunkBy 可用于执行此操作:

    #r "FSharpPlus.dll"
    open FSharpPlus
    
    seq [ ("a", 1); ("a", 2); ("a", 3); ("b", 1); ("c", 2); ("c", 3) ]
        |> chunkBy fst 
        |> map (fun (x,y) -> x, map snd y)
    

    它适用于seqarraylist

    seq 的实现与 Tomas 的 groupdAdjacent 几乎相同。

    【讨论】:

    • FsControl 看起来是个好主意。我不知道这个
    【解决方案4】:
    Seq.groupBy fst
    

    会成功的

    【讨论】:

    • 这会给你("a", [ ("a",1), ("a", 2) .. ] )
    • 更重要的是,我认为 Isaac 是在问如何对 相邻 元素进行分组,而不是输入中具有相同键的任何元素...
    • 不完全是,它会给你一个 seq>
    • 另外,groupby 也不必遍历整个集合来获取分组 - 我不想这样做,因为我知道一旦密钥更改,分组就会结束
    猜你喜欢
    • 1970-01-01
    • 2012-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-03
    • 2013-05-25
    相关资源
    最近更新 更多