Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: maps: Add Group and GroupFunc #66948

Open
leb-kuchen opened this issue Apr 21, 2024 · 6 comments
Open

proposal: maps: Add Group and GroupFunc #66948

leb-kuchen opened this issue Apr 21, 2024 · 6 comments
Labels
Milestone

Comments

@leb-kuchen
Copy link

leb-kuchen commented Apr 21, 2024

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
}
@gopherbot gopherbot added this to the Proposal milestone Apr 21, 2024
@adonovan
Copy link
Member

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

@leb-kuchen
Copy link
Author

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

@adonovan
Copy link
Member

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?

@leb-kuchen
Copy link
Author

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

@earthboundkid
Copy link
Contributor

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.

@leb-kuchen
Copy link
Author

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Status: Incoming
Development

No branches or pull requests

4 participants