如何在Go中合并两个地图?

问题描述 投票:0回答:6

我有一个递归函数,可以创建表示文件路径的对象(键是路径,值是有关文件的信息)。它是递归的,因为它仅用于处理文件,因此如果遇到目录,则会在目录上递归调用该函数。

话虽这么说,我想在两个映射上执行相当于集合并集的操作(即用递归调用的值更新“主”映射)。除了迭代一个映射并将其中的每个键、值分配给另一个映射中的相同内容之外,是否有一种惯用的方法可以做到这一点?

也就是说:给定

a,b
的类型为
map [string] *SomeObject
,并且最终填充
a
b
,有没有办法用
a
中的所有值更新
b

dictionary go union
6个回答
218
投票

没有内置方法,标准包中也没有任何方法可以进行此类合并。

理想的方法是简单地迭代:

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

54
投票

更新答案

Go 1.21 开始,您可以简单地使用新的

maps.Copy
功能:

package main

import (
    "fmt"
    "maps"
)

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

(游乐场)

输出:

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

原答案

从 Go 1.18 开始,您可以简单地使用

Copy
包中的
golang.org/x/exp/maps
函数

package main

import (
    "fmt"

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

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

(游乐场)

输出:

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

这种方法的一个警告是,在 Go 版本 1.18.x 到 1.19.x 中,映射的键类型必须是 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)
}

编译器输出:

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.

Go 1.20playground)中取消了此限制。


11
投票

从 go 1.18 开始,由于泛型功能的发布,现在有了联合映射的泛型函数!

您可以使用像 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]

7
投票

如果您有几个嵌套映射,

left
right
,那么此函数将递归地将
right
中的项目添加到
left
中。如果密钥已经在
left
中,那么我们会更深入地递归到结构中,并仅尝试将 add 键添加到
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"] = 1
right["x"] = 2
,那么上面的代码在尝试
leftVal.(m)
时将会出现恐慌。


4
投票

这是另一种选择,

  • 如果您试图限制第三方依赖项的数量,例如
    github.com/samber/lo
    ,OR
  • 您对
    golang.org/x/exp
    的实验性质不满意(请阅读警告),或者
  • 您更愿意使用类似
    append()
    的 API,而不是来自
    exp.Copy()
    golang.org/x/exp
    (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)
}

1
投票

Go 受到地图类型的限制。我怀疑没有内置函数,因为映射可能存在无限数量的类型声明。因此,您必须根据您使用的地图类型构建自己的合并函数:

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
}
© www.soinside.com 2019 - 2024. All rights reserved.