Proposal Details

I propose to add the functions Group and GroupFunc to the package maps , as I find myself reimplementing these often. They work similarly to Object.GroupBy in JavaScript.

func Group[K comparable, V any](seq iter.Seq2[K, V]) map[K][]V {
    m := make(map[K][]V)
    for k, v := range seq {
        s := m[k]
        s = append(s, v)
        m[k] = s
    }
    return m
}
func GroupFunc[K comparable, V any](seq iter.Seq[V], f func(V) K) map[K][]V {
    m := make(map[K][]V)
    for v := range seq {
        k := f(v)
        s := m[k]
        s = append(s, v)
        m[k] = s
    }
    return m
}

Comment From: adonovan

What's a typical implementation of Seq2 in this scenario? (Often a Seq2 is a map, which doesn't have repeated keys.)

Comment From: bGVia3VjaGVu

type Person struct {
    Name   string
    Age    int
    Gender bool
}
func PersonAll(ps []Person) iter.Seq2[string, Person] {
    return func(yield func(string, Person) bool) {
        for _, p := range ps {
            if !yield(p.Name, p) {
                return
            }
        }
    }
}

Comment From: adonovan

PersonAll is not an obvious iterator, because the first element (K=string) is redundant with the second (V=Person), and is not a primary key. Do we expect this kind of thing to be common?

Comment From: bGVia3VjaGVu

Group is definitely less common than GroupFunc and this example can also be expressed with GroupFunc, but it has its use cases.

Comment From: earthboundkid

Assume we have a people.All() iterator of some kind.

m := make(map[string][]Person)
for p := range people.All() {
    m[p.name] = append(m[p.name], p)
}

Would using a helper function save much here? Sometimes a loop is more clear than a helper function.

Comment From: bGVia3VjaGVu

For simple use cases like this, I think GroupFunc is more idiomatic. If name was a getter, you would have to use a variable. This conveys its meaning more clearly.

GroupFunc(Values(ps), func(p Person) string {
    if p.Age < 20 {
        return "Youngster"
    }
    return "Adult"
})

Object.groupBy was originally intended to be Array.prototype.group and was only changed due web compatibly issues. So there seems to be a demand for this kind of function. The iterator also supports simple use cases like Limit and Filter. Of course you can say that a loop is idiomatic, but this could be said about every higher order function.

Comment From: jimmyfrasche

A generalization of GroupBy is GroupReduce[K comparable, S, T any](sum S, reduce func(S, T) S, seq iter.Seq2[K, T]) map[K]S which makes GroupBy's implementation GroupReduce(nil, append, seq) (you need a wrapper around append to get it to work but I'm being a bit loose here to get the point across).

Comment From: earthboundkid

Cross linking #69123, which is also about a grouping iterator.

Comment From: jimmyfrasche

I'm not sure about GroupFunc. I see the utility but it seems to be fusing two operations. If there were a

func KeyBy[K, V any](key func(V) K, seq iter.Seq[V]) iter.Seq2[K, V]

that yields (key(v), v) then GroupFunc would just be return GroupBy(KeyBy(key, seq)) but then again KeyBy is trivial to write in terms of Map21 https://github.com/golang/go/issues/61898#issuecomment-1683969720