如何深拷贝一个 map 并清空原始 map?

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

How to deep copy a map and then clear the original?

问题

我正在尝试将一个地图(amap)的内容复制到另一个地图(aSuperMap)中,然后清空amap,以便在下一次迭代/循环中可以接受新的值。
问题在于,你不能只清空地图,而不清空超级地图中的引用。
以下是一些伪代码。

for something := range fruits{
        aMap := make(map[string]aStruct)
        aSuperMap := make(map[string]map[string]aStruct)
        
        for x := range something{
        	aMap[x] = aData
        	aSuperMap[y] = aMap
            delete(aMap, x)
    }
//保存aSuperMap
  saveASuperMap(something)

}

我还尝试了一些动态的方法,但显然会抛出错误(无法分配给nil)

aSuperMap[y][x] = aData

问题是如何创建一个关联地图?在PHP中,我只需使用aSuperMap[y][x] = aData。似乎golang没有任何明显的方法。如果我删除delete(aMap, x),它在超级地图中的引用也会被删除。如果我不删除它,超级地图最终会有重复的数据。基本上,在每次循环中,它会得到带有新值和所有旧值的aMap

英文:

I'm trying to copy the contents of a map ( amap ) inside another one (aSuperMap) and then clear amap so that it can take new values on the next iteration/loop.
The issue is that you can't clear the map without to clear its reference in the supermap as well.
Here is some pseudo code.

for something := range fruits{
        aMap := make(map[string]aStruct)
        aSuperMap := make(map[string]map[string]aStruct)
        
        for x := range something{
        	aMap[x] = aData
        	aSuperMap[y] = aMap
            delete(aMap, x)
    }
//save aSuperMap
  saveASuperMap(something)

}

I've also tried some dynamic stuff but obviously it throws an error (cannot assign to nil)

aSuperMap[y][x] = aData

The question is how can I create an associative map ? In PHP I simply use aSuperMap[y][x] = aData. It seems that golang doesn't have any obvious method. If I delete delete(aMap, x) its reference from the super map is deleted as well. If I don't delete it the supermap ends up with duplicate data. Basically on each loop it gets aMap with the new value plus all the old values.

答案1

得分: 162

你不是在复制地图,而是在复制地图的引用。因此,你的delete操作会修改原始地图和超级地图中的值。要复制一个地图,你需要使用一个for循环,像这样:

for k, v := range originalMap {
  newMap[k] = v
}

以下是一个来自现已停用的 SO 文档的示例:

// 创建原始地图
originalMap := make(map[string]int)
originalMap["one"] = 1
originalMap["two"] = 2

// 创建目标地图
targetMap := make(map[string]int)

// 从原始地图复制到目标地图
for key, value := range originalMap {
  targetMap[key] = value
}

摘自Maps - Copy a Map。原作者是JepZ。详细的归属信息可以在贡献者页面上找到。该来源使用CC BY-SA 3.0许可,并可在文档存档中找到。参考主题 ID:732,示例 ID:9834。

英文:

You are not copying the map, but the reference to the map. Your delete thus modifies the values in both your original map and the super map. To copy a map, you have to use a for loop like this:

for k,v := range originalMap {
  newMap[k] = v
}

Here's an example from the now-retired SO documentation:

// Create the original map
originalMap := make(map[string]int)
originalMap["one"] = 1
originalMap["two"] = 2

// Create the target map
targetMap := make(map[string]int)

// Copy from the original map to the target map
for key, value := range originalMap {
  targetMap[key] = value
}

> Excerpted from Maps - Copy a Map. The original author was JepZ. Attribution details can be found on the contributor page. The source is licenced under CC BY-SA 3.0 and may be found in the Documentation archive. Reference topic ID: 732 and example ID: 9834.

答案2

得分: 30

我会使用递归,这样你就可以深拷贝map,避免在更改map元素时出现意外情况。以下是一个在utils.go中的示例代码:

package utils

func CopyMap(m map[string]interface{}) map[string]interface{} {
	cp := make(map[string]interface{})
	for k, v := range m {
		vm, ok := v.(map[string]interface{})
		if ok {
			cp[k] = CopyMap(vm)
		} else {
			cp[k] = v
		}
	}

	return cp
}

以及它的测试文件(即utils_test.go):

package utils

import (
	"testing"

	"github.com/stretchr/testify/require"
)

func TestCopyMap(t *testing.T) {
	m1 := map[string]interface{}{
		"a": "bbb",
		"b": map[string]interface{}{
			"c": 123,
		},
	}

	m2 := CopyMap(m1)

	m1["a"] = "zzz"
	delete(m1, "b")

	require.Equal(t, map[string]interface{}{"a": "zzz"}, m1)
	require.Equal(t, map[string]interface{}{
		"a": "bbb",
		"b": map[string]interface{}{
			"c": 123,
		},
	}, m2)
}

如果你需要map的键是其他类型而不是string,那么很容易进行适应。

英文:

I'd use recursion just in case so you can deep copy the map and avoid bad surprises in case you were to change a map element that is a map itself.

Here's an example in a utils.go:

<!-- language: go -->

package utils

func CopyMap(m map[string]interface{}) map[string]interface{} {
	cp := make(map[string]interface{})
	for k, v := range m {
		vm, ok := v.(map[string]interface{})
		if ok {
			cp[k] = CopyMap(vm)
		} else {
			cp[k] = v
		}
	}

	return cp
}

And its test file (i.e. utils_test.go):

<!-- language: go -->

package utils

import (
	&quot;testing&quot;

	&quot;github.com/stretchr/testify/require&quot;
)

func TestCopyMap(t *testing.T) {
	m1 := map[string]interface{}{
		&quot;a&quot;: &quot;bbb&quot;,
		&quot;b&quot;: map[string]interface{}{
			&quot;c&quot;: 123,
		},
	}

	m2 := CopyMap(m1)

	m1[&quot;a&quot;] = &quot;zzz&quot;
	delete(m1, &quot;b&quot;)

	require.Equal(t, map[string]interface{}{&quot;a&quot;: &quot;zzz&quot;}, m1)
	require.Equal(t, map[string]interface{}{
		&quot;a&quot;: &quot;bbb&quot;,
		&quot;b&quot;: map[string]interface{}{
			&quot;c&quot;: 123,
		},
	}, m2)
}

It should easy enough to adapt if you need the map key to be something else instead of a string.

答案3

得分: 5

根据seong的评论所述:

> 请参阅http://golang.org/doc/effective_go.html#maps。重要的部分实际上是“对底层数据结构的引用”。这也适用于切片。

然而,这里的解决方案似乎都没有提供一个同时涵盖切片的适当深拷贝的解决方案。

我稍微修改了Francesco Casula的答案,以适应映射和切片。


这将同时涵盖复制您的映射本身以及复制任何子映射或切片。这两者都受到相同的“底层数据结构”问题的影响。它还包括一个用于直接在切片上执行相同类型的深拷贝的实用函数。

请记住,结果映射中的切片将是[]interface{}类型的,因此在使用它们时,您需要使用类型断言来检索预期类型的值。

示例用法

映射(将自动深拷贝切片)

copy := CopyableMap(originalMap).DeepCopy()

切片(将自动深拷贝映射)

copy := CopyableSlice(originalSlice).DeepCopy()

源代码

deepcopy.go

package utils

type CopyableMap   map[string]interface{}
type CopyableSlice []interface{}

// DeepCopy will create a deep copy of this map. The depth of this
// copy is all inclusive. Both maps and slices will be considered when
// making the copy.
func (m CopyableMap) DeepCopy() map[string]interface{} {
    result := map[string]interface{}{}

    for k,v := range m {
        // Handle maps
        mapvalue,isMap := v.(map[string]interface{})
        if isMap {
            result[k] = CopyableMap(mapvalue).DeepCopy()
            continue
        }

        // Handle slices
        slicevalue,isSlice := v.([]interface{})
        if isSlice {
            result[k] = CopyableSlice(slicevalue).DeepCopy()
            continue
        }

        result[k] = v
    }

    return result
}

// DeepCopy will create a deep copy of this slice. The depth of this
// copy is all inclusive. Both maps and slices will be considered when
// making the copy.
func (s CopyableSlice) DeepCopy() []interface{} {
    result := []interface{}{}

    for _,v := range s {
        // Handle maps
        mapvalue,isMap := v.(map[string]interface{})
        if isMap {
            result = append(result, CopyableMap(mapvalue).DeepCopy())
            continue
        }

        // Handle slices
        slicevalue,isSlice := v.([]interface{})
        if isSlice {
            result = append(result, CopyableSlice(slicevalue).DeepCopy())
            continue
        }

        result = append(result, v)
    }

    return result
}

测试文件(deepcopy_tests.go

package utils

import (
    "testing";

    "github.com/stretchr/testify/require";
)

func TestCopyMap(t *testing.T) {
    m1 := map[string]interface{}{
        "a": "bbb",
        "b": map[string]interface{}{
            "c": 123,
        },
        "c": []interface{} {
            "d", "e", map[string]interface{} {
                "f": "g",
            },
        },
    }

    m2 := CopyableMap(m1).DeepCopy()

    m1["a"] = "zzz"
    delete(m1, "b")
    m1["c"].([]interface{})[1] = "x"
    m1["c"].([]interface{})[2].(map[string]interface{})["f"] = "h"

    require.Equal(t, map[string]interface{}{
        "a": "zzz", 
        "c": []interface{} {
            "d", "x", map[string]interface{} {
                "f": "h",
            },
        },
    }, m1)
    require.Equal(t, map[string]interface{}{
        "a": "bbb",
        "b": map[string]interface{}{
            "c": 123,
        },
        "c": []interface{} {
            "d", "e", map[string]interface{} {
                "f": "g",
            },
        },
    }, m2)
}
英文:

As stated in seong's comment:

> Also see http://golang.org/doc/effective_go.html#maps. The important part is really the "reference to underlying data structure". This also applies to slices.

However, none of the solutions here seem to offer a solution for a proper deep copy that also covers slices.

I've slightly altered Francesco Casula's answer to accommodate for both maps and slices.


This should cover both copying your map itself, as well as copying any child maps or slices. Both of which are affected by the same "underlying data structure" issue. It also includes a utility function for performing the same type of Deep Copy on a slice directly.

Keep in mind that the slices in the resulting map will be of type []interface{}, so when using them, you will need to use type assertion to retrieve the value in the expected type.

Example Usage

Maps (Will automatically deep-copy slices as well)

copy := CopyableMap(originalMap).DeepCopy()

Slices (Will automatically deep-copy maps as well)

copy := CopyableSlice(originalSlice).DeepCopy()

Source

deepcopy.go

package utils

type CopyableMap   map[string]interface{}
type CopyableSlice []interface{}

// DeepCopy will create a deep copy of this map. The depth of this
// copy is all inclusive. Both maps and slices will be considered when
// making the copy.
func (m CopyableMap) DeepCopy() map[string]interface{} {
    result := map[string]interface{}{}

    for k,v := range m {
        // Handle maps
        mapvalue,isMap := v.(map[string]interface{})
        if isMap {
            result[k] = CopyableMap(mapvalue).DeepCopy()
            continue
        }

        // Handle slices
        slicevalue,isSlice := v.([]interface{})
        if isSlice {
            result[k] = CopyableSlice(slicevalue).DeepCopy()
            continue
        }

        result[k] = v
    }

    return result
}

// DeepCopy will create a deep copy of this slice. The depth of this
// copy is all inclusive. Both maps and slices will be considered when
// making the copy.
func (s CopyableSlice) DeepCopy() []interface{} {
    result := []interface{}{}

    for _,v := range s {
        // Handle maps
        mapvalue,isMap := v.(map[string]interface{})
        if isMap {
            result = append(result, CopyableMap(mapvalue).DeepCopy())
            continue
        }

        // Handle slices
        slicevalue,isSlice := v.([]interface{})
        if isSlice {
            result = append(result, CopyableSlice(slicevalue).DeepCopy())
            continue
        }

        result = append(result, v)
    }

    return result
}

Test File (deepcopy_tests.go)

package utils

import (
    &quot;testing&quot;

    &quot;github.com/stretchr/testify/require&quot;
)

func TestCopyMap(t *testing.T) {
    m1 := map[string]interface{}{
        &quot;a&quot;: &quot;bbb&quot;,
        &quot;b&quot;: map[string]interface{}{
            &quot;c&quot;: 123,
        },
        &quot;c&quot;: []interface{} {
            &quot;d&quot;, &quot;e&quot;, map[string]interface{} {
                &quot;f&quot;: &quot;g&quot;,
            },
        },
    }

    m2 := CopyableMap(m1).DeepCopy()

    m1[&quot;a&quot;] = &quot;zzz&quot;
    delete(m1, &quot;b&quot;)
    m1[&quot;c&quot;].([]interface{})[1] = &quot;x&quot;
    m1[&quot;c&quot;].([]interface{})[2].(map[string]interface{})[&quot;f&quot;] = &quot;h&quot;

    require.Equal(t, map[string]interface{}{
        &quot;a&quot;: &quot;zzz&quot;, 
        &quot;c&quot;: []interface{} {
            &quot;d&quot;, &quot;x&quot;, map[string]interface{} {
                &quot;f&quot;: &quot;h&quot;,
            },
        },
    }, m1)
    require.Equal(t, map[string]interface{}{
        &quot;a&quot;: &quot;bbb&quot;,
        &quot;b&quot;: map[string]interface{}{
            &quot;c&quot;: 123,
        },
        &quot;c&quot;: []interface{} {
            &quot;d&quot;, &quot;e&quot;, map[string]interface{} {
                &quot;f&quot;: &quot;g&quot;,
            },
        },
    }, m2)
}

答案4

得分: 4

你可以通过encoding/gob来进行往返转换:

package main

import (
   "bytes"
   "encoding/gob"
)

func copyMap(in, out interface{}) {
   buf := new(bytes.Buffer)
   gob.NewEncoder(buf).Encode(in)
   gob.NewDecoder(buf).Decode(out)
}

func main() {
   a := map[string]int{"month": 12, "day": 31}
   b := make(map[string]int)
   copyMap(a, &b)
}

这个方法也适用于其他包。以下是使用net/url的示例:

package main

import (
   "fmt"
   "net/url"
)

func copyVal(v url.Values) url.Values {
   u := url.URL{
      RawQuery: v.Encode(),
   }
   return u.Query()
}

func main() {
   a := url.Values{
      "west": {"left"}, "east": {"right"},
   }
   b := copyVal(a)
   fmt.Println(b)
}
英文:

You can roundtrip it through encoding/gob:

package main
import (
&quot;bytes&quot;
&quot;encoding/gob&quot;
)
func copyMap(in, out interface{}) {
buf := new(bytes.Buffer)
gob.NewEncoder(buf).Encode(in)
gob.NewDecoder(buf).Decode(out)
}
func main() {
a := map[string]int{&quot;month&quot;: 12, &quot;day&quot;: 31}
b := make(map[string]int)
copyMap(a, &amp;b)
}

This works with other packages too. Here is an example with net/url:

package main
import (
&quot;fmt&quot;
&quot;net/url&quot;
)
func copyVal(v url.Values) url.Values {
u := url.URL{
RawQuery: v.Encode(),
}
return u.Query()
}
func main() {
a := url.Values{
&quot;west&quot;: {&quot;left&quot;}, &quot;east&quot;: {&quot;right&quot;},
}
b := copyVal(a)
fmt.Println(b)
}

答案5

得分: 1

你必须手动将每个键/值对复制到一个新的map中。这是一个循环,每次想要深拷贝map时,人们都必须重新编写这个循环。

你可以通过安装maps包中的mapper来自动生成此功能,使用以下命令:

go get -u github.com/drgrib/maps/cmd/mapper

然后运行以下命令:

mapper -types string:aStruct

这将生成名为map_float_astruct.go的文件,其中包含不仅是用于(深度)复制mapCopy函数,还包括其他“缺失”的map函数,如ContainsKeyContainsValueGetKeysGetValues

func ContainsKeyStringAStruct(m map[string]aStruct, k string) bool {
	_, ok := m[k]
	return ok
}

func ContainsValueStringAStruct(m map[string]aStruct, v aStruct) bool {
	for _, mValue := range m {
		if mValue == v {
			return true
		}
	}

	return false
}

func GetKeysStringAStruct(m map[string]aStruct) []string {
	keys := []string{}

	for k, _ := range m {
		keys = append(keys, k)
	}

	return keys
}

func GetValuesStringAStruct(m map[string]aStruct) []aStruct {
	values := []aStruct{}

	for _, v := range m {
		values = append(values, v)
	}

	return values
}

func CopyStringAStruct(m map[string]aStruct) map[string]aStruct {
	copyMap := map[string]aStruct{}

	for k, v := range m {
		copyMap[k] = v
	}

	return copyMap
}

完整披露:我是这个工具的创建者。我创建了它及其所包含的包,因为我发现自己经常为不同类型组合的Go map重写这些算法。

英文:

You have to manually copy each key/value pair to a new map. This is a loop that people have to reprogram any time they want a deep copy of a map.

You can automatically generate the function for this by installing mapper from the maps package using

go get -u github.com/drgrib/maps/cmd/mapper

and running

mapper -types string:aStruct

which will generate the file map_float_astruct.go containing not only a (deep) Copy for your map but also other "missing" map functions ContainsKey, ContainsValue, GetKeys, and GetValues:

func ContainsKeyStringAStruct(m map[string]aStruct, k string) bool {
	_, ok := m[k]
	return ok
}

func ContainsValueStringAStruct(m map[string]aStruct, v aStruct) bool {
	for _, mValue := range m {
		if mValue == v {
			return true
		}
	}

	return false
}

func GetKeysStringAStruct(m map[string]aStruct) []string {
	keys := []string{}

	for k, _ := range m {
		keys = append(keys, k)
	}

	return keys
}

func GetValuesStringAStruct(m map[string]aStruct) []aStruct {
	values := []aStruct{}

	for _, v := range m {
		values = append(values, v)
	}

	return values
}

func CopyStringAStruct(m map[string]aStruct) map[string]aStruct {
	copyMap := map[string]aStruct{}

	for k, v := range m {
		copyMap[k] = v
	}

	return copyMap
}

Full disclosure: I am the creator of this tool. I created it and its containing package because I found myself constantly rewriting these algorithms for the Go map for different type combinations.

答案6

得分: 0

个别元素复制,在一个简单的例子中似乎对我有效。

maps := map[string]int {
    "alice":12,
    "jimmy":15,
}

maps2 := make(map[string]int)
for k2,v2 := range maps {
    maps2[k2] = v2
}

maps2["miki"]=rand.Intn(100)

fmt.Println("maps: ",maps," vs. ","maps2: ",maps2)

请注意,这是一个Go语言的代码示例。它创建了一个名为maps的映射,其中包含了一些键值对。然后,它使用range循环将maps中的键值对复制到另一个名为maps2的映射中。最后,它向maps2中添加了一个新的键值对,并打印出mapsmaps2的内容。

英文:

Individual element copy, it seems to work for me with just a simple example.

maps := map[string]int {
&quot;alice&quot;:12,
&quot;jimmy&quot;:15,
}
maps2 := make(map[string]int)
for k2,v2 := range maps {
maps2[k2] = v2
}
maps2[&quot;miki&quot;]=rand.Intn(100)
fmt.Println(&quot;maps: &quot;,maps,&quot; vs. &quot;,&quot;maps2: &quot;,maps2)

huangapple
  • 本文由 发表于 2014年4月14日 18:28:38
  • 转载请务必保留本文链接:https://go.coder-hub.com/23057785.html
匿名

发表评论

匿名网友

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

确定