Generic map operations and utilities for Go
The mapz package provides 30+ utility functions for working with maps. All functions are generic and immutable (return new maps rather than modifying inputs, unless explicitly marked).
By Category:
- Access & Extraction - Keys, Values, Lookup
- Comparison - Equal, EqualBy
- Cloning & Copying - Clone, Copy, Clear
- Combination - Merge, MergeWith
- Filtering - Filter, Reject, By Keys/Values
- Transformation - MapKeys, MapValues, Remap, Invert
- Update Operations - Update, GetOrSet
- Set Operations - Difference, Intersection
- Conversion - To/From Slice, Entries
go get github.com/modfin/henry/mapzimport "github.com/modfin/henry/mapz"
scores := map[string]int{"Alice": 85, "Bob": 92, "Charlie": 78}
// Get all names
names := mapz.Keys(scores)
// names = []string{"Alice", "Bob", "Charlie"} (order not guaranteed)
// Filter high scores
highScorers := mapz.Filter(scores, func(name string, score int) bool {
return score >= 80
})
// highScorers = {"Alice": 85, "Bob": 92}Extract all keys as a slice.
scores := map[string]int{"Alice": 85, "Bob": 92}
names := mapz.Keys(scores)
// names = []string{"Alice", "Bob"} (order not guaranteed)Extract all values as a slice.
scores := map[string]int{"Alice": 85, "Bob": 92}
scoresList := mapz.Values(scores)
// scoresList = []int{85, 92} (order not guaranteed)Get value or fallback.
scores := map[string]int{"Alice": 85}
alice := mapz.ValueOr(scores, "Alice", 0) // alice = 85
charlie := mapz.ValueOr(scores, "Charlie", 0) // charlie = 0 (fallback)Check if two maps are equal (keys and values match).
m1 := map[string]int{"a": 1, "b": 2}
m2 := map[string]int{"a": 1, "b": 2}
m3 := map[string]int{"a": 1, "b": 3}
mapz.Equal(m1, m2) // true
mapz.Equal(m1, m3) // falseCheck equality with custom comparison function.
m1 := map[string]int{"a": 1, "b": 2}
m2 := map[string]int{"a": 2, "b": 4}
// Check if values are all even
mapz.EqualBy(m1, m2, func(v1, v2 int) bool {
return v1%2 == v2%2
}) // true (both 1→2 and 2→4 are odd→even)Create a shallow copy of a map.
original := map[string]int{"a": 1, "b": 2}
copy := mapz.Clone(original)
// copy = {"a": 1, "b": 2}
// Modifying copy doesn't affect originalCopy all entries from source to destination.
dst := map[string]int{"a": 1}
src := map[string]int{"b": 2, "c": 3}
mapz.Copy(dst, src)
// dst = {"a": 1, "b": 2, "c": 3}Remove all entries from a map (mutates).
m := map[string]int{"a": 1, "b": 2}
mapz.Clear(m)
// m = {}Merge multiple maps into one.
m1 := map[string]int{"a": 1, "b": 2}
m2 := map[string]int{"b": 20, "c": 3} // Note: b has different value
m3 := map[string]int{"d": 4}
merged := mapz.Merge(m1, m2, m3)
// merged = {"a": 1, "b": 20, "c": 3, "d": 4}
// Later maps overwrite earlier ones for duplicate keysMerge with custom conflict resolution.
m1 := map[string]int{"a": 1, "b": 2}
m2 := map[string]int{"b": 3, "c": 4}
// Sum values for duplicate keys
merged := mapz.MergeWith([]map[string]int{m1, m2}, func(vals ...int) int {
sum := 0
for _, v := range vals {
sum += v
}
return sum
})
// merged = {"a": 1, "b": 5, "c": 4} (2 + 3 = 5)Keep entries matching predicate.
scores := map[string]int{"Alice": 85, "Bob": 72, "Charlie": 90}
highScorers := mapz.Filter(scores, func(name string, score int) bool {
return score >= 80
})
// highScorers = {"Alice": 85, "Charlie": 90}Keep only specified keys.
scores := map[string]int{"Alice": 85, "Bob": 72, "Charlie": 90}
selected := mapz.FilterByKeys(scores, []string{"Alice", "Charlie", "Dave"})
// selected = {"Alice": 85, "Charlie": 90}
// Dave is ignored (doesn't exist in original)Keep only entries with specified values.
scores := map[string]int{"Alice": 85, "Bob": 90, "Charlie": 90}
in90s := mapz.FilterByValues(scores, []int{90})
// in90s = {"Bob": 90, "Charlie": 90}Remove entries matching predicate (inverse of Filter).
scores := map[string]int{"Alice": 85, "Bob": 72, "Charlie": 90}
passing := mapz.Reject(scores, func(name string, score int) bool {
return score < 80
})
// passing = {"Alice": 85, "Charlie": 90}Remove specified keys.
scores := map[string]int{"Alice": 85, "Bob": 72, "Charlie": 90}
withoutBob := mapz.RejectByKeys(scores, []string{"Bob"})
// withoutBob = {"Alice": 85, "Charlie": 90}Remove entries with specified values.
scores := map[string]int{"Alice": 85, "Bob": 72, "Charlie": 72}
without72s := mapz.RejectByValues(scores, []int{72})
// without72s = {"Alice": 85}Remove entries where predicate is true (mutates).
scores := map[string]int{"Alice": 85, "Bob": 72, "Charlie": 90}
mapz.Delete(scores, func(name string, score int) bool {
return score < 80
})
// scores = {"Alice": 85, "Charlie": 90}Remove entries by keys (mutates).
scores := map[string]int{"Alice": 85, "Bob": 72, "Charlie": 90}
mapz.DeleteKeys(scores, "Bob")
// scores = {"Alice": 85, "Charlie": 90}Remove entries by values (mutates).
scores := map[string]int{"Alice": 85, "Bob": 72, "Charlie": 72}
mapz.DeleteValues(scores, 72)
// scores = {"Alice": 85}Transform all keys.
scores := map[string]int{"alice": 85, "bob": 92}
upperKeys := mapz.MapKeys(scores, strings.ToUpper)
// upperKeys = {"ALICE": 85, "BOB": 92}Transform all values.
scores := map[string]int{"Alice": 85, "Bob": 92}
doubled := mapz.MapValues(scores, func(score int) int {
return score * 2
})
// doubled = {"Alice": 170, "Bob": 184}Transform both keys and values.
scores := map[string]int{"Alice": 85, "Bob": 92}
remapped := mapz.Remap(scores, func(name string, score int) (string, string) {
return strings.ToUpper(name), fmt.Sprintf("%d%%", score)
})
// remapped = {"ALICE": "85%", "BOB": "92%"}Transform only keys.
ids := map[int]string{1: "Alice", 2: "Bob"}
withPrefix := mapz.RemapKeys(ids, func(id int, name string) string {
return fmt.Sprintf("user-%d", id)
})
// withPrefix = {"user-1": "Alice", "user-2": "Bob"}Transform only values (like MapValues but receives key too).
scores := map[string]int{"Alice": 85, "Bob": 92}
withGrades := mapz.RemapValues(scores, func(name string, score int) string {
if score >= 90 {
return "A"
} else if score >= 80 {
return "B"
}
return "C"
})
// withGrades = {"Alice": "B", "Bob": "A"}Swap keys and values.
scores := map[string]int{"Alice": 85, "Bob": 92}
byScore := mapz.Invert(scores)
// byScore = map[int]string{85: "Alice", 92: "Bob"}
// Note: If duplicate values exist, last one winsUpdate value at key if it exists.
counters := map[string]int{"visits": 0, "clicks": 5}
updated := mapz.Update(counters, "visits", func(v int) int {
return v + 1
})
// updated = true, counters = {"visits": 1, "clicks": 5}
// Key doesn't exist
updated = mapz.Update(counters, "downloads", func(v int) int {
return v + 1
})
// updated = false, counters unchangedGet value or compute and set if missing.
cache := map[string]string{}
// First call - computes and caches
value := mapz.GetOrSet(cache, "config", func() string {
// Expensive operation
return loadConfigFromDisk()
})
// Second call - returns cached value
value = mapz.GetOrSet(cache, "config", func() string {
// This function is NOT called!
return "never used"
})Keys in first map but not in second.
m1 := map[string]int{"a": 1, "b": 2, "c": 3}
m2 := map[string]int{"b": 20, "d": 4}
diff := mapz.Difference(m1, m2)
// diff = {"a": 1, "c": 3}Keys present in both maps.
m1 := map[string]int{"a": 1, "b": 2, "c": 3}
m2 := map[string]int{"b": 20, "c": 30, "d": 4}
common := mapz.Intersection(m1, m2)
// common = {"b": 2, "c": 3}
// Values from m1 are usedConvert map to slice.
scores := map[string]int{"Alice": 85, "Bob": 92}
pairs := mapz.Slice(scores, func(name string, score int) string {
return fmt.Sprintf("%s=%d", name, score)
})
// pairs = []string{"Alice=85", "Bob=92"} (order not guaranteed)Type representing a key-value pair.
type Entry[K comparable, V any] struct {
Key K
Value V
}Convert map to slice of entries.
scores := map[string]int{"Alice": 85, "Bob": 92}
entries := mapz.Entries(scores)
// entries = []Entry{{"Alice", 85}, {"Bob", 92}}Convert entries back to map.
entries := []mapz.Entry[string, int]{
{Key: "Alice", Value: 85},
{Key: "Bob", Value: 92},
}
m := mapz.FromEntries(entries)
// m = {"Alice": 85, "Bob": 92}- Immutable by default: Functions return new maps (safer, easier to reason about)
- Pre-allocation: Merge and Clone pre-allocate capacity to avoid reallocations
- Mutation marked: Functions that mutate (Clear, Delete, etc.) are clearly documented