From 1aa5d804b5a8fa8bddcfbd21827af0032e8ca67f Mon Sep 17 00:00:00 2001 From: David Sharnoff Date: Wed, 30 Apr 2025 22:11:01 -0700 Subject: [PATCH] CopyMapSubset --- map.go | 45 +++++++++++++++++++-- map_test.go | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++ set.go | 2 +- 3 files changed, 155 insertions(+), 5 deletions(-) diff --git a/map.go b/map.go index 88a0d3a..3dfc454 100644 --- a/map.go +++ b/map.go @@ -26,6 +26,7 @@ func CompareKeys[K comparable, V any](a, b map[K]V) ([]K, []K) { // MissingKeys returns the keys that are in a but not b func MissingKeys[K comparable, V any](a, b map[K]V) []K { + // Pre-allocate with capacity of a since that's the maximum possible size onlyA := make([]K, 0, len(a)) for k := range a { if _, ok := b[k]; !ok { @@ -35,12 +36,19 @@ func MissingKeys[K comparable, V any](a, b map[K]V) []K { return onlyA } +// EqualKeys checks if two maps have exactly the same keys. +// Returns true if both maps contain the same set of keys, regardless of values. func EqualKeys[K comparable, V any](a, b map[K]V) bool { if len(a) != len(b) { return false } - if len(MissingKeys(a, b)) != 0 { - return false + // We don't need to check if there are extras in b not in a because + // we checked that there are an equal number of keys so all we have + // to check is that all of a is in b + for k := range a { + if _, ok := b[k]; !ok { + return false + } } return true } @@ -49,18 +57,39 @@ func CopyMap[K comparable, V any](m map[K]V) map[K]V { if m == nil { return nil } - newM := make(map[K]V) + // Pre-allocate with capacity of m + newM := make(map[K]V, len(m)) for k, v := range m { newM[k] = v } return newM } +// CopyMapSubset creates a new map containing only the specified keys from the original map +func CopyMapSubset[K comparable, V any](m map[K]V, keys []K) map[K]V { + if m == nil { + return nil + } + + if len(m) == 0 { + return make(map[K]V) + } + + // Pre-allocate with capacity of keys since that's the maximum possible size + result := make(map[K]V, len(keys)) + for _, k := range keys { + if v, ok := m[k]; ok { + result[k] = v + } + } + return result +} + // Merge copies b onto a, overriding any common keys: // a is modified and returned. func Merge[K comparable, V any](a, b map[K]V) map[K]V { if a == nil { - return b + return CopyMap(b) } for k, v := range b { a[k] = v @@ -68,6 +97,8 @@ func Merge[K comparable, V any](a, b map[K]V) map[K]V { return a } +// AllKeys returns true if all keys in the map satisfy the given filter function. +// Returns true for empty maps (vacuous truth). func AllKeys[K comparable, V any](m map[K]V, filter func(K) bool) bool { for k := range m { if !filter(k) { @@ -77,6 +108,8 @@ func AllKeys[K comparable, V any](m map[K]V, filter func(K) bool) bool { return true } +// AnyKey returns true if at least one key in the map satisfies the given filter function. +// Returns false for empty maps. func AnyKey[K comparable, V any](m map[K]V, filter func(K) bool) bool { for k := range m { if filter(k) { @@ -86,6 +119,8 @@ func AnyKey[K comparable, V any](m map[K]V, filter func(K) bool) bool { return false } +// AllValues returns true if all values in the map satisfy the given filter function. +// Returns true for empty maps (vacuous truth). func AllValues[K comparable, V any](m map[K]V, filter func(V) bool) bool { for _, v := range m { if !filter(v) { @@ -95,6 +130,8 @@ func AllValues[K comparable, V any](m map[K]V, filter func(V) bool) bool { return true } +// AnyValue returns true if at least one value in the map satisfies the given filter function. +// Returns false for empty maps. func AnyValue[K comparable, V any](m map[K]V, filter func(V) bool) bool { for _, v := range m { if filter(v) { diff --git a/map_test.go b/map_test.go index 7c93499..46b3514 100644 --- a/map_test.go +++ b/map_test.go @@ -344,6 +344,119 @@ func TestCopyMap(t *testing.T) { }) } +func TestCopyMapSubset(t *testing.T) { + t.Parallel() + + t.Run("copies specified keys", func(t *testing.T) { + t.Parallel() + + original := map[string]int{ + "a": 1, + "b": 2, + "c": 3, + "d": 4, + } + + keys := []string{"a", "c"} + subset := generic.CopyMapSubset(original, keys) + + t.Log("Should copy only specified keys from the original map") + assert.Len(t, subset, 2) + assert.Equal(t, 1, subset["a"]) + assert.Equal(t, 3, subset["c"]) + assert.NotContains(t, subset, "b") + assert.NotContains(t, subset, "d") + }) + + t.Run("ignores keys not in original map", func(t *testing.T) { + t.Parallel() + + original := map[string]int{ + "a": 1, + "b": 2, + } + + keys := []string{"a", "c", "d"} + subset := generic.CopyMapSubset(original, keys) + + t.Log("Should only copy keys that exist in the original map") + assert.Len(t, subset, 1) + assert.Equal(t, 1, subset["a"]) + assert.NotContains(t, subset, "c") + assert.NotContains(t, subset, "d") + }) + + t.Run("returns empty map for nil input", func(t *testing.T) { + t.Parallel() + + var original map[string]int = nil + keys := []string{"a", "b"} + subset := generic.CopyMapSubset(original, keys) + + t.Log("Should return empty map when input map is nil") + assert.Nil(t, subset) + }) + + t.Run("handles empty keys", func(t *testing.T) { + t.Parallel() + + original := map[string]int{ + "a": 1, + "b": 2, + } + + var keys []string = nil + subset := generic.CopyMapSubset(original, keys) + + t.Log("Should return empty map when keys slice is nil") + assert.Empty(t, subset) + assert.NotNil(t, subset) + + keys = []string{} + subset = generic.CopyMapSubset(original, keys) + + t.Log("Should return empty map when keys slice is empty") + assert.Empty(t, subset) + assert.NotNil(t, subset) + }) + + t.Run("handles empty map", func(t *testing.T) { + t.Parallel() + + original := make(map[string]int) + keys := []string{"a", "b"} + subset := generic.CopyMapSubset(original, keys) + + t.Log("Should return empty map when input map is empty") + assert.Empty(t, subset) + assert.NotNil(t, subset) + }) + + t.Run("handles complex values", func(t *testing.T) { + t.Parallel() + + type User struct { + Name string + Admin bool + } + + original := map[string]User{ + "a": {Name: "Alice", Admin: false}, + "b": {Name: "Bob", Admin: true}, + "c": {Name: "Charlie", Admin: false}, + } + + keys := []string{"a", "c"} + subset := generic.CopyMapSubset(original, keys) + + t.Log("Should work with complex value types") + assert.Len(t, subset, 2) + assert.Equal(t, User{Name: "Alice", Admin: false}, subset["a"]) + assert.Equal(t, User{Name: "Charlie", Admin: false}, subset["c"]) + assert.NotContains(t, subset, "b") + }) +} + func TestMerge(t *testing.T) { t.Parallel() diff --git a/set.go b/set.go index 321c5c4..9554f82 100644 --- a/set.go +++ b/set.go @@ -1,7 +1,7 @@ package generic func ToSet[T comparable](slice []T) map[T]struct{} { - m := make(map[T]struct{}) + m := make(map[T]struct{}, len(slice)) for _, item := range slice { m[item] = struct{}{} }