Skip to content

Commit d8762bb

Browse files
committed
refactor: generalize slice utility functions to support multiple data types
1 parent c527ee0 commit d8762bb

File tree

2 files changed

+155
-62
lines changed

2 files changed

+155
-62
lines changed

sliceutil/sliceutil.go

Lines changed: 20 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package sliceutil
22

3-
// AppendUnique appends unique strings from elems to slice, avoiding duplicates.
4-
func AppendUnique(slice []string, elems ...string) []string {
5-
uniqueMap := make(map[string]bool)
3+
// AppendUnique appends unique elements from elems to slice, avoiding duplicates.
4+
func AppendUnique[T comparable](slice []T, elems ...T) []T {
5+
uniqueMap := make(map[T]bool)
66
for _, v := range slice {
77
uniqueMap[v] = true
88
}
@@ -15,8 +15,8 @@ func AppendUnique(slice []string, elems ...string) []string {
1515
return slice
1616
}
1717

18-
// Contains checks if a slice contains a specified string.
19-
func Contains(slice []string, item string) bool {
18+
// Contains checks if a slice contains a specified element.
19+
func Contains[T comparable](slice []T, item T) bool {
2020
for _, a := range slice {
2121
if a == item {
2222
return true
@@ -25,13 +25,8 @@ func Contains(slice []string, item string) bool {
2525
return false
2626
}
2727

28-
// Equal checks if a string is in a slice of strings.
29-
func Equal(v string, elems ...string) bool {
30-
return Contains(elems, v)
31-
}
32-
33-
// Remove deletes an item from a slice of strings.
34-
func Remove(slice []string, item string) []string {
28+
// Remove deletes an item from a slice of elements.
29+
func Remove[T comparable](slice []T, item T) []T {
3530
for i, a := range slice {
3631
if a == item {
3732
return append(slice[:i], slice[i+1:]...)
@@ -40,28 +35,27 @@ func Remove(slice []string, item string) []string {
4035
return slice
4136
}
4237

43-
// DeleteEmpty removes empty strings from a slice.
44-
func DeleteEmpty(list []string) []string {
45-
var r []string
46-
for _, str := range list {
47-
if str != "" {
48-
r = append(r, str)
38+
// DeleteEmpty removes zero-value elements from a slice.
39+
func DeleteEmpty[T comparable](list []T) []T {
40+
var r []T
41+
for _, elem := range list {
42+
var zeroValue T
43+
if elem != zeroValue {
44+
r = append(r, elem)
4945
}
5046
}
5147
return r
5248
}
5349

54-
// Unique returns a slice with only unique strings.
55-
func Unique(items []string) []string {
56-
uniqueMap := make(map[string]bool)
50+
// Unique returns a slice with only unique elements, preserving the order of the first occurrence.
51+
func Unique[T comparable](items []T) []T {
52+
uniqueMap := make(map[T]bool)
53+
var uniqueItems []T
5754
for _, item := range items {
58-
if _, ok := uniqueMap[item]; !ok {
55+
if !uniqueMap[item] {
5956
uniqueMap[item] = true
57+
uniqueItems = append(uniqueItems, item)
6058
}
6159
}
62-
uniqueItems := make([]string, 0, len(uniqueMap))
63-
for item := range uniqueMap {
64-
uniqueItems = append(uniqueItems, item)
65-
}
6660
return uniqueItems
6761
}

sliceutil/sliceutil_test.go

Lines changed: 135 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,63 +6,162 @@ import (
66
)
77

88
func TestAppendUnique(t *testing.T) {
9-
initial := []string{"a", "b"}
10-
result := AppendUnique(initial, "b", "c", "d")
11-
expected := []string{"a", "b", "c", "d"}
12-
13-
if !reflect.DeepEqual(result, expected) {
14-
t.Errorf("AppendUnique failed, expected %v, got %v", expected, result)
15-
}
16-
}
17-
18-
func TestContains(t *testing.T) {
19-
slice := []string{"a", "b", "c"}
20-
21-
if !Contains(slice, "b") {
22-
t.Error("Contains failed, expected true, got false")
9+
tests := []struct {
10+
name string
11+
input interface{}
12+
elems interface{}
13+
expected interface{}
14+
}{
15+
{"AppendUnique with strings", []string{"apple", "banana"}, []string{"banana", "cherry", "apple"}, []string{"apple", "banana", "cherry"}},
16+
{"AppendUnique with integers", []int{1, 2, 3}, []int{3, 4, 1, 5}, []int{1, 2, 3, 4, 5}},
17+
{"AppendUnique with empty slice", []float64{}, []float64{1.1, 2.2, 1.1}, []float64{1.1, 2.2}},
2318
}
2419

25-
if Contains(slice, "d") {
26-
t.Error("Contains failed, expected false, got true")
20+
for _, tt := range tests {
21+
t.Run(tt.name, func(t *testing.T) {
22+
// Type switch based on the input type
23+
switch v := tt.input.(type) {
24+
case []string:
25+
result := AppendUnique(v, tt.elems.([]string)...)
26+
if !reflect.DeepEqual(result, tt.expected) {
27+
t.Errorf("AppendUnique() = %v, expected %v", result, tt.expected)
28+
}
29+
case []int:
30+
result := AppendUnique(v, tt.elems.([]int)...)
31+
if !reflect.DeepEqual(result, tt.expected) {
32+
t.Errorf("AppendUnique() = %v, expected %v", result, tt.expected)
33+
}
34+
case []float64:
35+
result := AppendUnique(v, tt.elems.([]float64)...)
36+
if !reflect.DeepEqual(result, tt.expected) {
37+
t.Errorf("AppendUnique() = %v, expected %v", result, tt.expected)
38+
}
39+
default:
40+
t.Errorf("Unhandled type %T", tt.input)
41+
}
42+
})
2743
}
2844
}
2945

30-
func TestEqual(t *testing.T) {
31-
if !Equal("a", "a", "b", "c") {
32-
t.Error("Equal failed, expected true, got false")
46+
// Test Contains with different data types
47+
func TestContains(t *testing.T) {
48+
tests := []struct {
49+
name string
50+
input interface{}
51+
item interface{}
52+
expected bool
53+
}{
54+
{"Contains with strings", []string{"apple", "banana", "cherry"}, "banana", true},
55+
{"Contains with strings (not present)", []string{"apple", "banana", "cherry"}, "mango", false},
56+
{"Contains with integers", []int{1, 2, 3, 4}, 3, true},
57+
{"Contains with integers (not present)", []int{1, 2, 3, 4}, 5, false},
3358
}
3459

35-
if Equal("d", "a", "b", "c") {
36-
t.Error("Equal failed, expected false, got true")
60+
for _, tt := range tests {
61+
t.Run(tt.name, func(t *testing.T) {
62+
var result bool
63+
switch v := tt.input.(type) {
64+
case []string:
65+
result = Contains(v, tt.item.(string))
66+
case []int:
67+
result = Contains(v, tt.item.(int))
68+
}
69+
if result != tt.expected {
70+
t.Errorf("Contains() = %v, expected %v", result, tt.expected)
71+
}
72+
})
3773
}
3874
}
3975

76+
// Test Remove with different data types
4077
func TestRemove(t *testing.T) {
41-
initial := []string{"a", "b", "c"}
42-
result := Remove(initial, "b")
43-
expected := []string{"a", "c"}
78+
tests := []struct {
79+
name string
80+
input interface{}
81+
item interface{}
82+
expected interface{}
83+
}{
84+
{"Remove with strings", []string{"apple", "banana", "cherry"}, "banana", []string{"apple", "cherry"}},
85+
{"Remove with integers", []int{1, 2, 3, 4}, 3, []int{1, 2, 4}},
86+
{"Remove with float64", []float64{1.1, 2.2, 3.3, 4.4}, 2.2, []float64{1.1, 3.3, 4.4}},
87+
}
4488

45-
if !reflect.DeepEqual(result, expected) {
46-
t.Errorf("Remove failed, expected %v, got %v", expected, result)
89+
for _, tt := range tests {
90+
t.Run(tt.name, func(t *testing.T) {
91+
var result interface{}
92+
switch v := tt.input.(type) {
93+
case []string:
94+
result = Remove(v, tt.item.(string))
95+
case []int:
96+
result = Remove(v, tt.item.(int))
97+
case []float64:
98+
result = Remove(v, tt.item.(float64))
99+
}
100+
if !reflect.DeepEqual(result, tt.expected) {
101+
t.Errorf("Remove() = %v, expected %v", result, tt.expected)
102+
}
103+
})
47104
}
48105
}
49106

107+
// Test DeleteEmpty with different data types
50108
func TestDeleteEmpty(t *testing.T) {
51-
initial := []string{"a", "", "b", "", "c"}
52-
result := DeleteEmpty(initial)
53-
expected := []string{"a", "b", "c"}
109+
tests := []struct {
110+
name string
111+
input interface{}
112+
expected interface{}
113+
}{
114+
{"DeleteEmpty with strings", []string{"apple", "", "banana", "", "cherry"}, []string{"apple", "banana", "cherry"}},
115+
{"DeleteEmpty with integers", []int{0, 1, 0, 2, 0, 3}, []int{1, 2, 3}}, // zero-value for int is 0
116+
{"DeleteEmpty with float64", []float64{0.0, 1.1, 0.0, 2.2}, []float64{1.1, 2.2}}, // zero-value for float64 is 0.0
117+
}
54118

55-
if !reflect.DeepEqual(result, expected) {
56-
t.Errorf("DeleteEmpty failed, expected %v, got %v", expected, result)
119+
for _, tt := range tests {
120+
t.Run(tt.name, func(t *testing.T) {
121+
var result interface{}
122+
switch v := tt.input.(type) {
123+
case []string:
124+
result = DeleteEmpty(v)
125+
case []int:
126+
result = DeleteEmpty(v)
127+
case []float64:
128+
result = DeleteEmpty(v)
129+
}
130+
if !reflect.DeepEqual(result, tt.expected) {
131+
t.Errorf("DeleteEmpty() = %v, expected %v", result, tt.expected)
132+
}
133+
})
57134
}
58135
}
59136

137+
// Test Unique with different data types
60138
func TestUnique(t *testing.T) {
61-
initial := []string{"a", "b", "a", "c", "b"}
62-
result := Unique(initial)
63-
expected := []string{"a", "b", "c"}
139+
tests := []struct {
140+
name string
141+
input interface{}
142+
expected interface{}
143+
}{
144+
{"Unique with strings", []string{"apple", "banana", "apple", "cherry"}, []string{"apple", "banana", "cherry"}},
145+
{"Unique with integers", []int{1, 2, 2, 3, 1, 4}, []int{1, 2, 3, 4}},
146+
{"Unique with float64", []float64{1.1, 2.2, 1.1, 3.3}, []float64{1.1, 2.2, 3.3}},
147+
}
64148

65-
if !reflect.DeepEqual(result, expected) {
66-
t.Errorf("Unique failed, expected %v, got %v", expected, result)
149+
for _, tt := range tests {
150+
t.Run(tt.name, func(t *testing.T) {
151+
var result interface{}
152+
switch v := tt.input.(type) {
153+
case []string:
154+
result = Unique(v)
155+
case []int:
156+
result = Unique(v)
157+
case []float64:
158+
result = Unique(v)
159+
default:
160+
t.Fatalf("Unhandled type %T", tt.input)
161+
}
162+
if !reflect.DeepEqual(result, tt.expected) {
163+
t.Errorf("Unique() = %v, expected %v", result, tt.expected)
164+
}
165+
})
67166
}
68167
}

0 commit comments

Comments
 (0)