diff --git a/slice/slice.go b/slice/slice.go index 13830a9..eef7dbd 100644 --- a/slice/slice.go +++ b/slice/slice.go @@ -796,6 +796,46 @@ func UniqueBy[T comparable](slice []T, iteratee func(item T) T) []T { return Unique(result) } +// UniqueByField remove duplicate elements in struct slice by struct field. +// Play: todo +func UniqueByField[T any](slice []T, field string) ([]T, error) { + seen := map[any]struct{}{} + + var result []T + for _, item := range slice { + val, err := getField(item, field) + if err != nil { + return nil, fmt.Errorf("get field %s failed: %v", field, err) + } + if _, ok := seen[val]; !ok { + seen[val] = struct{}{} + result = append(result, item) + } + } + + return result, nil +} + +func getField[T any](item T, field string) (interface{}, error) { + v := reflect.ValueOf(item) + t := reflect.TypeOf(item) + + if t.Kind() == reflect.Ptr { + t = t.Elem() + v = v.Elem() + } + if v.Kind() != reflect.Struct { + return nil, fmt.Errorf("data type %T not support, shuld be struct or pointer to struct", item) + } + + f := v.FieldByName(field) + if !f.IsValid() { + return nil, fmt.Errorf("field name %s not found", field) + } + + return v.FieldByName(field).Interface(), nil +} + // Union creates a slice of unique elements, in order, from all given slices. // Play: https://go.dev/play/p/hfXV1iRIZOf func Union[T comparable](slices ...[]T) []T { diff --git a/slice/slice_example_test.go b/slice/slice_example_test.go index f949e75..8fa8c13 100644 --- a/slice/slice_example_test.go +++ b/slice/slice_example_test.go @@ -780,6 +780,28 @@ func ExampleUniqueBy() { // [1 2 0] } +func ExampleUniqueByField() { + type User struct { + ID int `json:"id"` + Name string `json:"name"` + } + + users := []User{ + {ID: 1, Name: "a"}, + {ID: 2, Name: "b"}, + {ID: 1, Name: "c"}, + } + + result, err := UniqueByField(users, "ID") + if err != nil { + } + + fmt.Println(result) + + // Output: + // [{1 a} {2 b}] +} + func ExampleUnion() { nums1 := []int{1, 3, 4, 6} nums2 := []int{1, 2, 5, 6} diff --git a/slice/slice_test.go b/slice/slice_test.go index fb2c4e2..c1d4d19 100644 --- a/slice/slice_test.go +++ b/slice/slice_test.go @@ -2,11 +2,12 @@ package slice import ( "fmt" - "github.com/duke-git/lancet/v2/internal" "math" "reflect" "strconv" "testing" + + "github.com/duke-git/lancet/v2/internal" ) func TestContain(t *testing.T) { @@ -735,6 +736,33 @@ func TestUniqueBy(t *testing.T) { assert.Equal([]int{1, 2, 3, 0}, actual) } +func TestUniqueByField(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestUniqueByField") + + type User struct { + ID int `json:"id"` + Name string `json:"name"` + } + + users := []User{ + {ID: 1, Name: "a"}, + {ID: 2, Name: "b"}, + {ID: 1, Name: "c"}, + } + + uniqueUsers, err := UniqueByField(users, "ID") + if err != nil { + t.Error(err) + } + + assert.Equal([]User{ + {ID: 1, Name: "a"}, + {ID: 2, Name: "b"}, + }, uniqueUsers) +} + func TestUnion(t *testing.T) { t.Parallel()