diff --git a/.gitignore b/.gitignore index 0fd6007..804df32 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,4 @@ Temporary Items .apdisk docs/public .trivycache/ +.vscode/launch.json diff --git a/join.go b/join.go new file mode 100644 index 0000000..1b2b60b --- /dev/null +++ b/join.go @@ -0,0 +1,39 @@ +package underscore + +// Joins two slices together and returns a Tuple of [T, []P], the selectors allow you to pick the +// keys from your structure you would like to join the sets together with +func Join[T, P any, S comparable]( + left []T, + right []P, + leftSelector func(T) S, + rightSelector func(P) S) []Tuple[T, []P] { + + var results = make([]Tuple[T, []P], 0, len(left)) + for _, l := range left { + var matches = Filter(right, func(r P) bool { return leftSelector(l) == rightSelector(r) }) + var tuple = Tuple[T, []P]{Left: l, Right: matches} + results = append(results, tuple) + } + + return results +} + +// Joins two slices together and returns a []R where R is defined by the output of your projection +// function +// The selectors allow you to pick the keys from your structure you would like to join the sets +// together with. +// While the projection functions allows you to reformat joined datasets contains in the +// Tuple of [T, []P] into your own structure or type +func JoinProject[T, P, R any, S comparable]( + left []T, + right []P, + leftSelector func(T) S, + rightSelector func(P) S, + projection func(Tuple[T, []P]) R) (results []R) { + + for _, x := range Join(left, right, leftSelector, rightSelector) { + results = append(results, projection(x)) + } + + return results +} diff --git a/join_test.go b/join_test.go new file mode 100644 index 0000000..fbf0d4d --- /dev/null +++ b/join_test.go @@ -0,0 +1,30 @@ +package underscore_test + +import ( + "reflect" + "testing" + + u "github.com/rjNemo/underscore" +) + +func Test_Join_Can_Join_Two_Slices_Together(t *testing.T) { + one := u.Tuple[int, string]{Left: 1, Right: "One"} + two := u.Tuple[int, string]{Left: 2, Right: "Two"} + three := u.Tuple[int, string]{Left: 3, Right: "Three"} + + var left = []u.Tuple[int, string]{one, two, three} + var right = []u.Tuple[int, string]{one, three, two, three, two, three} + + selector := func(x u.Tuple[int, string]) int { return x.Left } + + var joined = u.Join(left, right, selector, selector) + var want = []u.Tuple[u.Tuple[int, string], []u.Tuple[int, string]]{ + {Left: one, Right: []u.Tuple[int, string]{one}}, + {Left: two, Right: []u.Tuple[int, string]{two, two}}, + {Left: three, Right: []u.Tuple[int, string]{three, three, three}}, + } + + if !reflect.DeepEqual(joined, want) { + t.Errorf("Expected to get %v but we got %v instead", want, joined) + } +} diff --git a/range.go b/range.go new file mode 100644 index 0000000..204c6b8 --- /dev/null +++ b/range.go @@ -0,0 +1,16 @@ +package underscore + +// Creates a sequence of numbers, i.e. u.Range(0, 3) = [0 1 2 3], while u.Range(3, 0) = [3 2 1 0] +func Range(start int, end int) (result []int) { + if start < end { + for i := start; i <= end; i++ { + result = append(result, i) + } + } else { + for i := start; i >= end; i-- { + result = append(result, i) + } + } + + return result +} diff --git a/range_test.go b/range_test.go new file mode 100644 index 0000000..f59ee5c --- /dev/null +++ b/range_test.go @@ -0,0 +1,24 @@ +package underscore_test + +import ( + "reflect" + "testing" + + u "github.com/rjNemo/underscore" +) + +func Test_Range_Creates_Slices(t *testing.T) { + range1 := u.Range(0, 5) + want1 := []int{0, 1, 2, 3, 4, 5} + + if !reflect.DeepEqual(range1, want1) { + t.Errorf("Expected the result to be %v but we got %v", want1, range1) + } + + range2 := u.Range(5, 0) + want2 := []int{5, 4, 3, 2, 1, 0} + + if !reflect.DeepEqual(range2, want2) { + t.Errorf("Expected the result to be %v but we got %v", want2, range2) + } +} diff --git a/sum.go b/sum.go index 3d2b5db..0cc9652 100644 --- a/sum.go +++ b/sum.go @@ -9,3 +9,13 @@ func Sum[T constraints.Ordered](values []T) (sum T) { } return sum } + +// Sums the values you select from your struct, basically a sort cut instead of +// having to perform a u.Map followed by a u.Sum +func SumMap[T any, R constraints.Ordered](list []T, selector func(T) R) (sum R) { + for _, v := range list { + sum += selector(v) + } + + return sum +} diff --git a/sum_test.go b/sum_test.go index 05a2a8a..4d5556d 100644 --- a/sum_test.go +++ b/sum_test.go @@ -13,3 +13,21 @@ func TestSum(t *testing.T) { assert.Equal(t, want, u.Sum(nums)) } + +func TestSumMap(t *testing.T) { + nums := []u.Tuple[string, int]{ + {"zero", 0}, + {"one", 1}, + {"two", 2}, + {"three", 3}, + {"four", 4}, + {"five", 5}, + {"six", 6}, + {"seven", 7}, + {"eight", 8}, + {"nine", 9}, + } + want := 45 + + assert.Equal(t, want, u.SumMap(nums, func(item u.Tuple[string, int]) int { return item.Right })) +} diff --git a/tuple.go b/tuple.go new file mode 100644 index 0000000..a229f52 --- /dev/null +++ b/tuple.go @@ -0,0 +1,6 @@ +package underscore + +type Tuple[L, R any] struct { + Left L + Right R +} diff --git a/zip.go b/zip.go new file mode 100644 index 0000000..ca53e62 --- /dev/null +++ b/zip.go @@ -0,0 +1,20 @@ +package underscore + +// Zips two slices togther so all the elements of left slice are attached to the corresponding +// elements of the right slice, i.e. [one two three] [1 2 3 4] = [{one, 1} {two, 2} {three, 3}] +// the returned data will be the size of the smallest slice +func Zip[L any, R any](left []L, right []R) []Tuple[L, R] { + shortest := 0 + if len(left) < len(right) { + shortest = len(left) + } else { + shortest = len(right) + } + + results := make([]Tuple[L, R], shortest) + for i := 0; i < shortest; i++ { + results[i] = Tuple[L, R]{Left: left[i], Right: right[i]} + } + + return results +} diff --git a/zip_test.go b/zip_test.go new file mode 100644 index 0000000..66b8ddf --- /dev/null +++ b/zip_test.go @@ -0,0 +1,65 @@ +package underscore_test + +import ( + "reflect" + "testing" + + u "github.com/rjNemo/underscore" +) + +func Test_Zip_Can_Zip_Two_Equal_Sized_Slices(t *testing.T) { + left := []string{"Left 1", "Left 2", "Left 3"} + right := []int{1, 2, 3} + + var zipped = u.Zip(left, right) + + want := []u.Tuple[string, int]{ + {Left: "Left 1", Right: 1}, + {Left: "Left 2", Right: 2}, + {Left: "Left 3", Right: 3}, + } + + if !reflect.DeepEqual(zipped, want) { + t.Errorf("Expected the result to be %v but we got %v", want, zipped) + } +} + +func Test_Zip_Can_Zip_Two_Different_Sized_Slices_Left_Larger(t *testing.T) { + left := []string{"Left 1", "Left 2", "Left 3", "Left 4"} + right := []int{1, 2, 3} + + var zipped = u.Zip(left, right) + if len(zipped) != 3 { + t.Errorf("Expected the result of Zip(left, right) to have a length of 3 but got %v", len(zipped)) + } + + want := []u.Tuple[string, int]{ + {Left: "Left 1", Right: 1}, + {Left: "Left 2", Right: 2}, + {Left: "Left 3", Right: 3}, + } + + if !reflect.DeepEqual(zipped, want) { + t.Errorf("Expected the result to be %v but we got %v", want, zipped) + } +} + +func Test_Zip_Can_Zip_Two_Different_Sized_Slices_Right_Larger(t *testing.T) { + left := []string{"Left 1", "Left 2", "Left 3"} + right := []int{1, 2, 3, 4} + + var zipped = u.Zip(left, right) + if len(zipped) != 3 { + t.Errorf("Expected the result of Zip(left, right) to have a length of 3 but got %v", len(zipped)) + } + + want := []u.Tuple[string, int]{ + {Left: "Left 1", Right: 1}, + {Left: "Left 2", Right: 2}, + {Left: "Left 3", Right: 3}, + } + + if !reflect.DeepEqual(zipped, want) { + t.Errorf("Expected the result to be %v but we got %v", want, zipped) + } +}