From 2e42102ef0bf20f5ea90c8319eec7416ef6505bc Mon Sep 17 00:00:00 2001 From: Sterling Hanenkamp Date: Tue, 3 Sep 2024 16:42:07 -0500 Subject: [PATCH] chore: adding All, Any, None, sets.FlipMap, maps.FlipSlice --- Changes.md | 6 ++++ maps/transform.go | 25 +++++++++++++++++ maps/transform_test.go | 26 +++++++++++++++++ set/set.go | 57 ++++++++++++++++++++++++++++++++++--- set/set_test.go | 64 ++++++++++++++++++++++++++++++++++++++---- set/transform.go | 23 +++++++++++++++ set/transform_test.go | 35 +++++++++++++++++++++++ 7 files changed, 227 insertions(+), 9 deletions(-) create mode 100644 set/transform.go create mode 100644 set/transform_test.go diff --git a/Changes.md b/Changes.md index d3a40d0..5ac0d03 100644 --- a/Changes.md +++ b/Changes.md @@ -1,3 +1,9 @@ +## WIP TBD + + * Adding `maps.Flip`, `maps.FlipSlice`, and `sets.FlipMap`. + * Adding `sets.All`, `sets.Any`, and `sets.None`. + * The signature of `sets.Delete` and `sets.Insert` has changed to use variadic args to allow multiple deletes and inserts in a single call, respectively. + ## v0.8.0 2024-06-25 * Adding `bytes.ContainsOnly`, `bytes.FromRange`, `bytes.Reverse`, `bytes.Indent`. diff --git a/maps/transform.go b/maps/transform.go index 962efd3..2e1c372 100644 --- a/maps/transform.go +++ b/maps/transform.go @@ -34,6 +34,9 @@ func KVs[K comparable, V any](in map[K]V) []any { // Flip will return a new map with the keys and values of the input map flipped. // That is, the keys of the input map will be the values of the output map and // the values of the input map will be the keys of the output map. +// +// This will only work if the value type is comparable and if values are +// repeated, it is indeterminate which key will be kept. func Flip[K comparable, V comparable](in map[K]V) map[V]K { out := make(map[V]K, len(in)) for k, v := range in { @@ -41,3 +44,25 @@ func Flip[K comparable, V comparable](in map[K]V) map[V]K { } return out } + +// FlipSlice will return a new map with the keys and values of the input map +// flipped. That is, the keys of the input map will be the values of the output +// map and the values of the input map will be the keys of the output map. +// However, the resulting value is a slice of keys. +// +// This will only work if the value type is comparable. If values repeat, the +// keys will be collected into the value slice. The ordering is indeterminate. +// +// You may also be interested in set.FlipMap. +func FlipSlice[K comparable, V comparable](in map[K]V) map[V][]K { + out := make(map[V][]K, len(in)) + for k, v := range in { + outv, alreadyExists := out[v] + if !alreadyExists { + outv = make([]K, 0, 1) + } + outv = append(outv, k) + out[v] = outv + } + return out +} diff --git a/maps/transform_test.go b/maps/transform_test.go index aa879a5..d4d9bb8 100644 --- a/maps/transform_test.go +++ b/maps/transform_test.go @@ -92,3 +92,29 @@ func TestFlip(t *testing.T) { assert.Equal(t, k, a[v]) } } + +func TestFlipSlice(t *testing.T) { + t.Parallel() + + a := map[string]int{ + "a": 1, + "b": 2, + "c": 3, + "d": 1, + "e": 1, + } + + vks := maps.FlipSlice(a) + assert.Len(t, vks, 3) + for k, v := range a { + assert.Contains(t, vks, v) + assert.Contains(t, vks[v], k) + } + + for k, vs := range vks { + for _, v := range vs { + assert.Contains(t, a, v) + assert.Equal(t, k, a[v]) + } + } +} diff --git a/set/set.go b/set/set.go index 48169ca..aeb0e04 100644 --- a/set/set.go +++ b/set/set.go @@ -36,16 +36,65 @@ func (s Set[T]) Contains(val T) bool { return hasVal } +// All returns true if all the given values are contained within the set. +// This is similar to running: +// +// s.Intersection(set.New(vals...)).Len()==len(vals) +// +// This is both more efficient and easier to read in many cases. +func (s Set[T]) All(vals ...T) bool { + for _, val := range vals { + if _, contains := s[val]; !contains { + return false + } + } + return true +} + +// Any returns true if any of the given values are contained within the +// set. This is similar to running: +// +// s.Intersection(set.New(vals...)).Len()>0 +// +// This is both more efficient and easier to read in many cases. +func (s Set[T]) Any(vals ...T) bool { + for _, val := range vals { + if _, contains := s[val]; contains { + return true + } + } + return false +} + +// None returns true if none of the given values are contained within the +// set. This is similar to running: +// +// s.Intersection(set.New(vals...)).Len()==0 +// +// This is both more efficient and easier to read in many cases. +func (s Set[T]) None(vals ...T) bool { + for _, val := range vals { + if _, contains := s[val]; contains { + return false + } + } + return true +} + // Insert adds the given value to the set. If the value is already present, this // will have no effect. -func (s Set[T]) Insert(val T) { - s[val] = exists +func (s Set[T]) Insert(vals ...T) { + for _, val := range vals { + s[val] = exists + } } // Delete removes the given value from the set. If the value is not present, // this will have no effect. -func (s Set[T]) Delete(val T) { - delete(s, val) +func (s Set[T]) Delete(val ...T) { + for _, val := range val { + delete(s, val) + } } // Len returns the size of the set. diff --git a/set/set_test.go b/set/set_test.go index a688668..3d8b65c 100644 --- a/set/set_test.go +++ b/set/set_test.go @@ -61,21 +61,24 @@ func TestSet_Contains(t *testing.T) { func TestSet_Delete(t *testing.T) { t.Parallel() - s := set.New(1, 2, 3) + s := set.New(1, 2, 3, 4, 5, 6) s.Delete(0) - assert.Equal(t, set.New(1, 2, 3), s) + assert.Equal(t, set.New(1, 2, 3, 4, 5, 6), s) s.Delete(1) - assert.Equal(t, set.New(2, 3), s) + assert.Equal(t, set.New(2, 3, 4, 5, 6), s) s.Delete(2) - assert.Equal(t, set.New(3), s) + assert.Equal(t, set.New(3, 4, 5, 6), s) s.Delete(3) + assert.Equal(t, set.New(4, 5, 6), s) + + s.Delete(4, 5, 6) assert.Equal(t, set.New[int](), s) - s.Delete(4) + s.Delete(7) assert.Equal(t, set.New[int](), s) } @@ -97,6 +100,9 @@ func TestSet_Insert(t *testing.T) { s.Insert(3) assert.Equal(t, set.New(3, 2, 1), s) + + s.Insert(4, 5, 6) + assert.Equal(t, set.New(3, 2, 1, 4, 5, 6), s) } func TestSet_Len(t *testing.T) { @@ -213,3 +219,51 @@ func TestDiff(t *testing.T) { assert.Equal(t, set.New(2, 4), one) assert.Equal(t, set.New(5, 7), two) } + +func TestSet_All(t *testing.T) { + t.Parallel() + + s := set.New(1, 2, 3) + + assert.True(t, s.All(1, 2, 3)) + assert.True(t, s.All(1, 2)) + assert.True(t, s.All(1)) + assert.True(t, s.All(2)) + assert.True(t, s.All(3)) + assert.False(t, s.All(2, 3, 4)) + assert.False(t, s.All(2, 4)) + assert.False(t, s.All(4)) + assert.False(t, s.All(4, 5, 6)) +} + +func TestSet_Any(t *testing.T) { + t.Parallel() + + s := set.New(1, 2, 3) + + assert.True(t, s.Any(1, 2, 3)) + assert.True(t, s.Any(1, 2)) + assert.True(t, s.Any(1)) + assert.True(t, s.Any(2)) + assert.True(t, s.Any(3)) + assert.True(t, s.Any(2, 3, 4)) + assert.True(t, s.Any(2, 4)) + assert.False(t, s.Any(4)) + assert.False(t, s.Any(4, 5, 6)) +} + +func TestSet_None(t *testing.T) { + t.Parallel() + + s := set.New(1, 2, 3) + + assert.False(t, s.None(1, 2, 3)) + assert.False(t, s.None(1, 2)) + assert.False(t, s.None(1)) + assert.False(t, s.None(2)) + assert.False(t, s.None(3)) + assert.False(t, s.None(2, 3, 4)) + assert.False(t, s.None(2, 4)) + assert.True(t, s.None(4)) + assert.True(t, s.None(4, 5, 6)) +} diff --git a/set/transform.go b/set/transform.go new file mode 100644 index 0000000..7e69e9e --- /dev/null +++ b/set/transform.go @@ -0,0 +1,23 @@ +package set + +// FlipMap will return a new map with the keys and values of the input map +// flipped. That is, the keys of the input map will be the values of the output +// map and the values of the input map will be the keys of the output map. +// However, the resulting value is a Set of keys. +// +// This will only work if the value type is comparable. If values repeat, the +// keys will be collected into the value set. The ordering is indeterminate. +// +// You might also be interested in maps.Flip and maps.FlipSlice. +func FlipMap[K comparable, V comparable](in map[K]V) map[V]Set[K] { + out := make(map[V]Set[K], len(in)) + for k, v := range in { + outv, alreadyExists := out[v] + if !alreadyExists { + outv = New[K]() + } + outv.Insert(k) + out[v] = outv + } + return out +} diff --git a/set/transform_test.go b/set/transform_test.go new file mode 100644 index 0000000..1625a04 --- /dev/null +++ b/set/transform_test.go @@ -0,0 +1,35 @@ +package set_test + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/zostay/go-std/set" +) + +func TestFlipMap(t *testing.T) { + t.Parallel() + + a := map[string]int{ + "a": 1, + "b": 2, + "c": 3, + "d": 1, + "e": 1, + } + + vks := set.FlipMap(a) + assert.Len(t, vks, 3) + for k, v := range a { + assert.Contains(t, vks, v) + assert.True(t, vks[v].Contains(k)) + } + + for k, vs := range vks { + for _, v := range vs.Keys() { + assert.Contains(t, a, v) + assert.Equal(t, k, a[v]) + } + } +}