I'm liking this solution better. It generates a new sequence from the existing sequence (meaning it doesn't need to traverse the whole sequence to get a result - that's critical if you're doing something like log processing, where you can't call things like Length).
I ended up writing a blog post with more detail about how I got here.
module Seq =
let grouped_by_with_leftover_processing f (f2: 'a list -> list<'a> option) (s: seq<'a>)=
let rec grouped_by_with_acc (f: 'a -> 'a list -> 'a list option * 'a list) acc (ie: IEnumerator<'a>) =
seq {
if ie.MoveNext()
then
let nextValue, leftovers = f ie.Current acc
if nextValue.IsSome then yield nextValue.Value
yield! grouped_by_with_acc f leftovers ie
else
let rems = f2 acc
if rems.IsSome then yield rems.Value
}
seq {
yield! grouped_by_with_acc f [] (s.GetEnumerator())
}
let YieldReversedLeftovers (f: 'a list) =
if f.IsEmpty
then None
else Some (List.rev f)
let grouped_by f s =
grouped_by_with_leftover_processing f YieldReversedLeftovers s
let group_by_length_n n s =
let grouping_function newValue acc =
let newList = newValue :: acc
// If we have the right length, return
// a Some as the first value. That'll
// be yielded by the sequence.
if List.length acc = n - 1
then Some (List.rev newList), []
// If we don't have the right length,
// use None (so nothing will be yielded)
else None, newList
grouped_by grouping_function s
Large sequences aren't an issue:
seq { for i in 1..1000000000 -> i} |> Seq.group_by_length_n 3;;
val it : seq<int list>
= seq [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]; [10; 11; 12]; ...]
>