diff --git a/intersect.go b/intersect.go index cf6cab3d..4629829b 100644 --- a/intersect.go +++ b/intersect.go @@ -141,6 +141,48 @@ func Difference[T comparable](list1 []T, list2 []T) ([]T, []T) { return left, right } +// DifferenceBy returns the difference between two collections: the elements from list1 that are not present in list2, +// and the elements from list2 that are not present in list1, based on the provided predicate function. +// The predicate function is used to compare elements from both lists. +// It takes two arguments of type T and returns a boolean value indicating whether the elements are considered equal. +// The returned slices maintain the order of the elements in the original lists. +func DifferenceBy[T any]( + list1 []T, + list2 []T, + predicate func(T, T) bool, +) ([]T, []T) { + left := []T{} + right := []T{} + + for _, aValue := range list1 { + found := false + for _, bValue := range list2 { + if predicate(aValue, bValue) { + found = true + break + } + } + if !found { + left = append(left, aValue) + } + } + + for _, bValue := range list2 { + found := false + for _, aValue := range list1 { + if predicate(bValue, aValue) { + found = true + break + } + } + if !found { + right = append(right, bValue) + } + } + + return left, right +} + // Union returns all distinct elements from given collections. // result returns will not change the order of elements relatively. func Union[T comparable](lists ...[]T) []T { diff --git a/intersect_test.go b/intersect_test.go index 339dbcbb..d8644288 100644 --- a/intersect_test.go +++ b/intersect_test.go @@ -206,6 +206,136 @@ func TestDifference(t *testing.T) { is.Equal(right3, []int{}) } +func TestDifferenceBy(t *testing.T) { + t.Parallel() + is := assert.New(t) + + testCases := []struct { + name string + list1 []int + list2 []int + predicate func(list1 int, list2 int) bool + expectedLeft []int + expectedRight []int + }{ + { + name: "differences in left and right", + list1: []int{0, 1, 2, 3, 4, 5}, + list2: []int{0, 2, 6}, + predicate: func(l int, r int) bool { + return l == r + }, + expectedLeft: []int{1, 3, 4, 5}, + expectedRight: []int{6}, + }, + { + name: "no differences", + list1: []int{0, 1, 2, 3, 4, 5}, + list2: []int{0, 1, 2, 3, 4, 5}, + predicate: func(l int, r int) bool { + return l == r + }, + expectedLeft: []int{}, + expectedRight: []int{}, + }, + { + name: "differences in left only", + list1: []int{0, 1, 2, 3, 4, 5}, + list2: []int{0, 1, 2}, + predicate: func(l int, r int) bool { + return l == r + }, + expectedLeft: []int{3, 4, 5}, + expectedRight: []int{}, + }, + { + name: "differences in right only", + list1: []int{0, 1, 2}, + list2: []int{0, 1, 2, 3, 4, 5}, + predicate: func(l int, r int) bool { + return l == r + }, + expectedLeft: []int{}, + expectedRight: []int{3, 4, 5}, + }, + { + name: "differences in right only, (list1 is empty)", + list1: []int{}, + list2: []int{0, 1, 2, 3, 4, 5}, + predicate: func(l int, r int) bool { + return l == r + }, + expectedLeft: []int{}, + expectedRight: []int{0, 1, 2, 3, 4, 5}, + }, + { + name: "differences in left only (list2 is empty)", + list1: []int{0, 1, 2, 3, 4, 5}, + list2: []int{}, + predicate: func(l int, r int) bool { + return l == r + }, + expectedLeft: []int{0, 1, 2, 3, 4, 5}, + expectedRight: []int{}, + }, + { + name: "no differences (both list1 and list2 are empty)", + list1: []int{}, + list2: []int{}, + predicate: func(l int, r int) bool { + return l == r + }, + expectedLeft: []int{}, + expectedRight: []int{}, + }, + { + name: "no differences (both list1 and list2 are nil)", + list1: nil, + list2: nil, + predicate: func(l int, r int) bool { + return l == r + }, + expectedLeft: []int{}, + expectedRight: []int{}, + }, + { + name: "differences in right only (list1 is nil)", + list1: nil, + list2: []int{1, 2, 3}, + predicate: func(l int, r int) bool { + return l == r + }, + expectedLeft: []int{}, + expectedRight: []int{1, 2, 3}, + }, + { + name: "differences in left only (list2 is nil)", + list1: []int{1, 2, 3}, + list2: nil, + predicate: func(l int, r int) bool { + return l == r + }, + expectedLeft: []int{1, 2, 3}, + expectedRight: []int{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + left, right := DifferenceBy( + testCase.list1, + testCase.list2, + func(l int, r int) bool { + return l == r + }, + ) + is.Equal(testCase.expectedLeft, left, testCase.name) + is.Equal(testCase.expectedRight, right, testCase.name) + }) + } + +} + func TestUnion(t *testing.T) { t.Parallel() is := assert.New(t)