From 543202cae54f4aa8a3ed6f818db0a702938fb0a9 Mon Sep 17 00:00:00 2001 From: nicolaasuni-vonage Date: Thu, 31 Aug 2023 11:42:53 +0100 Subject: [PATCH 1/5] Add maputil package for the non-thread-safe version of the tsmap package functions. --- pkg/maputil/example_maputil_test.go | 57 +++++++++++++++++++++++++++++ pkg/maputil/maputil.go | 53 +++++++++++++++++++++++++++ pkg/maputil/maputil_test.go | 56 ++++++++++++++++++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 pkg/maputil/example_maputil_test.go create mode 100644 pkg/maputil/maputil.go create mode 100644 pkg/maputil/maputil_test.go diff --git a/pkg/maputil/example_maputil_test.go b/pkg/maputil/example_maputil_test.go new file mode 100644 index 00000000..8c91e0c3 --- /dev/null +++ b/pkg/maputil/example_maputil_test.go @@ -0,0 +1,57 @@ +package maputil_test + +import ( + "fmt" + + "github.com/Vonage/gosrvlib/pkg/maputil" +) + +func ExampleFilter() { + m := map[int]string{0: "Hello", 1: "World"} + + filterFn := func(_ int, v string) bool { return v == "World" } + + s2 := maputil.Filter(m, filterFn) + + fmt.Println(s2) + + // Output: + // map[1:World] +} + +func ExampleMap() { + m := map[int]string{0: "Hello", 1: "World"} + + mapFn := func(k int, v string) (string, int) { return "_" + v, k + 1 } + + s2 := maputil.Map(m, mapFn) + + fmt.Println(s2) + + // Output: + // map[_Hello:1 _World:2] +} + +func ExampleReduce() { + m := map[int]int{0: 2, 1: 3, 2: 5, 3: 7, 4: 11} + init := 97 + reduceFn := func(k, v, r int) int { return k + v + r } + + r := maputil.Reduce(m, init, reduceFn) + + fmt.Println(r) + + // Output: + // 135 +} + +func ExampleInvert() { + m := map[int]int{1: 10, 2: 20} + + s2 := maputil.Invert(m) + + fmt.Println(s2) + + // Output: + // map[10:1 20:2] +} diff --git a/pkg/maputil/maputil.go b/pkg/maputil/maputil.go new file mode 100644 index 00000000..ffd55044 --- /dev/null +++ b/pkg/maputil/maputil.go @@ -0,0 +1,53 @@ +// Package maputil provides a collection of map functions. +package maputil + +// Filter returns a new map containing +// only the elements in the input map m for which the specified function f is true. +func Filter[M ~map[K]V, K comparable, V any](m M, f func(K, V) bool) M { + r := make(M, len(m)) + + for k, v := range m { + if f(k, v) { + r[k] = v + } + } + + return r +} + +// Map returns a new map that contains +// each of the elements of the input map m mutated by the specified function. +func Map[M ~map[K]V, K, J comparable, V, U any](m M, f func(K, V) (J, U)) map[J]U { + r := make(map[J]U, len(m)) + + for k, v := range m { + j, u := f(k, v) + r[j] = u + } + + return r +} + +// Reduce applies the reducing function f +// to each element of the input map m, and returns the value of the last call to f. +// The first parameter of the reducing function f is initialized with init. +func Reduce[M ~map[K]V, K comparable, V, U any](m M, init U, f func(K, V, U) U) U { + r := init + + for k, v := range m { + r = f(k, v, r) + } + + return r +} + +// Invert returns a new map were keys and values are swapped. +func Invert[M ~map[K]V, K, V comparable](m M) map[V]K { + r := make(map[V]K, len(m)) + + for k, v := range m { + r[v] = k + } + + return r +} diff --git a/pkg/maputil/maputil_test.go b/pkg/maputil/maputil_test.go new file mode 100644 index 00000000..95c13e7e --- /dev/null +++ b/pkg/maputil/maputil_test.go @@ -0,0 +1,56 @@ +package maputil + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFilter(t *testing.T) { + t.Parallel() + + m := map[int]string{0: "Hello", 1: "World"} + filterFn := func(_ int, v string) bool { return v == "World" } + + got := Filter(m, filterFn) + + require.Len(t, got, 1) + require.Equal(t, "World", m[1]) +} + +func TestMap(t *testing.T) { + t.Parallel() + + m := map[int]string{0: "Hello", 1: "World"} + mapFn := func(k int, v string) (string, int) { return "_" + v, k + 1 } + + got := Map(m, mapFn) + + require.Len(t, got, 2) + require.Equal(t, 1, got["_Hello"]) + require.Equal(t, 2, got["_World"]) +} + +func TestReduce(t *testing.T) { + t.Parallel() + + m := map[int]int{0: 2, 1: 3, 2: 5, 3: 7, 4: 11} + init := 97 + reduceFn := func(k, v, r int) int { return k + v + r } + + got := Reduce(m, init, reduceFn) + + require.Equal(t, 135, got) +} + +func TestInvert(t *testing.T) { + t.Parallel() + + m := map[int]int{1: 10, 2: 20} + + got := Invert(m) + + require.Len(t, got, 2) + require.Equal(t, 1, got[10]) + require.Equal(t, 2, got[20]) +} From 61af68d95f86bde1cf5ec245345fb26c4da8d42c Mon Sep 17 00:00:00 2001 From: nicolaasuni-vonage Date: Thu, 31 Aug 2023 11:43:28 +0100 Subject: [PATCH 2/5] Add sliceutil package for the non-thread-safe version of the tsslice package functions. --- pkg/sliceutil/example_sliceutil_test.go | 47 +++++++++++++++++++++++++ pkg/sliceutil/sliceutil.go | 41 +++++++++++++++++++++ pkg/sliceutil/sliceutil_test.go | 41 +++++++++++++++++++++ 3 files changed, 129 insertions(+) create mode 100644 pkg/sliceutil/example_sliceutil_test.go create mode 100644 pkg/sliceutil/sliceutil.go create mode 100644 pkg/sliceutil/sliceutil_test.go diff --git a/pkg/sliceutil/example_sliceutil_test.go b/pkg/sliceutil/example_sliceutil_test.go new file mode 100644 index 00000000..53c81851 --- /dev/null +++ b/pkg/sliceutil/example_sliceutil_test.go @@ -0,0 +1,47 @@ +package sliceutil_test + +import ( + "fmt" + + "github.com/Vonage/gosrvlib/pkg/sliceutil" +) + +func ExampleFilter() { + s := []string{"Hello", "World", "Extra"} + + filterFn := func(_ int, v string) bool { return v == "World" } + + s2 := sliceutil.Filter(s, filterFn) + + fmt.Println(s2) + + // Output: + // [World] +} + +func ExampleMap() { + s := []string{"Hello", "World", "Extra"} + + mapFn := func(k int, v string) int { return k + len(v) } + + s2 := sliceutil.Map(s, mapFn) + + fmt.Println(s2) + + // Output: + // [5 6 7] +} + +func ExampleReduce() { + s := []int{2, 3, 5, 7, 11} + + init := 97 + reduceFn := func(k, v, r int) int { return k + v + r } + + r := sliceutil.Reduce(s, init, reduceFn) + + fmt.Println(r) + + // Output: + // 135 +} diff --git a/pkg/sliceutil/sliceutil.go b/pkg/sliceutil/sliceutil.go new file mode 100644 index 00000000..ac5dea7e --- /dev/null +++ b/pkg/sliceutil/sliceutil.go @@ -0,0 +1,41 @@ +// Package sliceutil provides a collection of slice functions. +package sliceutil + +// Filter returns a new slice containing +// only the elements in the input slice s for which the specified function f is true. +func Filter[S ~[]E, E any](s S, f func(int, E) bool) S { + r := make(S, 0) + + for k, v := range s { + if f(k, v) { + r = append(r, v) + } + } + + return r +} + +// Map returns a new slice that contains +// each of the elements of the input slice s mutated by the specified function. +func Map[S ~[]E, E any, U any](s S, f func(int, E) U) []U { + r := make([]U, len(s)) + + for k, v := range s { + r[k] = f(k, v) + } + + return r +} + +// Reduce applies the reducing function f +// to each element of the input slice s, and returns the value of the last call to f. +// The first parameter of the reducing function f is initialized with init. +func Reduce[S ~[]E, E any, U any](s S, init U, f func(int, E, U) U) U { + r := init + + for k, v := range s { + r = f(k, v, r) + } + + return r +} diff --git a/pkg/sliceutil/sliceutil_test.go b/pkg/sliceutil/sliceutil_test.go new file mode 100644 index 00000000..faef665c --- /dev/null +++ b/pkg/sliceutil/sliceutil_test.go @@ -0,0 +1,41 @@ +package sliceutil + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFilter(t *testing.T) { + t.Parallel() + + s := []string{"Hello", "World", "Extra"} + filterFn := func(_ int, v string) bool { return v == "World" } + + got := Filter(s, filterFn) + + require.ElementsMatch(t, []string{"World"}, got) +} + +func TestMap(t *testing.T) { + t.Parallel() + + s := []string{"Hello", "World", "Extra"} + mapFn := func(k int, v string) int { return k + len(v) } + + got := Map(s, mapFn) + + require.ElementsMatch(t, []int{5, 6, 7}, got) +} + +func TestReduce(t *testing.T) { + t.Parallel() + + s := []int{2, 3, 5, 7, 11} + init := 97 + reduceFn := func(k, v, r int) int { return k + v + r } + + got := Reduce(s, init, reduceFn) + + require.Equal(t, 135, got) +} From 73806065207e998d837f791e9ec6bc846ba69908 Mon Sep 17 00:00:00 2001 From: nicolaasuni-vonage Date: Thu, 31 Aug 2023 11:44:17 +0100 Subject: [PATCH 3/5] Wrap the functions in the new sliceutil package. --- pkg/threadsafe/tsslice/tsslice.go | 27 ++++----------------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/pkg/threadsafe/tsslice/tsslice.go b/pkg/threadsafe/tsslice/tsslice.go index 4bac0cd2..c5165e05 100644 --- a/pkg/threadsafe/tsslice/tsslice.go +++ b/pkg/threadsafe/tsslice/tsslice.go @@ -2,6 +2,7 @@ package tsslice import ( + "github.com/Vonage/gosrvlib/pkg/sliceutil" "github.com/Vonage/gosrvlib/pkg/threadsafe" ) @@ -44,15 +45,7 @@ func Filter[S ~[]E, E any](mux threadsafe.RLocker, s S, f func(int, E) bool) S { mux.RLock() defer mux.RUnlock() - r := make(S, 0) - - for k, v := range s { - if f(k, v) { - r = append(r, v) - } - } - - return r + return sliceutil.Filter(s, f) } // Map is a thread-safe function that returns a new slice that contains @@ -61,13 +54,7 @@ func Map[S ~[]E, E any, U any](mux threadsafe.RLocker, s S, f func(int, E) U) [] mux.RLock() defer mux.RUnlock() - r := make([]U, len(s)) - - for k, v := range s { - r[k] = f(k, v) - } - - return r + return sliceutil.Map(s, f) } // Reduce is a thread-safe function that applies the reducing function f @@ -77,11 +64,5 @@ func Reduce[S ~[]E, E any, U any](mux threadsafe.RLocker, s S, init U, f func(in mux.RLock() defer mux.RUnlock() - r := init - - for k, v := range s { - r = f(k, v, r) - } - - return r + return sliceutil.Reduce(s, init, f) } From b0361278834ea5801c9fb0ba03f704d37ba5e081 Mon Sep 17 00:00:00 2001 From: nicolaasuni-vonage Date: Thu, 31 Aug 2023 11:45:07 +0100 Subject: [PATCH 4/5] Wrap functions in the new maputil package and add Invert function. --- pkg/threadsafe/tsmap/example_tsmap_test.go | 13 +++++++++ pkg/threadsafe/tsmap/tsmap.go | 32 +++++++--------------- pkg/threadsafe/tsmap/tsmap_test.go | 14 ++++++++++ 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/pkg/threadsafe/tsmap/example_tsmap_test.go b/pkg/threadsafe/tsmap/example_tsmap_test.go index d1e955db..afd5d5d2 100644 --- a/pkg/threadsafe/tsmap/example_tsmap_test.go +++ b/pkg/threadsafe/tsmap/example_tsmap_test.go @@ -86,3 +86,16 @@ func ExampleReduce() { // Output: // 135 } + +func ExampleInvert() { + mux := &sync.RWMutex{} + + m := map[int]int{1: 10, 2: 20} + + s2 := tsmap.Invert(mux, m) + + fmt.Println(s2) + + // Output: + // map[10:1 20:2] +} diff --git a/pkg/threadsafe/tsmap/tsmap.go b/pkg/threadsafe/tsmap/tsmap.go index 77fa302b..78a0907a 100644 --- a/pkg/threadsafe/tsmap/tsmap.go +++ b/pkg/threadsafe/tsmap/tsmap.go @@ -2,6 +2,7 @@ package tsmap import ( + "github.com/Vonage/gosrvlib/pkg/maputil" "github.com/Vonage/gosrvlib/pkg/threadsafe" ) @@ -35,15 +36,7 @@ func Filter[M ~map[K]V, K comparable, V any](mux threadsafe.RLocker, m M, f func mux.RLock() defer mux.RUnlock() - r := make(M, len(m)) - - for k, v := range m { - if f(k, v) { - r[k] = v - } - } - - return r + return maputil.Filter(m, f) } // Map is a thread-safe function that returns a new map that contains @@ -53,14 +46,7 @@ func Map[M ~map[K]V, K, J comparable, V, U any](mux threadsafe.RLocker, m M, f f mux.RLock() defer mux.RUnlock() - r := make(map[J]U, len(m)) - - for k, v := range m { - j, u := f(k, v) - r[j] = u - } - - return r + return maputil.Map(m, f) } // Reduce is a thread-safe function that applies the reducing function f @@ -70,11 +56,13 @@ func Reduce[M ~map[K]V, K comparable, V, U any](mux threadsafe.RLocker, m M, ini mux.RLock() defer mux.RUnlock() - r := init + return maputil.Reduce(m, init, f) +} - for k, v := range m { - r = f(k, v, r) - } +// Invert is a thread-safe function that returns a new map were keys and values are swapped. +func Invert[M ~map[K]V, K, V comparable](mux threadsafe.RLocker, m M) map[V]K { + mux.RLock() + defer mux.RUnlock() - return r + return maputil.Invert(m) } diff --git a/pkg/threadsafe/tsmap/tsmap_test.go b/pkg/threadsafe/tsmap/tsmap_test.go index c89dec1f..1236c0d9 100644 --- a/pkg/threadsafe/tsmap/tsmap_test.go +++ b/pkg/threadsafe/tsmap/tsmap_test.go @@ -83,3 +83,17 @@ func TestReduce(t *testing.T) { require.Equal(t, 135, got) } + +func TestInvert(t *testing.T) { + t.Parallel() + + mux := &sync.RWMutex{} + + m := map[int]int{1: 10, 2: 20} + + got := Invert(mux, m) + + require.Len(t, got, 2) + require.Equal(t, 1, got[10]) + require.Equal(t, 2, got[20]) +} From 3c617de50e2ba898b562d4fe24aa54c4604eb891 Mon Sep 17 00:00:00 2001 From: nicolaasuni-vonage Date: Thu, 31 Aug 2023 11:45:18 +0100 Subject: [PATCH 5/5] Bump version. --- VERSION | 2 +- examples/service/go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VERSION b/VERSION index d3bef3cf..b3a8c61e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.78.4 +1.79.0 diff --git a/examples/service/go.mod b/examples/service/go.mod index 33aa7556..7654bee7 100644 --- a/examples/service/go.mod +++ b/examples/service/go.mod @@ -5,7 +5,7 @@ go 1.21 replace github.com/Vonage/gosrvlib => ../.. require ( - github.com/Vonage/gosrvlib v1.78.4 + github.com/Vonage/gosrvlib v1.79.0 github.com/golang/mock v1.6.0 github.com/jstemmer/go-junit-report v0.9.1 github.com/prometheus/client_golang v1.16.0