golang / go

The Go programming language
https://go.dev
BSD 3-Clause "New" or "Revised" License
122.7k stars 17.5k forks source link

proposal: maps: add Diff, Intersect, CopyFunc, Filter funcs #68480

Open idsulik opened 1 month ago

idsulik commented 1 month ago

I want to propose adding new functions to the maps package:

// CopyFunc is like Copy, but calls the conflict function to resolve duplicate keys.
func CopyFunc[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](m1 M1, m2 M2, conflict func(K, V, V) V) {
    for k, v := range m2 {
        if v1, ok := m1[k]; ok {
            m1[k] = conflict(k, v1, v)
        } else {
            m1[k] = v
        }
    }
}

// Filter creates a new map containing only the key-value pairs that match a predicate
func Filter[M ~map[K]V, K comparable, V any](m M, keep func(K, V) bool) M {
    m2 := make(M, len(m))
    for k, v := range m {
        if keep(k, v) {
            m2[k] = v
        }
    }
    return m2
}

// Diff returns a new map containing key-value pairs present in the first map but not in the second.
func Diff[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](m1 M1, m2 M2) M1 {
    m3 := make(M1, len(m1))
    for k, v := range m1 {
        if _, ok := m2[k]; !ok {
            m3[k] = v
        }
    }
    return m3
}

// Intersect returns a new map containing key-value pairs present in both maps.
func Intersect[M1 ~map[K]V, M2 ~map[K]V, K comparable, V any](m1 M1, m2 M2) M1 {
    m3 := make(M1, len(m1))
    for k, v := range m1 {
        if _, ok := m2[k]; ok {
            m3[k] = v
        }
    }
    return m3
}

I'll be happy to contribute a PR with tests if this is a good idea.

gabyhelp commented 1 month ago

Related Issues and Documentation

(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)

gophun commented 1 month ago

How is Merge different from Copy?

idsulik commented 1 month ago

How is Merge different from Copy?

You're right, they're the same, I think we can keep MergeFunc, but replace it with CopyFunc so we can resolve key conflicts

jimmyfrasche commented 1 month ago

See the discussion for a generic set type: https://github.com/golang/go/discussions/47331 (possibly to be revived soon once iterators land in the next version, as that's what stalled the original discussion)

I also wrote https://pkg.go.dev/github.com/jimmyfrasche/mapset for doing set like things with regular maps

ianlancetaylor commented 1 month ago

Filter seems like something for #61898. I don't see why we need a maps-specific filter.

idsulik commented 1 month ago

Filter seems like something for https://github.com/golang/go/issues/61898. I don't see why we need a maps-specific filter.

The most important and useful are Diff and Intersect.
Filter will be useful for example, if you create map of files using some external package and you need to exclude some files from the map, or you create some items counter map and need to exclude items with less than 10 count

ianlancetaylor commented 1 month ago

I'm not saying that Filter is not useful. I'm saying that the Filter2 from #61898 is fine, and we don't also need a separate maps.Filter.

isgj commented 1 month ago

The most important and useful are Diff and Intersect.

The first thing that I thought when reading the title was how will you compare the values. I think the documentation should mention that only the keys of the maps are used for comparison. IMHO these are not natural operations of maps.

idsulik commented 1 month ago

I'm not saying that Filter is not useful. I'm saying that the Filter2 from #61898 is fine, and we don't also need a separate maps.Filter.

the filter allows you to filter only based on value, this one will also provide you key

idsulik commented 1 month ago

I think the documentation should mention that only the keys of the maps are used for comparison

We can add value checking as well, if key is not found or values are diff, then append it to result map

ianlancetaylor commented 1 month ago

the filter allows you to filter only based on value, this one will also provide you key

Filter2 filters based on both value and key.

idsulik commented 1 month ago

Filter2 filters based on both value and key.

You're right, I missed it. so I think I can implement Diff and Intersect

isgj commented 1 month ago

We can add value checking as well, if key is not found or values are diff

How will you compare the values? Or will you restrict the type constraint of V to comparable?

idsulik commented 1 month ago

Or will you restrict the type constraint of V to comparable?

I see only this way for this