From 107d5794c3ef7e82714a40c3d07e52b6fdf631c3 Mon Sep 17 00:00:00 2001 From: nicklausliu Date: Thu, 15 Aug 2024 11:46:25 +0800 Subject: [PATCH 1/9] feat: add WithoutBy --- intersect.go | 11 +++++++++++ intersect_test.go | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/intersect.go b/intersect.go index 2df0e741..cfca9508 100644 --- a/intersect.go +++ b/intersect.go @@ -176,6 +176,17 @@ func Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice { return result } +// WithoutBy returns a slice excluding values whose extracted key is in the exclude list. +func WithoutBy[T any, K comparable](collection []T, extract func(item T) K, exclude ...K) []T { + result := make([]T, 0, len(collection)) + for _, item := range collection { + if !Contains(exclude, extract(item)) { + result = append(result, item) + } + } + return result +} + // WithoutEmpty returns slice excluding empty values. // // Deprecated: Use lo.Compact instead. diff --git a/intersect_test.go b/intersect_test.go index 53911599..b0b9fd7d 100644 --- a/intersect_test.go +++ b/intersect_test.go @@ -270,6 +270,26 @@ func TestWithout(t *testing.T) { is.IsType(nonempty, allStrings, "type preserved") } +func TestWithoutBy(t *testing.T) { + t.Parallel() + is := assert.New(t) + + type user struct { + name string + age int + } + + result1 := WithoutBy([]user{{name: "nick"}, {name: "peter"}}, + func(item user) string { + return item.name + }, "nick", "lily") + result2 := WithoutBy([]user{}, func(item user) int { return item.age }, 1, 2, 3) + result3 := WithoutBy([]user{}, func(item user) string { return item.name }) + is.Equal(result1, []user{{name: "peter"}}) + is.Equal(result2, []user{}) + is.Equal(result3, []user{}) +} + func TestWithoutEmpty(t *testing.T) { t.Parallel() is := assert.New(t) From 3cf2fc4ba8360570b94c1ea71f8badd1887db36d Mon Sep 17 00:00:00 2001 From: nicklausliu Date: Sat, 17 Aug 2024 20:14:12 +0800 Subject: [PATCH 2/9] feat: modify WithoutBy function comment --- intersect.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/intersect.go b/intersect.go index cfca9508..2350eb26 100644 --- a/intersect.go +++ b/intersect.go @@ -176,7 +176,8 @@ func Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice { return result } -// WithoutBy returns a slice excluding values whose extracted key is in the exclude list. +// WithoutBy filters a slice by excluding elements whose extracted keys match any in the exclude list. +// It returns a new slice containing only the elements whose keys are not in the exclude list. func WithoutBy[T any, K comparable](collection []T, extract func(item T) K, exclude ...K) []T { result := make([]T, 0, len(collection)) for _, item := range collection { From 21f10dac8f3984f94bbcf26c0095eb79adf03e3e Mon Sep 17 00:00:00 2001 From: nicklausliu Date: Sat, 17 Aug 2024 20:21:35 +0800 Subject: [PATCH 3/9] feat: add WithoutBy example test --- intersect_example_test.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 intersect_example_test.go diff --git a/intersect_example_test.go b/intersect_example_test.go new file mode 100644 index 00000000..653dc6db --- /dev/null +++ b/intersect_example_test.go @@ -0,0 +1,34 @@ +package lo + +import ( + "fmt" +) + +func ExampleWithoutBy() { + type user struct { + id int + name string + } + // Example usage + users := []user{ + {id: 1, name: "Alice"}, + {id: 2, name: "Bob"}, + {id: 3, name: "Charlie"}, + } + + // Exclude users with IDs 2 and 3 + excludedIDs := []int{2, 3} + + // Extract function to get the user ID + extractID := func(user user) int { + return user.id + } + + // Filtering users + filteredUsers := WithoutBy(users, extractID, excludedIDs...) + + // Output the filtered users + fmt.Printf("%v\n", filteredUsers) + // Output: + // [{1 Alice}] +} From b9680569a16ceb6ee65169392e0aafa83564b4cc Mon Sep 17 00:00:00 2001 From: nicklausliu Date: Sat, 17 Aug 2024 20:29:50 +0800 Subject: [PATCH 4/9] feat: add README --- README.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.md b/README.md index 3f73cc8e..1f20d910 100644 --- a/README.md +++ b/README.md @@ -206,6 +206,7 @@ Supported intersection helpers: - [Difference](#difference) - [Union](#union) - [Without](#without) +- [WithoutBy](#withoutby) - [WithoutEmpty](#withoutempty) Supported search helpers: @@ -2097,6 +2098,38 @@ subset := lo.Without([]int{0, 2, 10}, 0, 1, 2, 3, 4, 5) // []int{10} ``` +### WithoutBy + +WithoutBy filters a slice by excluding elements whose extracted keys match any in the exclude list. +It returns a new slice containing only the elements whose keys are not in the exclude list. + + +```go +type user { + id int + name string +} + +// original users +users := []user{ + {id: 1, name: "Alice"}, + {id: 2, name: "Bob"}, + {id: 3, name: "Charlie"}, +} + +// exclude users with IDs 2 and 3 +excludedIDs := []int{2, 3} + +// extract function to get the user ID +extractID := func(user user) int { + return user.id +} + +// filtering users +filteredUsers := WithoutBy(users, extractID, excludedIDs...) +// []user[{id: 1, name: "Alice"}] +``` + ### WithoutEmpty Returns slice excluding empty values. From fd53232a4b0004439e134bc3d9e432a4c84bfca6 Mon Sep 17 00:00:00 2001 From: nicklausliu Date: Sat, 17 Aug 2024 20:31:00 +0800 Subject: [PATCH 5/9] feat: modify ExampleWithoutBy comments --- intersect_example_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/intersect_example_test.go b/intersect_example_test.go index 653dc6db..3535754f 100644 --- a/intersect_example_test.go +++ b/intersect_example_test.go @@ -9,25 +9,25 @@ func ExampleWithoutBy() { id int name string } - // Example usage + // original users users := []user{ {id: 1, name: "Alice"}, {id: 2, name: "Bob"}, {id: 3, name: "Charlie"}, } - // Exclude users with IDs 2 and 3 + // exclude users with IDs 2 and 3 excludedIDs := []int{2, 3} - // Extract function to get the user ID + // extract function to get the user ID extractID := func(user user) int { return user.id } - // Filtering users + // filtering users filteredUsers := WithoutBy(users, extractID, excludedIDs...) - // Output the filtered users + // output the filtered users fmt.Printf("%v\n", filteredUsers) // Output: // [{1 Alice}] From 4f1f56a930c293dc182344852908604b6af97fff Mon Sep 17 00:00:00 2001 From: nicklausliu Date: Sun, 18 Aug 2024 14:47:17 +0800 Subject: [PATCH 6/9] feat: convert Test struct to exported --- README.md | 20 ++++++++++---------- intersect_example_test.go | 18 +++++++++--------- intersect_test.go | 22 +++++++++++----------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/README.md b/README.md index 1f20d910..15a2bad8 100644 --- a/README.md +++ b/README.md @@ -2105,29 +2105,29 @@ It returns a new slice containing only the elements whose keys are not in the ex ```go -type user { - id int - name string +type User { + ID int + Name string } // original users -users := []user{ - {id: 1, name: "Alice"}, - {id: 2, name: "Bob"}, - {id: 3, name: "Charlie"}, +users := []User{ + {ID: 1, Name: "Alice"}, + {ID: 2, Name: "Bob"}, + {ID: 3, Name: "Charlie"}, } // exclude users with IDs 2 and 3 excludedIDs := []int{2, 3} // extract function to get the user ID -extractID := func(user user) int { - return user.id +extractID := func(user User) int { + return user.ID } // filtering users filteredUsers := WithoutBy(users, extractID, excludedIDs...) -// []user[{id: 1, name: "Alice"}] +// []User[{ID: 1, Name: "Alice"}] ``` ### WithoutEmpty diff --git a/intersect_example_test.go b/intersect_example_test.go index 3535754f..9c07458b 100644 --- a/intersect_example_test.go +++ b/intersect_example_test.go @@ -5,23 +5,23 @@ import ( ) func ExampleWithoutBy() { - type user struct { - id int - name string + type User struct { + ID int + Name string } // original users - users := []user{ - {id: 1, name: "Alice"}, - {id: 2, name: "Bob"}, - {id: 3, name: "Charlie"}, + users := []User{ + {ID: 1, Name: "Alice"}, + {ID: 2, Name: "Bob"}, + {ID: 3, Name: "Charlie"}, } // exclude users with IDs 2 and 3 excludedIDs := []int{2, 3} // extract function to get the user ID - extractID := func(user user) int { - return user.id + extractID := func(user User) int { + return user.ID } // filtering users diff --git a/intersect_test.go b/intersect_test.go index b0b9fd7d..a7063ed3 100644 --- a/intersect_test.go +++ b/intersect_test.go @@ -274,20 +274,20 @@ func TestWithoutBy(t *testing.T) { t.Parallel() is := assert.New(t) - type user struct { - name string - age int + type User struct { + Name string + Age int } - result1 := WithoutBy([]user{{name: "nick"}, {name: "peter"}}, - func(item user) string { - return item.name + result1 := WithoutBy([]User{{Name: "nick"}, {Name: "peter"}}, + func(item User) string { + return item.Name }, "nick", "lily") - result2 := WithoutBy([]user{}, func(item user) int { return item.age }, 1, 2, 3) - result3 := WithoutBy([]user{}, func(item user) string { return item.name }) - is.Equal(result1, []user{{name: "peter"}}) - is.Equal(result2, []user{}) - is.Equal(result3, []user{}) + result2 := WithoutBy([]User{}, func(item User) int { return item.Age }, 1, 2, 3) + result3 := WithoutBy([]User{}, func(item User) string { return item.Name }) + is.Equal(result1, []User{{Name: "peter"}}) + is.Equal(result2, []User{}) + is.Equal(result3, []User{}) } func TestWithoutEmpty(t *testing.T) { From 6b9a75d6e4800b63f9590c434f23825726d3ada7 Mon Sep 17 00:00:00 2001 From: nicklausliu Date: Fri, 23 Aug 2024 10:59:38 +0800 Subject: [PATCH 7/9] feat: fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 15a2bad8..08be882e 100644 --- a/README.md +++ b/README.md @@ -2105,7 +2105,7 @@ It returns a new slice containing only the elements whose keys are not in the ex ```go -type User { +type struct User { ID int Name string } From 6e5a845f9f0b8ba6abbaffa34998bbb6ce1de414 Mon Sep 17 00:00:00 2001 From: nicklausliu Date: Fri, 23 Aug 2024 11:20:46 +0800 Subject: [PATCH 8/9] feat: impove WithoutBy performance --- intersect.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/intersect.go b/intersect.go index 2350eb26..1b3821c9 100644 --- a/intersect.go +++ b/intersect.go @@ -179,9 +179,14 @@ func Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice { // WithoutBy filters a slice by excluding elements whose extracted keys match any in the exclude list. // It returns a new slice containing only the elements whose keys are not in the exclude list. func WithoutBy[T any, K comparable](collection []T, extract func(item T) K, exclude ...K) []T { + blacklist := make(map[K]struct{}, len(exclude)) + for _, e := range exclude { + blacklist[e] = struct{}{} + } + result := make([]T, 0, len(collection)) for _, item := range collection { - if !Contains(exclude, extract(item)) { + if _, ok := blacklist[extract(item)]; !ok { result = append(result, item) } } From c102c037381366a60046a2ea59f37aa8be79636a Mon Sep 17 00:00:00 2001 From: nicklausliu Date: Fri, 23 Aug 2024 11:32:04 +0800 Subject: [PATCH 9/9] feat: replace Without implementation by WithoutBy --- intersect.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/intersect.go b/intersect.go index 1b3821c9..165c401f 100644 --- a/intersect.go +++ b/intersect.go @@ -167,13 +167,7 @@ func Union[T comparable, Slice ~[]T](lists ...Slice) Slice { // Without returns slice excluding all given values. func Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice { - result := make(Slice, 0, len(collection)) - for i := range collection { - if !Contains(exclude, collection[i]) { - result = append(result, collection[i]) - } - } - return result + return WithoutBy(collection, func(item T) T { return item }, exclude...) } // WithoutBy filters a slice by excluding elements whose extracted keys match any in the exclude list.