Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merge Maps in Golang

I need to merge multiple maps map1 = [ id: id_1 val: val_1 ], map2 = [ id: id_2 val: val_2 ] and map3 = [id: id_1, val: val_3] such that the result map should be merged on the id values:

result_map = [id: id_1 val: {val_1, val_3}, id: id_2 var: {val_2}} ]

The code I've tried:

var a = make(map[string]interface{})
for _, m := range data {
    for _, n := range data {
        if m["id"] == n["id"] {
            for l, k := range n {
                c[l] = k
            }
        }
    }
}

Is there a way this can be done? Am using Golang 1.7

Thanks

like image 329
user3809560 Avatar asked May 14 '18 06:05

user3809560


1 Answers

Simple merge

Yes, they can be merged, but since in the result map there may be multiple values associated to the same key, the value type should be a slice, such as map[string][]string.

To do the merge, simply range over the maps to be merged, and append each value from the source maps to the slice associated with the same key in the result map.

One thing to look out for is that once you do the append, you have to assign back the result slice to the same key in the result map.

This is a simple implementation:

func merge(ms ...map[string]string) map[string][]string {
    res := map[string][]string{}
    for _, m := range ms {
        for k, v := range m {
            res[k] = append(res[k], v)
        }
    }
    return res
}

This merge() function has a variadic parameter, which means you may pass any number of maps to it.

Note that you don't need to initialize the slices in the target map, as indexing a map with a key that is not yet in it will result in the zero value of its type (which is nil for slices), and you may append to a nil slice, the builtin append() function takes care of (re-)allocations.

Testing it:

m1 := map[string]string{"id_1": "val_1"}
m2 := map[string]string{"id_2": "val_2"}
m3 := map[string]string{"id_1": "val_3"}

res := merge(m1, m2, m3)
fmt.Println(res)

Output (try it on the Go Playground):

map[id_1:[val_1 val_3] id_2:[val_2]]

Avoiding duplicates

Note that the above merge() will not filter out duplicates, meaning if the same "id_1": "val_1" pair is contained in multiple input maps, it will be listed multiple times in the target like "id_1": ["val_1", "val_1", "val_x"]. To filter out such duplicates (to only list it once in the target), we have to check this before doing the append (and if we've encountered it before, skip the append).

This is how it could be done:

func merge(ms ...map[string]string) map[string][]string {
    res := map[string][]string{}
    for _, m := range ms {
    srcMap:
        for k, v := range m {
            // Check if (k,v) was added before:
            for _, v2 := range res[k] {
                if v == v2 {
                    continue srcMap
                }
            }
            res[k] = append(res[k], v)
        }
    }
    return res
}

Testing it:

m1 := map[string]string{"id_1": "val_1"}
m2 := map[string]string{"id_2": "val_2", "id_1": "val_1"}
m3 := map[string]string{"id_1": "val_3"}

res := merge(m1, m2, m3)
fmt.Println(res)

Output (try it on the Go Playground):

map[id_1:[val_1 val_3] id_2:[val_2]]

We can see that "id_1": "val_1" was included both in m1 and m2, yet the value "val_1" is only listed once in in the slice associated with "id_1" key in the target map.

like image 86
icza Avatar answered Oct 22 '22 21:10

icza