如何在go中合并两张Map?

tmb3ates  于 2022-12-16  发布在  Go
关注(0)|答案(6)|浏览(1867)

我有一个递归函数,它创建表示文件路径的对象(键是路径,值是关于文件的信息)。它是递归的,因为它只用于处理文件,所以如果遇到目录,该函数将在目录上递归调用。
话虽如此,我想在两个Map上做一个集合并集的等价物(即用来自递归调用的值更新的“main”Map)。除了在一个Map上迭代并将其中的每个键和值赋给另一个Map中的相同内容之外,有没有一种惯用的方法来做这件事?
也就是说:假设a,b的类型为map [string] *SomeObject,并且ab最终会被填充,那么是否有办法用b中的所有值更新a

nsc4cvqm

nsc4cvqm1#

没有内置的方式,也没有标准包中的任何方法来进行这样的合并。
惯用的方法是简单地迭代:

for k, v := range b {
    a[k] = v
}
qxsslcnc

qxsslcnc2#

从Go语言1.18开始,你可以简单地使用golang.org/x/exp/maps包中的Copy函数:

package main

import (
    "fmt"

    "golang.org/x/exp/maps"
)

func main() {
    src := map[string]int{
        "one": 1,
        "two": 2,
    }
    dst := map[string]int{
        "two":   2,
        "three": 3,
    }
    maps.Copy(dst, src)
    fmt.Println("src:", src)
    fmt.Println("dst:", dst)
}

Playground
输出:

src: map[one:1 two:2]
dst: map[one:1 three:3 two:2]

这种方法的一个警告是你的Map的键类型必须是 concrete,也就是说不是接口类型,例如,编译器不允许你把map[io.Reader]int类型的值传递给Copy函数:

package main

import (
    "fmt"
    "io"

    "golang.org/x/exp/maps"
)

func main() {
    var src, dst map[io.Reader]int
    maps.Copy(dst, src)
    fmt.Println("src:", src)
    fmt.Println("dst:", dst)
}

Playground
编译器输出:

go: finding module for package golang.org/x/exp/maps
go: downloading golang.org/x/exp v0.0.0-20220328175248-053ad81199eb
./prog.go:12:11: io.Reader does not implement comparable

Go build failed.

proposal: spec: permit values to have type "comparable"中有关此限制的更多信息。

laximzn5

laximzn53#

从go 1.18开始,由于泛型特性的发布,现在有了union map的泛型函数!
你可以使用像https://github.com/samber/lo这样的包来实现这一点。注意键可以是任何“可比较的”类型,而值可以是任何类型。
示例:

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    map1 := map[string]interface{}{"k1": "v1", "k2": 2}
    map2 := map[string]interface{}{"k2": "v2new", "k3": true}
    map1 = lo.Assign(map1, map2)
    fmt.Printf("%v", map1)
}

结果是:

map[k1:v1 k2:v2new k3:true]
wsxa1bj1

wsxa1bj14#

如果你有两个嵌套的Mapleftright,那么这个函数将递归地把right中的项添加到left中。如果这个键已经在left中,那么我们将递归到这个结构的更深处,并且只尝试把 * 键添加a到left中(例如,从不替换它们)。

type m = map[string]interface{}

// Given two maps, recursively merge right into left, NEVER replacing any key that already exists in left
func mergeKeys(left, right m) m {
    for key, rightVal := range right {
        if leftVal, present := left[key]; present {
            //then we don't want to replace it - recurse
            left[key] = mergeKeys(leftVal.(m), rightVal.(m))
        } else {
            // key not in left so we can just shove it in
            left[key] = rightVal
        }
    }
    return left
}

注意:我不处理值本身不是map[string]interface{}的情况,所以如果你有left["x"] = 1right["x"] = 2,那么上面的代码在尝试leftVal.(m)时会死机。

64jmpszr

64jmpszr5#

Go语言受map类型的限制。我怀疑由于map可以有无限多的类型声明,所以Go语言没有内置函数。所以你必须根据你使用的map类型来构建你自己的Merge函数:

func MergeJSONMaps(maps ...map[string]interface{}) (result map[string]interface{}) {
    result = make(map[string]interface{})
    for _, m := range maps {
        for k, v := range m {
            result[k] = v
        }
    }
    return result
}
py49o6xq

py49o6xq6#

还有一个选择

  • 如果您试图限制第三方依赖项的数量,例如github.com/samber/lo,或者
  • 您对golang.org/x/exp的实验性质感到不舒服(阅读警告),或
  • 您宁愿使用类似于append()的API,而不是来自golang.org/x/expexp.Copy()(append接受任意数量的列表,而Copy()只接受2个)。

但是它需要Go语言1.18+,因为它使用了Go语言的泛型。
将以下内容保存到您的某个模块/包中:

func MergeMaps[M ~map[K]V, K comparable, V any](src ...M) M {
    merged := make(M)
    for _, m := range src {
        for k, v := range m {
            merged[k] = v
        }
    }
    return merged
}

然后,您可以非常类似于append()来使用它:

func main() {
    mergedMaps := MergeMaps(
        map[string]int{"a": 1, "b": 2},
        map[string]int{"b": 3, "c": 4},
        map[string]int{"c": 3, "d": 4},
    )
    fmt.Println(mergedMaps)
}

相关问题