Check if a map is subset of another map

2.7k views Asked by At

This question is already answered in many other languages. In golang with simple maps (no nesting) how to find out if a map is subset of another. for example: map[string]string{"a": "b", "e": "f"} is subset of map[string]string{"a": "b", "c": "d", "e": "f"}. I want a generic method. My code:

package main

import (
    "fmt"
    "reflect"
)

func main() {
    a := map[string]string{"a": "b", "c": "d", "e": "f"}
    b := map[string]string{"a": "b", "e": "f"}
    c := IsMapSubset(a, b)
    fmt.Println(c)
}

func IsMapSubset(mapSet interface{}, mapSubset interface{}) bool {

    mapSetValue := reflect.ValueOf(mapSet)
    mapSubsetValue := reflect.ValueOf(mapSubset)

    if mapSetValue.Kind() != reflect.Map || mapSubsetValue.Kind() != reflect.Map {
        return false
    }
    if reflect.TypeOf(mapSetValue) != reflect.TypeOf(mapSubsetValue) {
        return false
    }
    if len(mapSubsetValue.MapKeys()) == 0 {
        return true
    }

    iterMapSubset := mapSubsetValue.MapRange()

    for iterMapSubset.Next() {
        k := iterMapSubset.Key()
        v := iterMapSubset.Value()

        if value := mapSetValue.MapIndex(k); value == nil || v != value { // invalid: value == nil
            return false
        }
    }

    return true
}

When I want to check if subset map key exists in set map, MapIndex returns zero value of type and make it impossible to compare it with anything.

Afterall can I do the same job better?

3

There are 3 answers

3
icza On BEST ANSWER

Value.MapIndex() returns a reflect.Value which is a struct, and nil is not a valid value for structs. You can't compare a struct value to nil.

Value.MapIndex() states that:

It returns the zero Value if key is not found in the map or if v represents a nil map.

So to tell if the key was not found in the map, check if the returned reflect.Value is its zero value. For that you may use the Value.IsValid() method.

You also can't (shouldn't) compare reflect.Value values. Instead obtain their wrapped value using Value.Interface(), and compare those.

if v2 := mapSetValue.MapIndex(k); !v2.IsValid() || v.Interface() != v2.Interface() {
    return false
}

Testing it:

a := map[string]string{"a": "b", "c": "d", "e": "f"}
b := map[string]string{"a": "b", "e": "f"}
fmt.Println(IsMapSubset(a, b))

c := map[string]string{"a": "b", "e": "X"}
fmt.Println(IsMapSubset(a, c))

Output will be (try it on the Go Playground):

true
false
0
jub0bs On

I want a generic method.

Now that Go 1.18 and generics are here, you can write such a generic function; see below and in this Playground. Reflection, which is generally discouraged, is not required to implement this functionality.

package main

import "fmt"

func IsMapSubset[K, V comparable](m, sub map[K]V) bool {
    if len(sub) > len(m) {
        return false
    }
    for k, vsub := range sub {
        if vm, found := m[k]; !found || vm != vsub {
            return false
        }
    }
    return true
}

type MyMap map[string]string

func main() {
    a := map[string]string{"a": "b", "c": "d", "e": "f"}
    b := map[string]string{"a": "b", "e": "f"}
    c := map[string]string{"a": "b", "e": "g"}
    fmt.Println(IsMapSubset(a, b))
    fmt.Println(IsMapSubset(a, c))
    fmt.Println(IsMapSubset(MyMap(a), c))
}

Output:

true
false

The usual caveats about NaN apply, though.

0
Xaqron On

This is the working solution in case anyone needs:

// IsMapSubset returns true if mapSubset is a subset of mapSet otherwise false
func IsMapSubset(mapSet interface{}, mapSubset interface{}) bool {

    mapSetValue := reflect.ValueOf(mapSet)
    mapSubsetValue := reflect.ValueOf(mapSubset)

    if fmt.Sprintf("%T", mapSet) != fmt.Sprintf("%T", mapSubset) {
        return false
    }

    if len(mapSetValue.MapKeys()) < len(mapSubsetValue.MapKeys()) {
        return false
    }

    if len(mapSubsetValue.MapKeys()) == 0 {
        return true
    }

    iterMapSubset := mapSubsetValue.MapRange()

    for iterMapSubset.Next() {
        k := iterMapSubset.Key()
        v := iterMapSubset.Value()

        value := mapSetValue.MapIndex(k)

        if !value.IsValid() || v.Interface() != value.Interface() {
            return false
        }
    }

    return true
}