Lazily grouping a flat sequence in F#

拥有回忆 提交于 2019-12-07 08:12:55

问题


Given a sequence of items as follows:

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

How can I convert this lazily into:

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

You can assume that the input data source is already sorted on the grouping key element e.g. "a" "b" and "c".

I'm using the { } there to indicate that it's a lazily-evaluated sequence of items.

I've gotten it working imperatively with two while loops operating over the IEnumerator of the source sequence, but this involves creating reference variables and mutation etc. etc. I'm sure that there are better ways of doing this, perhaps with Recursion or using some of the operations in the Seq library e.g. scan or unfold?


回答1:


If you want to implement this over IEnumerable<'T> (to make it lazy), then it is necessarily going to be somewhat imperative, because the IEnumerator<'T> type that is used to iterate over the input is imperative. But the rest can be written as a recursive function using sequence expressions.

The following is lazy in the first level (it produces each group lazily), but it does not produce elements of the group lazily (I think that would have pretty subtle semantics):

/// 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] }

Unfortunately, this (pretty useful!) function is not included in the standard F# library, so if you want to group adjacent elements (rather than arbitrary elements in the list using Seq.groupBy), you have to define it yourself...




回答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) 



回答3:


In F#+ there is a generic function chunkBy that can be used to do that:

#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)

And it works with seq, array and list.

The implementation for seq is pretty much the same as the groupdAdjacent from Tomas.




回答4:


Seq.groupBy fst

Will do the trick



来源:https://stackoverflow.com/questions/21753124/lazily-grouping-a-flat-sequence-in-f

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!