From 0e1593c67bdfe20e35878f78e022ee9dd416eb62 Mon Sep 17 00:00:00 2001 From: donutloop Date: Wed, 28 Feb 2024 04:27:23 +0100 Subject: [PATCH] Slice: Add SetToDefaultIf (#187) SetToDefaultIf sets elements to their zero value if they match the given predicate. It retains the positions of the elements in the slice. It returns slice of T and the count of modified slice items --- slice/slice.go | 15 ++++ slice/slice_example_test.go | 10 +++ slice/slice_test.go | 139 ++++++++++++++++++++++++++++++++++++ 3 files changed, 164 insertions(+) diff --git a/slice/slice.go b/slice/slice.go index 0ac436c..3fa19bf 100644 --- a/slice/slice.go +++ b/slice/slice.go @@ -1170,6 +1170,21 @@ func AppendIfAbsent[T comparable](slice []T, item T) []T { return slice } +// SetToDefaultIf sets elements to their default value if they match the given predicate. +// It retains the positions of the elements in the slice. +// It returns slice of T and the count of modified slice items +func SetToDefaultIf[T any](slice []T, predicate func(T) bool) ([]T, int) { + var count int + for i := 0; i < len(slice); i++ { + if predicate(slice[i]) { + var zeroValue T + slice[i] = zeroValue + count++ + } + } + return slice, count +} + // KeyBy converts a slice to a map based on a callback function. // Play: https://go.dev/play/p/uXod2LWD1Kg func KeyBy[T any, U comparable](slice []T, iteratee func(item T) U) map[U]T { diff --git a/slice/slice_example_test.go b/slice/slice_example_test.go index 7d6bb2f..037ea64 100644 --- a/slice/slice_example_test.go +++ b/slice/slice_example_test.go @@ -1102,3 +1102,13 @@ func ExampleRandom() { // Output: // okk } + +func ExampleSetToDefaultIf() { + strs := []string{"a", "b", "a", "c", "d", "a"} + modifiedStrs, count := SetToDefaultIf(strs, func(s string) bool { return "a" == s }) + fmt.Println(modifiedStrs) + fmt.Println(count) + // Output: + // [ b c d ] + // 3 +} diff --git a/slice/slice_test.go b/slice/slice_test.go index 59b0c39..582a156 100644 --- a/slice/slice_test.go +++ b/slice/slice_test.go @@ -3,6 +3,7 @@ package slice import ( "fmt" "math" + "reflect" "strconv" "testing" @@ -1218,3 +1219,141 @@ func TestRandom(t *testing.T) { assert.Greater(len(arr), idx) assert.Equal(arr[idx], val) } + +func TestSetToDefaultIf(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "SetToDefaultIf") + + // Subtest for strings + t.Run("strings", func(t *testing.T) { + strs := []string{"a", "b", "a", "c", "d", "a"} + actualStrs, count := SetToDefaultIf(strs, func(s string) bool { return "a" == s }) + assert.Equal([]string{"", "b", "", "c", "d", ""}, actualStrs) + assert.Equal(3, count) + }) + + // Subtest for integers + t.Run("integers", func(t *testing.T) { + ints := []int{1, 2, 3, 2, 4, 2} + actualInts, count := SetToDefaultIf(ints, func(i int) bool { return i == 2 }) + assert.Equal([]int{1, 0, 3, 0, 4, 0}, actualInts) + assert.Equal(3, count) + }) + + // Subtest for floating-point numbers + t.Run("floats", func(t *testing.T) { + floats := []float64{1.1, 2.2, 3.3, 2.2, 4.4, 2.2} + actualFloats, count := SetToDefaultIf(floats, func(f float64) bool { return f == 2.2 }) + assert.Equal([]float64{1.1, 0, 3.3, 0, 4.4, 0}, actualFloats) + assert.Equal(3, count) + }) + + // Subtest for booleans + t.Run("booleans", func(t *testing.T) { + bools := []bool{true, false, true, true, false} + actualBools, count := SetToDefaultIf(bools, func(b bool) bool { return b }) + assert.Equal([]bool{false, false, false, false, false}, actualBools) + assert.Equal(3, count) + }) + + // Subtest for a custom type + type customType struct { + field string + } + t.Run("customType", func(t *testing.T) { + customs := []customType{{"a"}, {"b"}, {"a"}, {"c"}} + actualCustoms, count := SetToDefaultIf(customs, func(c customType) bool { return c.field == "a" }) + expected := []customType{{""}, {"b"}, {""}, {"c"}} + assert.Equal(expected, actualCustoms) + assert.Equal(2, count) + }) + + // Subtest for slice of integers + t.Run("sliceOfInts", func(t *testing.T) { + sliceOfInts := [][]int{{1, 2}, {3, 4}, {5, 6}, {1, 2}} + actualSlice, count := SetToDefaultIf(sliceOfInts, func(s []int) bool { return reflect.DeepEqual(s, []int{1, 2}) }) + expected := [][]int{nil, {3, 4}, {5, 6}, nil} + assert.Equal(expected, actualSlice) + assert.Equal(2, count) + }) + + // Subtest for maps (simple use case) + t.Run("mapOfStringToInts", func(t *testing.T) { + maps := []map[string]int{{"a": 1}, {"b": 2}, {"a": 1}, {"c": 3}} + actualMaps, count := SetToDefaultIf(maps, func(m map[string]int) bool { _, ok := m["a"]; return ok }) + expected := []map[string]int{nil, {"b": 2}, nil, {"c": 3}} + assert.Equal(expected, actualMaps) + assert.Equal(2, count) + }) + + // Subtest for pointers to integers + t.Run("pointersToInts", func(t *testing.T) { + one, two, three := 1, 2, 3 + pointers := []*int{&one, &two, &one, &three} + actualPointers, count := SetToDefaultIf(pointers, func(p *int) bool { return p != nil && *p == 1 }) + expected := []*int{nil, &two, nil, &three} + assert.Equal(expected, actualPointers) + assert.Equal(2, count) + }) + + // Subtest for channels + t.Run("channels", func(t *testing.T) { + ch1, ch2 := make(chan int), make(chan int) + channels := []chan int{ch1, ch2, ch1} + actualChannels, count := SetToDefaultIf(channels, func(ch chan int) bool { return ch == ch1 }) + expected := []chan int{nil, ch2, nil} + assert.Equal(expected, actualChannels) + assert.Equal(2, count) + }) + + // Subtest for interfaces + t.Run("interfaces", func(t *testing.T) { + var i1, i2 interface{} = "hello", 42 + interfaces := []interface{}{i1, i2, i1} + actualInterfaces, count := SetToDefaultIf(interfaces, func(i interface{}) bool { _, ok := i.(string); return ok }) + expected := []interface{}{nil, 42, nil} + assert.Equal(expected, actualInterfaces) + assert.Equal(2, count) + }) + + // Subtest for complex structs + t.Run("complexStructs", func(t *testing.T) { + type ComplexStruct struct { + Name string + Value int + Data []byte + } + cs1, cs2 := ComplexStruct{Name: "Test", Value: 1, Data: []byte{1, 2, 3}}, ComplexStruct{Name: "Another", Value: 2, Data: []byte{4, 5, 6}} + complexStructs := []ComplexStruct{cs1, cs2, cs1} + actualComplexStructs, count := SetToDefaultIf(complexStructs, func(cs ComplexStruct) bool { return cs.Name == "Test" }) + expected := []ComplexStruct{{}, cs2, {}} + assert.Equal(expected, actualComplexStructs) + assert.Equal(2, count) + }) + + // Subtest for uints + t.Run("uints", func(t *testing.T) { + uints := []uint{1, 2, 3, 2, 4, 2} + actualUints, count := SetToDefaultIf(uints, func(u uint) bool { return u == 2 }) + assert.Equal([]uint{1, 0, 3, 0, 4, 0}, actualUints) + assert.Equal(3, count) + }) + + // Subtest for float32 + t.Run("float32s", func(t *testing.T) { + floats := []float32{1.1, 2.2, 3.3, 2.2, 4.4, 2.2} + actualFloats, count := SetToDefaultIf(floats, func(f float32) bool { return f == 2.2 }) + assert.Equal([]float32{1.1, 0, 3.3, 0, 4.4, 0}, actualFloats) + assert.Equal(3, count) + }) + + // Subtest for []byte + t.Run("byteSlices", func(t *testing.T) { + bytes := [][]byte{{'a', 'b'}, {'c', 'd'}, {'a', 'b'}} + actualBytes, count := SetToDefaultIf(bytes, func(b []byte) bool { return string(b) == "ab" }) + expected := [][]byte{nil, {'c', 'd'}, nil} + assert.Equal(expected, actualBytes) + assert.Equal(2, count) + }) +}