Maps in Golang
Maps are a built-in data structure in Go that associate keys with values. They’re similar to dictionaries in other languages like Python or hash tables in Java. Maps are unordered collections, and they’re extremely useful for storing and retrieving data quickly.
Creating Maps
You can create maps using the make function or with a map literal.
Using make()
var m map[keyType]valueType
m = make(map[keyType]valueType)package main
import "fmt"
func main() {
// Create an empty map
ages := make(map[string]int)
// Add some key-value pairs
ages["Alice"] = 25
ages["Bob"] = 30
ages["Charlie"] = 35
fmt.Println("Ages:", ages)
}Using Map Literals
m := map[keyType]valueType{
key1: value1,
key2: value2,
...
}package main
import "fmt"
func main() {
// Create a map with initial values
colors := map[string]string{
"red": "#FF0000",
"green": "#00FF00",
"blue": "#0000FF",
}
fmt.Println("Colors:", colors)
}Accessing Map Values
You can access values using square brackets with the key.
package main
import "fmt"
func main() {
capitals := map[string]string{
"France": "Paris",
"Germany": "Berlin",
"Italy": "Rome",
}
fmt.Println("Capital of France:", capitals["France"])
fmt.Println("Capital of Spain:", capitals["Spain"]) // Will be empty string
}Checking if a Key Exists
Maps return the zero value for non-existent keys. To check if a key exists, use the comma ok idiom.
package main
import "fmt"
func main() {
scores := map[string]int{
"Alice": 95,
"Bob": 87,
}
// Check if key exists
if score, exists := scores["Alice"]; exists {
fmt.Println("Alice's score:", score)
} else {
fmt.Println("Alice not found")
}
// Check for non-existent key
if score, exists := scores["Charlie"]; exists {
fmt.Println("Charlie's score:", score)
} else {
fmt.Println("Charlie not found")
}
}Modifying Maps
Adding and Updating Values
package main
import "fmt"
func main() {
inventory := make(map[string]int)
// Add items
inventory["apples"] = 10
inventory["bananas"] = 5
fmt.Println("Initial inventory:", inventory)
// Update existing item
inventory["apples"] = 15
// Add new item
inventory["oranges"] = 8
fmt.Println("Updated inventory:", inventory)
}Deleting Entries
Use the delete function to remove entries from a map.
delete(map, key)package main
import "fmt"
func main() {
products := map[string]float64{
"laptop": 999.99,
"mouse": 25.50,
"keyboard": 75.00,
}
fmt.Println("Before deletion:", products)
// Delete an item
delete(products, "mouse")
fmt.Println("After deletion:", products)
// Try to delete non-existent key (safe operation)
delete(products, "monitor")
fmt.Println("After attempting to delete non-existent key:", products)
}Iterating Over Maps
Use the range keyword to iterate over maps. Note that iteration order is not guaranteed.
package main
import "fmt"
func main() {
studentGrades := map[string]int{
"Alice": 95,
"Bob": 87,
"Charlie": 92,
"Diana": 88,
}
fmt.Println("Student grades:")
for name, grade := range studentGrades {
fmt.Printf("%s: %d\n", name, grade)
}
// Iterate only keys
fmt.Println("\nStudent names:")
for name := range studentGrades {
fmt.Println(name)
}
// Iterate only values
fmt.Println("\nGrades:")
for _, grade := range studentGrades {
fmt.Println(grade)
}
}Map Length
Use the len function to get the number of key-value pairs in a map.
package main
import "fmt"
func main() {
data := map[string]string{
"name": "John Doe",
"email": "[email protected]",
"phone": "123-456-7890",
"address": "123 Main St",
}
fmt.Printf("Number of fields: %d\n", len(data))
}Nested Maps
You can create maps of maps for more complex data structures.
package main
import "fmt"
func main() {
// Map of maps
students := map[string]map[string]interface{}{
"alice": {
"age": 20,
"major": "Computer Science",
"gpa": 3.8,
},
"bob": {
"age": 22,
"major": "Mathematics",
"gpa": 3.5,
},
}
fmt.Println("Alice's age:", students["alice"]["age"])
fmt.Println("Bob's major:", students["bob"]["major"])
// Add a new field
students["alice"]["graduation_year"] = 2024
fmt.Println("Updated Alice:", students["alice"])
}Map Operations and Best Practices
Copying Maps
Maps are reference types. To copy a map, you need to create a new one and copy elements.
package main
import "fmt"
func main() {
original := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
// Wrong way - this creates a reference
copy1 := original
copy1["a"] = 999
fmt.Println("Original after modifying copy1:", original)
// Right way - create new map and copy elements
copy2 := make(map[string]int)
for k, v := range original {
copy2[k] = v
}
copy2["a"] = 777
fmt.Println("Original after modifying copy2:", original)
fmt.Println("Copy2:", copy2)
}Maps as Function Parameters
Maps are passed by reference, so modifications in functions affect the original map.
package main
import "fmt"
func updateMap(m map[string]int, key string, value int) {
m[key] = value
}
func main() {
data := map[string]int{"x": 10, "y": 20}
fmt.Println("Before:", data)
updateMap(data, "z", 30)
fmt.Println("After:", data)
}Thread Safety
Maps are not thread-safe. For concurrent access, use sync.Mutex or sync.RWMutex.
package main
import (
"fmt"
"sync"
)
type SafeMap struct {
mu sync.RWMutex
data map[string]int
}
func (sm *SafeMap) Set(key string, value int) {
sm.mu.Lock()
defer sm.mu.Unlock()
sm.data[key] = value
}
func (sm *SafeMap) Get(key string) (int, bool) {
sm.mu.RLock()
defer sm.mu.RUnlock()
value, exists := sm.data[key]
return value, exists
}
func main() {
sm := &SafeMap{
data: make(map[string]int),
}
sm.Set("counter", 0)
// This would be safe to call from multiple goroutines
if value, exists := sm.Get("counter"); exists {
fmt.Println("Counter:", value)
}
}Common Use Cases
Counting Frequencies
package main
import (
"fmt"
"strings"
)
func countWords(text string) map[string]int {
words := strings.Fields(text)
counts := make(map[string]int)
for _, word := range words {
// Convert to lowercase for case-insensitive counting
word = strings.ToLower(word)
counts[word]++
}
return counts
}
func main() {
text := "The quick brown fox jumps over the lazy dog"
wordCounts := countWords(text)
fmt.Println("Word frequencies:")
for word, count := range wordCounts {
fmt.Printf("%s: %d\n", word, count)
}
}Grouping Data
package main
import "fmt"
type Person struct {
Name string
City string
}
func groupByCity(people []Person) map[string][]Person {
groups := make(map[string][]Person)
for _, person := range people {
groups[person.City] = append(groups[person.City], person)
}
return groups
}
func main() {
people := []Person{
{"Alice", "New York"},
{"Bob", "Los Angeles"},
{"Charlie", "New York"},
{"Diana", "Chicago"},
{"Eve", "Los Angeles"},
}
grouped := groupByCity(people)
for city, residents := range grouped {
fmt.Printf("%s residents:\n", city)
for _, person := range residents {
fmt.Printf(" - %s\n", person.Name)
}
}
}Caching
package main
import (
"fmt"
"time"
)
type Cache struct {
data map[string]interface{}
}
func NewCache() *Cache {
return &Cache{
data: make(map[string]interface{}),
}
}
func (c *Cache) Get(key string) (interface{}, bool) {
value, exists := c.data[key]
return value, exists
}
func (c *Cache) Set(key string, value interface{}) {
c.data[key] = value
}
// Simulate expensive computation
func expensiveOperation(n int) int {
time.Sleep(100 * time.Millisecond) // Simulate delay
return n * n
}
func main() {
cache := NewCache()
// First call - compute and cache
start := time.Now()
result1 := expensiveOperation(5)
cache.Set("5", result1)
fmt.Printf("First call took %v, result: %d\n", time.Since(start), result1)
// Second call - use cache
start = time.Now()
if cached, exists := cache.Get("5"); exists {
result2 := cached.(int)
fmt.Printf("Cached call took %v, result: %d\n", time.Since(start), result2)
}
}External Resources:
Related Tutorials: