懒惰地在F#中对平坦序列进行分组

时间:2014-02-13 11:38:22

标签: recursion f# immutability

给出一系列项目如下:

[ ("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库中的一些操作,例如扫描还是展开?

4 个答案:

答案 0 :(得分:5)

如果你想在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相邻的元素(而不是列表中的任意元素)进行分组),你必须自己定义......

答案 1 :(得分:4)

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) 

答案 2 :(得分:2)

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几乎相同。

答案 3 :(得分:1)

Seq.groupBy fst

会做的伎俩