-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[TASK] TRK-1612 Add DiffWithKeyBuilder functions to gofp library (#28)
* [TASK] TRK-1612 Add DiffWithKeyBuilder functions to gofp library * [TASK] TRK-1612 Update staticcheck version * [TASK] TRK-1612 Fix test with repeated elements in diff methods
- Loading branch information
Showing
5 changed files
with
389 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,7 +26,7 @@ jobs: | |
run: go version | ||
|
||
- name: Install staticcheck | ||
run: go install honnef.co/go/tools/cmd/[email protected] | ||
run: go install honnef.co/go/tools/cmd/[email protected].3 | ||
|
||
- name: Install cover | ||
run: go get -u golang.org/x/tools/cmd/cover | ||
|
@@ -50,4 +50,4 @@ jobs: | |
run: | | ||
cat profile_full.cov | grep -v .pb.go | grep -v mock | grep -v test > profile.cov; | ||
goveralls -coverprofile=profile.cov -service=github || true; | ||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,52 +1,61 @@ | ||
package gofp | ||
|
||
// SymDiff returns symmetric difference of 2 slices. | ||
// The function returns a new slice containing the elements that are in slice1 or slice2 but not in both. | ||
// https://en.wikipedia.org/wiki/Symmetric_difference | ||
func SymDiff[T comparable](slice1, slice2 []T) []T { | ||
checks := make(map[T]int8, len(slice1)+len(slice2)) | ||
idx := make([]T, 0, len(slice1)+len(slice2)) | ||
checks1 := make(map[T]bool) | ||
checks2 := make(map[T]bool) | ||
|
||
// Track unique elements in slice1 and slice2 | ||
for _, v := range slice1 { | ||
checks[v]++ | ||
idx = append(idx, v) | ||
checks1[v] = true | ||
} | ||
for _, v := range slice2 { | ||
checks[v]++ | ||
if checks[v] == 1 { | ||
idx = append(idx, v) | ||
checks2[v] = true | ||
} | ||
|
||
outer := make([]T, 0) | ||
|
||
// Add elements in slice1 but not in slice2 | ||
for v := range checks1 { | ||
if !checks2[v] { | ||
outer = append(outer, v) | ||
} | ||
} | ||
|
||
outer := make([]T, 0, len(slice1)+len(slice2)) | ||
for _, id := range idx { | ||
if checks[id] == 1 { | ||
outer = append(outer, id) | ||
// Add elements in slice2 but not in slice1 | ||
for v := range checks2 { | ||
if !checks1[v] { | ||
outer = append(outer, v) | ||
} | ||
} | ||
|
||
return outer | ||
} | ||
|
||
// DiffLeft returns left diff of 2 slices. | ||
// The function returns a new slice containing the elements that are in slice1 but not in slice2. | ||
func DiffLeft[T comparable](slice1, slice2 []T) []T { | ||
checks := make(map[T]int8, len(slice1)) | ||
for _, v := range slice1 { | ||
checks[v]++ | ||
} | ||
checks := make(map[T]bool) | ||
for _, v := range slice2 { | ||
checks[v]++ | ||
checks[v] = true | ||
} | ||
|
||
outer := make([]T, 0, len(slice1)) | ||
for _, id := range slice1 { | ||
if checks[id] == 1 { | ||
outer = append(outer, id) | ||
var outer []T | ||
used := make(map[T]bool) | ||
for _, v := range slice1 { | ||
if !checks[v] && !used[v] { | ||
used[v] = true | ||
outer = append(outer, v) | ||
} | ||
} | ||
|
||
return outer | ||
} | ||
|
||
// DiffRight returns right diff of 2 slices. | ||
// The function returns a new slice containing the elements that are in slice2 but not in slice1. | ||
func DiffRight[T comparable](slice1, slice2 []T) []T { | ||
return DiffLeft(slice2, slice1) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
package gofp | ||
|
||
// KeyBuilder is a type for a function that maps a value of type T to a comparable value of type C. | ||
// This can be used for constructing a comparable key from an object, allowing it to be used in comparison operations. | ||
type KeyBuilder[T any, C comparable] func(T) C | ||
|
||
// SymDiffWithKeyBuilder calculates the symmetric difference between two slices. | ||
// It uses a KeyBuilder function to map values in the slices to a comparable key, which is used for the comparison. | ||
// The function returns a new slice containing the elements that are in slice1 or slice2 but not in both. | ||
// | ||
// Notes: | ||
// - This function doesn't return an ordered response. | ||
// - The KeyBuilder function is used to identify unique elements in the slices for comparison. | ||
// Therefore, it's important that the KeyBuilder function produces unique keys for unique elements. | ||
// If the keys are not unique, the function may not provide accurate results. | ||
// For example, consider two slices of a struct with attributes 'Name' and 'Age'. If the KeyBuilder function | ||
// uses only the 'Name' attribute for creating the key, two elements with the same 'Name' but different 'Age' | ||
// will be considered identical, which may not be the intended behavior. | ||
// | ||
// Example: | ||
// | ||
// slice1 := []Person{ | ||
// {Name: "Alice", Age: 25}, | ||
// {Name: "Bob", Age: 30}, | ||
// {Name: "Alice", Age: 40}, | ||
// } | ||
// slice2 := []Person{ | ||
// {Name: "Charlie", Age: 35}, | ||
// } | ||
// kb := func(p Person) string { | ||
// return p.Name | ||
// } | ||
// result := SymDiffWithKeyBuilder(slice1, slice2, kb) | ||
// | ||
// The 'result' will be: | ||
// | ||
// []Person{ | ||
// {Name: "Alice", Age: 25}, | ||
// {Name: "Bob", Age: 30}, | ||
// {Name: "Charlie", Age: 35}, | ||
// } | ||
// | ||
// Note that for the 'Alice' element, the 'Age' attribute of 25 was used and the 40 was omitted. | ||
func SymDiffWithKeyBuilder[T any, C comparable](slice1, slice2 []T, kb KeyBuilder[T, C]) []T { | ||
checks1, checks2 := make(Set[C]), make(Set[C]) | ||
valueByKey := make(map[C]T) | ||
for _, v := range slice1 { | ||
k := kb(v) | ||
checks1.Add(k) | ||
valueByKey[k] = v | ||
} | ||
|
||
for _, v := range slice2 { | ||
checks2.Add(kb(v)) | ||
k := kb(v) | ||
if _, ok := valueByKey[k]; !ok { | ||
valueByKey[k] = v | ||
} | ||
} | ||
|
||
var result []T | ||
for k := range checks1 { | ||
if !checks2.Has(k) { | ||
result = append(result, valueByKey[k]) | ||
} | ||
} | ||
for k := range checks2 { | ||
if !checks1.Has(k) { | ||
result = append(result, valueByKey[k]) | ||
} | ||
} | ||
|
||
return result | ||
} | ||
|
||
// DiffLeftWithKeyBuilder calculates the difference between two slices, i.e., elements that are in the first slice, but not in the second. | ||
// It uses a KeyBuilder function to map values in the slices to a comparable key, which is used for the comparison. | ||
// The function returns a new slice containing the elements that are in slice1 but not in slice2. | ||
// | ||
// Notes: | ||
// - This function does not retain the order of elements in the original slices. The order of elements in the resulting slice depends on the order of iteration over slice1. | ||
// - The KeyBuilder function is used to identify unique elements in the slices for comparison. | ||
// Therefore, it's important that the KeyBuilder function produces unique keys for unique elements. | ||
// If the keys are not unique, the function may not provide accurate results. | ||
// For example, consider two slices of a struct with attributes 'Name' and 'Age'. If the KeyBuilder function | ||
// uses only the 'Name' attribute for creating the key, two elements with the same 'Name' but different 'Age' | ||
// will be considered identical, which may not be the intended behavior. | ||
// | ||
// Example: | ||
// | ||
// slice1 := []Person{ | ||
// {Name: "Alice", Age: 25}, | ||
// {Name: "Bob", Age: 30}, | ||
// {Name: "Alice", Age: 40}, | ||
// {Name: "Charlie", Age: 35}, | ||
// } | ||
// slice2 := []Person{ | ||
// {Name: "Charlie", Age: 20}, | ||
// } | ||
// kb := func(p Person) string { | ||
// return p.Name | ||
// } | ||
// result := DiffLeftWithKeyBuilder(slice1, slice2, kb) | ||
// | ||
// The 'result' will be: | ||
// | ||
// []Person{ | ||
// {Name: "Bob", Age: 30}, | ||
// {Name: "Alice", Age: 25}, | ||
// } | ||
// | ||
// Note that for the 'Alice' element, the 'Age' attribute of 25 was used and the 40 was omitted. | ||
func DiffLeftWithKeyBuilder[T any, C comparable](slice1, slice2 []T, kb KeyBuilder[T, C]) []T { | ||
checks := make(Set[C]) | ||
for _, v := range slice2 { | ||
checks.Add(kb(v)) | ||
} | ||
|
||
outer := make([]T, 0) | ||
used := make(Set[C]) | ||
for _, v := range slice1 { | ||
k := kb(v) | ||
if !checks.Has(k) && !used.Has(k) { | ||
outer = append(outer, v) | ||
used.Add(k) | ||
} | ||
} | ||
|
||
return outer | ||
} | ||
|
||
// DiffRightWithKeyBuilder calculates the difference between two slices, i.e., elements that are in the second slice, but not in the first. | ||
// It uses a KeyBuilder function to map values in the slices to a comparable key, which is used for the comparison. | ||
// The function returns a new slice containing the elements that are in slice2 but not in slice1. | ||
// | ||
// Notes: | ||
// - This function does not retain the order of elements in the original slices. The order of elements in the resulting slice depends on the order of iteration over slice2. | ||
// - The KeyBuilder function is used to identify unique elements in the slices for comparison. | ||
// Therefore, it's important that the KeyBuilder function produces unique keys for unique elements. | ||
// If the keys are not unique, the function may not provide accurate results. | ||
// For example, consider two slices of a struct with attributes 'Name' and 'Age'. If the KeyBuilder function | ||
// uses only the 'Name' attribute for creating the key, two elements with the same 'Name' but different 'Age' | ||
// will be considered identical, which may not be the intended behavior. | ||
// | ||
// Example: | ||
// | ||
// slice1 := []Person{ | ||
// {Name: "Charlie", Age: 20}, | ||
// } | ||
// slice2 := []Person{ | ||
// {Name: "Alice", Age: 25}, | ||
// {Name: "Bob", Age: 30}, | ||
// {Name: "Alice", Age: 40}, | ||
// {Name: "Charlie", Age: 35}, | ||
// } | ||
// kb := func(p Person) string { | ||
// return p.Name | ||
// } | ||
// result := DiffRightWithKeyBuilder(slice1, slice2, kb) | ||
// | ||
// The 'result' will be: | ||
// | ||
// []Person{ | ||
// {Name: "Bob", Age: 30}, | ||
// {Name: "Alice", Age: 25}, | ||
// } | ||
// | ||
// Note that for the 'Alice' element, the 'Age' attribute of 25 was used and the 40 was omitted. | ||
func DiffRightWithKeyBuilder[T any, C comparable](slice1, slice2 []T, kb KeyBuilder[T, C]) []T { | ||
return DiffLeftWithKeyBuilder(slice2, slice1, kb) | ||
} |
Oops, something went wrong.