How to copy a map?

后端 未结 5 724
傲寒
傲寒 2020-12-05 01:36

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

相关标签:
5条回答
  • 2020-12-05 01:55

    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

    copy := CopyableMap(originalMap).DeepCopy()
    

    Source File (util.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 (util_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)
    }
    
    0 讨论(0)
  • 2020-12-05 02:05

    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:

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

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

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

    0 讨论(0)
  • 2020-12-05 02:05

    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.

    0 讨论(0)
  • 2020-12-05 02:06

    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.

    0 讨论(0)
  • 2020-12-05 02:12

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

    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)
    
    0 讨论(0)
提交回复
热议问题