Skip to content

Commit

Permalink
Add a generic ContainsAtLeastOne function
Browse files Browse the repository at this point in the history
  • Loading branch information
kubakl committed Aug 7, 2024
1 parent d1d0b87 commit 8333cad
Show file tree
Hide file tree
Showing 2 changed files with 156 additions and 0 deletions.
27 changes: 27 additions & 0 deletions slicex/slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,30 @@ func validateReflection(method string, kind reflect.Kind, v reflect.Value) {
panic(&reflect.ValueError{Method: method, Kind: v.Kind()})
}
}

// ContainsAtLeastOne checks if at least one element from needles list is in the haystack.
func ContainsAtLeastOne[T comparable](haystack []T, needles ...T) bool {
// Avoid allocations for a single check.
if len(needles) == 1 {
for _, h := range haystack {
if h == needles[0] {
return true
}
}
return false
}

checks := make(map[T]struct{}, len(needles))
for _, n := range needles {
checks[n] = struct{}{}
}

for _, h := range haystack {
_, ok := checks[h]
if ok {
return true
}
}

return false
}
129 changes: 129 additions & 0 deletions slicex/slice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,135 @@ func TestContains(t *testing.T) {
}
}

type containsAtLeastOneTestCase[T comparable] struct {
name string
slice []T
contains []T
want bool
}

func TestContainsAtLeastOne_GenericFloat64(t *testing.T) {
tests := []containsAtLeastOneTestCase[float64]{
{
name: "found 1 element",
slice: []float64{1, 2, 3, 4, 5, 6},
contains: []float64{3},
want: true,
},
{
name: "found 3 elements",
slice: []float64{1, 2, 3, 4, 5, 6},
contains: []float64{3, 5, 6},
want: true,
},
{
name: "found some but not all elements",
slice: []float64{1, 2, 3, 4, 5, 6},
contains: []float64{3, 5, 6, 7},
want: true,
},
{
name: "found no elements",
slice: []float64{1, 2, 3, 4, 5, 6},
contains: []float64{7, 9},
want: false,
},
{
name: "found no element",
slice: []float64{1, 2, 3, 4, 5, 6},
contains: []float64{7},
want: false,
},
}

testContainsAtLeastOne[float64](t, tests)
}

func TestContainsAtLeastOne_GenericInt64(t *testing.T) {
tests := []containsAtLeastOneTestCase[int64]{
{
name: "found 1 element",
slice: []int64{1, 2, 3, 4, 5, 6},
contains: []int64{3},
want: true,
},
{
name: "found 3 elements",
slice: []int64{1, 2, 3, 4, 5, 6},
contains: []int64{3, 5, 6},
want: true,
},
{
name: "found some but not all elements",
slice: []int64{1, 2, 3, 4, 5, 6},
contains: []int64{3, 5, 6, 7},
want: true,
},
{
name: "found no elements",
slice: []int64{1, 2, 3, 4, 5, 6},
contains: []int64{7, 9},
want: false,
},
{
name: "found no element",
slice: []int64{1, 2, 3, 4, 5, 6},
contains: []int64{7},
want: false,
},
}

testContainsAtLeastOne[int64](t, tests)
}

func TestContainsAtLeastOne_GenericString(t *testing.T) {
tests := []containsAtLeastOneTestCase[string]{
{
name: "found 1 element",
slice: []string{"1", "2", "3", "4", "5", "6"},
contains: []string{"3"},
want: true,
},
{
name: "found 3 elements",
slice: []string{"1", "2", "3", "4", "5", "6"},
contains: []string{"3", "5", "6"},
want: true,
},
{
name: "found some but not all elements",
slice: []string{"1", "2", "3", "4", "5", "6"},
contains: []string{"3", "5", "6", "7"},
want: true,
},
{
name: "found no elements",
slice: []string{"1", "2", "3", "4", "5", "6"},
contains: []string{"7", "9"},
want: false,
},
{
name: "found no element",
slice: []string{"1", "2", "3", "4", "5", "6"},
contains: []string{"7"},
want: false,
},
}

testContainsAtLeastOne[string](t, tests)
}

func testContainsAtLeastOne[T comparable](t *testing.T, tests []containsAtLeastOneTestCase[T]) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ContainsAtLeastOne(tt.slice, tt.contains...)
if tt.want != got {
t.Errorf("Got %+v, want %+v", got, tt.want)
}
})
}
}

func TestContains_Panic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
Expand Down

0 comments on commit 8333cad

Please sign in to comment.