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