golang

Map Copying in Golang (Copy Map)

In Go, maps refer to a data structure that allows us to store the key-value pairs. Maps are extremely versatile and play an important role when building complex and real-world applications. You will often hear maps referred to as dictionaries, associative arrays, or hash maps in other programming languages.

However, when working with maps, we might encounter such instances when we need to make a copy of an existing map.

In this tutorial, we will delve into the world of maps in Go and learn about map copying, what it is, and how to use it.

Golang Maps

Before we dive into map copying, let us start with the fundamentals of maps in Go.

Declaration

In Go, we can declare a map using the “map” keyword followed by the key and value types that are enclosed in square brackets.

The syntax is as follows:

var m map[KeyType]ValueType

An example is as follows:

var m map[string]int

Initialization

Before we can use a map, we need to initialize a map. In Go, we can do this using the “make” function as follows:

m := make(map[string]int)

Adding and Accessing the Elements

Once we initialize a map, we can add the elements to a map by assigning a value to a specific key.

An example is as follows:

m["MySQL"] = 3306
m["PostgreSQL"] = 5094

To access the value associated with a key, use the key within the square brackets as shown in the following example:

fmt.Println(m["PostgreSQL"])

This should return the value associated with the specified key.

Copy a Map in Golang

With the basics out of the way, let us discuss how to copy a map.

What Is Map Copying?

Map copying refers to the technique of creating a duplicate or cloning of an existing map. Although it may seem like a trivial task, map copying plays a crucial role such as:

  1. When you need to make modifications to a map without affecting the original map.
  2. When passing a map to a function by value. By default, Go passes the maps by reference. So, to avoid modifying the original map when used as an argument in a function, it is good to create a copy of the map.
  3. When you implement the concurrency-safe maps.

Now that we understand why map copying is important, let us proceed to explore the different methods for copying maps in Go.

Method 1: Manual Copy

One method of copying a map is creating a new map and manually copying each key-value pair into it.

An example on how to do this is as follows:

package main
import (
    "fmt"
)
func main() {
    databases := map[string]int{
        "MySQL":      3306,
        "PostgreSQL": 5094,
        "MongoDB":    27071,
    }
    databases_copy := make(map[string]int)
    for key, value := range databases {
        databases_copy[key] = value
    }   fmt.Println("Original Map:")
    for key, value := range databases {
        fmt.Printf("%v: %v\n", key, value)
    }
    fmt.Println("\nCopied Map:")
    for key, value := range databases_copy {
        fmt.Printf("%v: %v\n", key, value)
    }
}

In the given example, we initialize an empty map called “databases_copy” and proceed to copy each key-value pair from the original map to the new one.

One thing you will notice with this technique is that it is quite verbose and straightforward. However, it can get repetitive and inefficient when working with a large map.

Method 2: Using a Constructor Function

We can also use a constructor function to create and copy a map. Take a look at the following example:

package main
import (
    "fmt"
)
func main() {
    databases := map[string]int{
        "MySQL":      3306,
        "PostgreSQL": 5094,
        "MongoDB":    27071,
    }
    databases_copy := make(map[string]int, len(databases))
    for key, value := range databases {
        databases_copy[key] = value
    }
    fmt.Println("Original Map:")
    for key, value := range databases {
        fmt.Printf("%s: %d\n", key, value)
    }
    fmt.Println("\nCopied Map:")
    for key, value := range databases_copy {
        fmt.Printf("%s: %d\n", key, value)
    }
}

In this example, we create a copy of the original map using a constructor-like approach. This initializes a new empty map called “databases_copy” with a similar storage capacity as the original.

Finally, we copy each key-value pair from the original to the new map.

Unlike the first approach, this method allows us to specify the capacity of the new map which can be more efficient on large maps.

Method 3: Copy-On-Write (COW)

The third approach is known as Copy-On-Write which is a technique that creates a copy of the map only when the map is modified.

One huge advantage of this approach is memory efficiency especially when dealing with large maps.

To demonstrate the COW approach in action, take a look at the following code:

package main
import (
    "fmt"
    "sync"
)

type COWMap struct {
    m    map[string]int
    lock sync.RWMutex
}

func NewCOWMap(original map[string]int) *COWMap {
    return &COWMap{m: original}
}

func (cm *COWMap) Get(key string) (int, bool) {
    cm.lock.RLock()
    defer cm.lock.RUnlock()
    value, exists := cm.m[key]
    return value, exists
}

func (cm *COWMap) Set(key string, value int) {
    cm.lock.Lock()
    defer cm.lock.Unlock()
    if cm.m == nil {
        cm.m = make(map[string]int)
    }
    cm.m[key] = value
}

func (cm *COWMap) Copy() *COWMap {
    cm.lock.RLock()
    defer cm.lock.RUnlock()
    copy := make(map[string]int, len(cm.m))
    for key, value := range cm.m {
        copy[key] = value
    }
    return &COWMap{m: copy}
}

func main() {
    // Original map
    original := map[string]int{
        "MySQL":      3306,
        "PostgreSQL": 5049,
        "MongoDB":    27071,
    }
    // Create a COWMap using the constructor function
    cowMap := NewCOWMap(original)

    // Get a value from the COWMap
    key := "MySQL"
    value, exists := cowMap.Get(key)
    if exists {
        fmt.Printf("Value for key %v: %v\n", key, value)
    } else {
        fmt.Printf("Key %v does not exist\n", key)
    }

    // Set a new value in the COWMap
    newKey := "PostgreSQL"
    newValue := 5076
    cowMap.Set(newKey, newValue)

    // Create a copy of the COWMap
    copiedMap := cowMap.Copy()

    fmt.Println("\nOriginal COWMap:")
    for key, value := range cowMap.m {
        fmt.Printf("%v: %v\n", key, value)
    }
    fmt.Println("\nCopied COWMap:")
    for key, value := range copiedMap.m {
        fmt.Printf("%v: %v\n", key, value)
    }
}

In this technique, we create a COWMap type that wraps the original map and provides the method for reading and modifying it.

This allows us to only create a copy of the method when the “Copy” method is called, hence after modification.

Conclusion

Maps are crucial in building versatile and high-performant applications. As you can see, map copying comes in handy when dealing with various scenarios. Luckily, we discussed all the methods of performing the map copying in Go in this tutorial.

About the author

John Otieno

My name is John and am a fellow geek like you. I am passionate about all things computers from Hardware, Operating systems to Programming. My dream is to share my knowledge with the world and help out fellow geeks. Follow my content by subscribing to LinuxHint mailing list