如何在Go语言中合并两个地图(maps)?

huangapple go评论84阅读模式
英文:

How can I merge two maps in go?

问题

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

话虽如此,我想对两个映射执行等同于集合并的操作(即将递归调用的值更新到“主”映射中)。除了迭代一个映射并将其中的每个键值分配给另一个映射中的相同内容之外,是否有一种惯用的方法来实现这一点?

也就是说:给定 a,b 的类型为 map [string] *SomeObject,并且 ab 最终被填充,是否有任何方法可以将 b 中的所有值更新到 a 中?

英文:

I have a recursive function that creates objects representing file paths (the keys are paths and the values are info about the file). It's recursive as it's only meant to handle files, so if a directory is encountered, the function is recursively called on the directory.

All that being said, I'd like to do the equivalent of a set union on two maps (i.e. the "main" map updated with the values from the recursive call). Is there an idiomatic way to do this aside from iterating over one map and assigning each key, value in it to the same thing in the other map?

That is: given a,b are of type map [string] *SomeObject, and a and b are eventually populated, is there any way to update a with all the values in b?

答案1

得分: 214

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

惯用的方法是简单地进行迭代:

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

There is no built in way, nor any method in the standard packages to do such a merge.

The idomatic way is to simply iterate:

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

答案2

得分: 38

更新的答案

自从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":   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]

原始答案

自从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]

这种方法的一个注意事项是,在Go版本1.18.x到1.19.x中,您的映射的键类型必须是_具体_的,即不能是接口类型。例如,编译器不允许您将类型为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.

这个限制在Go 1.20中被解除了

(Playground)

英文:

Updated answer

Since Go 1.21 (soon to be released), you can simply use the new maps.Copy function:

package main

import (
	"fmt"
	"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)

Output:

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

Original answer

Since Go 1.18, you can simply use the Copy function from the golang.org/x/exp/maps package:

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)

Output:

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

One caveat of this approach is that, in Go versions 1.18.x to 1.19.x, your map's key type must be concrete, i.e. not an interface type. For instance, the compiler won't allow you to pass values of type map[io.Reader]int to the Copy function:

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)

Compiler output:

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.

This limitation was lifted in Go 1.20.

(Playground)

答案3

得分: 10

从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]
英文:

Starting at go 1.18, thanks to the release of the Generics feature, there are now generic functions that union maps!

You can use a package like https://github.com/samber/lo in order to do so.
Note that the key can be of any "comparable" type, while the value can be of any type.

Example:

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)
}

The result is:

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

答案4

得分: 7

如果你有一对嵌套的映射leftright,那么这个函数将递归地将right中的项添加到left中。如果键已经存在于left中,那么我们会进一步递归到结构中,并尝试仅向left添加键(例如,不替换它们)。


type m = map[string]interface{}

// 给定两个映射,递归地将right合并到left中,永远不替换left中已经存在的任何键
func mergeKeys(left, right m) m {
	for key, rightVal := range right {
		if leftVal, present := left[key]; present {
			// 那么我们不想替换它-递归
			left[key] = mergeKeys(leftVal.(m), rightVal.(m))
		} else {
			// 键不在left中,所以我们可以直接将其放入
			left[key] = rightVal
		}
	}
	return left
}

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

英文:

If you have a couple of nested maps, left and right, then this function will recursively add the items from right into left. If the key is already in left then we recurse deeper into the structure and attempt only add keys to left (e.g. never replace them).


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
}

NOTE: I do not handle the case in which the value is not itself a map[string]interface{}. So if you have left["x"] = 1 and right["x"] = 2 then the above code will panic when attempting leftVal.(m).

答案5

得分: 3

这是另一种选择,

  • 如果你想限制第三方依赖的数量,比如 github.com/samber/lo
  • 或者你对 golang.org/x/exp 的实验性质感到不舒服(请阅读警告),
  • 或者你更喜欢使用类似 append() 的 API 而不是来自 golang.org/x/expexp.Copy()append() 可以接受任意数量的列表,而 Copy() 只能接受两个)。

但是它需要 Go 1.18+,因为它使用了泛型。

将以下代码保存在你的模块/包中:

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)
}
英文:

Here is another option,

  • in case you are trying to limit the number of third-party dependencies such github.com/samber/lo, OR
  • you are not comfortable with the experimental nature of golang.org/x/exp (read the warning), OR
  • you would rather the convenience of an append()-like API instead of exp.Copy() from golang.org/x/exp (append accepts any number of lists, whereas Copy() accepts only 2).

However it requires Go 1.18+ as it uses go generics.

Save the following in one of your modules/packages:

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
}

Then you can use it very similarly to 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)
}

答案6

得分: 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
}
英文:

Go is limited by what type of map it is. I'd suspect that there isn't built in functions because of the infinite number of type declarations that could exist for a map. So you have to build your own Merge functions depending on what type of map you are using:

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
}

huangapple
  • 本文由 发表于 2014年3月25日 06:17:51
  • 转载请务必保留本文链接:https://go.coder-hub.com/22621754.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定