diff --git a/slice/slice.go b/slice/slice.go index 89a62c8..4f2d7f3 100644 --- a/slice/slice.go +++ b/slice/slice.go @@ -808,6 +808,30 @@ func UniqueBy[T any, U comparable](slice []T, iteratee func(item T) U) []T { return result } +// UniqueByComparator removes duplicate elements from the input slice using the provided comparator function. +// The function maintains the order of the elements. +// Play: todo +func UniqueByComparator[T comparable](slice []T, comparator func(item T, other T) bool) []T { + result := make([]T, 0, len(slice)) + seen := make([]T, 0, len(slice)) + + for _, item := range slice { + duplicate := false + for _, seenItem := range seen { + if comparator(item, seenItem) { + duplicate = true + break + } + } + if !duplicate { + seen = append(seen, item) + result = append(result, item) + } + } + + return result +} + // UniqueByField remove duplicate elements in struct slice by struct field. // Play: https://go.dev/play/p/6cifcZSPIGu func UniqueByField[T any](slice []T, field string) ([]T, error) { diff --git a/slice/slice_example_test.go b/slice/slice_example_test.go index db8d0b5..99ac6cf 100644 --- a/slice/slice_example_test.go +++ b/slice/slice_example_test.go @@ -5,6 +5,7 @@ import ( "math" "reflect" "strconv" + "strings" ) func ExampleContain() { @@ -780,6 +781,23 @@ func ExampleUniqueBy() { // [1 2 3] } +func ExampleUniqueByComparator() { + uniqueNums := UniqueByComparator([]int{1, 2, 3, 1, 2, 4, 5, 6, 4}, func(item int, other int) bool { + return item == other + }) + + caseInsensitiveStrings := UniqueByComparator([]string{"apple", "banana", "Apple", "cherry", "Banana", "date"}, func(item string, other string) bool { + return strings.ToLower(item) == strings.ToLower(other) + }) + + fmt.Println(uniqueNums) + fmt.Println(caseInsensitiveStrings) + + // Output: + // [1 2 3 4 5 6] + // [apple banana cherry date] +} + func ExampleUniqueByField() { type User struct { ID int `json:"id"` diff --git a/slice/slice_test.go b/slice/slice_test.go index d03fb9c..85b9049 100644 --- a/slice/slice_test.go +++ b/slice/slice_test.go @@ -5,6 +5,7 @@ import ( "math" "reflect" "strconv" + "strings" "testing" "github.com/duke-git/lancet/v2/internal" @@ -764,6 +765,58 @@ func TestUniqueByField(t *testing.T) { }, uniqueUsers) } +func TestUniqueByComparator(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestUniqueByComparator") + + t.Run("equal comparison", func(t *testing.T) { + nums := []int{1, 2, 3, 1, 2, 4, 5, 6, 4, 7} + comparator := func(item int, other int) bool { + return item == other + } + result := UniqueByComparator(nums, comparator) + + assert.Equal([]int{1, 2, 3, 4, 5, 6, 7}, result) + }) + + t.Run("unique struct slice by field", func(t *testing.T) { + type student struct { + Name string + Age int + } + + students := []student{ + {Name: "a", Age: 11}, + {Name: "b", Age: 12}, + {Name: "a", Age: 13}, + {Name: "c", Age: 14}, + } + + comparator := func(item, other student) bool { return item.Name == other.Name } + + result := UniqueByComparator(students, comparator) + + assert.Equal([]student{ + {Name: "a", Age: 11}, + {Name: "b", Age: 12}, + {Name: "c", Age: 14}, + }, result) + }) + + t.Run("case-insensitive string comparison", func(t *testing.T) { + stringSlice := []string{"apple", "banana", "Apple", "cherry", "Banana", "date"} + caseInsensitiveComparator := func(item, other string) bool { + return strings.ToLower(item) == strings.ToLower(other) + } + + result := UniqueByComparator(stringSlice, caseInsensitiveComparator) + + assert.Equal([]string{"apple", "banana", "cherry", "date"}, result) + }) + +} + func TestUnion(t *testing.T) { t.Parallel()