Adding some new funky functions (#32)

* Adding some new funky functions which I find useful

Created a Tuple struct as some of the new functions require you to return a new slice with two fields which is the result of the new functions

Created the Join, JoinProjection, Range, SumMap, Zip functions, ecah fuction is documented with how it works and had a unit test or maybe more

* Added in an OrderBy function

* Documentation comment for OrderBy which I missed out

* Adding a Unit test for JoinProject function

Updated the comments on the Join & OrderBy functions so they make a little more sense.

Covered an extra test case with the Join test, where the left set has more data than the right and so the Right handside array of the join is empty
This commit is contained in:
Andy Long 2022-09-04 08:21:31 +01:00 committed by GitHub
parent 4042208482
commit 0bc3a54efd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 300 additions and 0 deletions

1
.gitignore vendored
View file

@ -59,3 +59,4 @@ Temporary Items
.apdisk .apdisk
docs/public docs/public
.trivycache/ .trivycache/
.vscode/launch.json

38
join.go Normal file
View file

@ -0,0 +1,38 @@
package underscore
// Joins two slices together and returns a Tuple of [T, []P], the selectors allow you to pick the
// keys you want to use from your struct's to join the sets together
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 []O where O is defined by the output
// of your projection function
// The selectors allow you to pick the keys from your structure to use as the join keys
// While the projection functions allows you to reformat joined datasets
// (Tuple of [T, []P]) into your own struct or type
func JoinProject[L, R, O any, S comparable](
left []L,
right []R,
leftSelector func(L) S,
rightSelector func(R) S,
projection func(Tuple[L, []R]) O) (results []O) {
for _, x := range Join(left, right, leftSelector, rightSelector) {
results = append(results, projection(x))
}
return results
}

45
join_test.go Normal file
View file

@ -0,0 +1,45 @@
package underscore_test
import (
"testing"
u "github.com/rjNemo/underscore"
"github.com/stretchr/testify/assert"
)
var zero = u.Tuple[int, string]{Left: 0, Right: "Zero"}
var one = u.Tuple[int, string]{Left: 1, Right: "One"}
var two = u.Tuple[int, string]{Left: 2, Right: "Two"}
var three = u.Tuple[int, string]{Left: 3, Right: "Three"}
func Test_Join_Can_Join_Two_Slices_Together(t *testing.T) {
var left = []u.Tuple[int, string]{zero, 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: zero, Right: nil},
{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}},
}
assert.Equal(t, want, joined)
}
func Test_Join_Can_Join_and_Project_Two_Slices_Together(t *testing.T) {
var left = []u.Tuple[int, string]{zero, 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 }
project := func(x u.Tuple[u.Tuple[int, string], []u.Tuple[int, string]]) int {
return len(x.Right) // projecting to a could of how many
}
var joined = u.JoinProject(left, right, selector, selector, project)
var want = []int{0, 1, 2, 3}
assert.Equal(t, want, joined)
}

26
orderBy.go Normal file
View file

@ -0,0 +1,26 @@
package underscore
// Orders a slice by a field value within a struct, the predicate allows you
// to pick the fields you want to orderBy. Use > for ASC or < for DESC
// func (left Person, right Person) bool { return left.Age > right.Age }
func OrderBy[T any](list []T, predicate func(T, T) bool) []T {
swaps := true
var tmp T
//todo: replace with a faster algorithm, this one is pretty simple
for swaps {
swaps = false
for i := 0; i < len(list)-1; i++ {
if predicate(list[i], list[i+1]) {
swaps = true
tmp = list[i]
list[i] = list[i+1]
list[i+1] = tmp
}
}
}
return list
}

31
orderBy_test.go Normal file
View file

@ -0,0 +1,31 @@
package underscore_test
import (
"testing"
"github.com/stretchr/testify/assert"
u "github.com/rjNemo/underscore"
)
func Test_OrderBy_Asc(t *testing.T) {
set := u.Range(5, 0)
want := u.Range(0, 5)
result := u.OrderBy(set, func(left int, right int) bool {
return left > right
})
assert.Equal(t, want, result)
}
func Test_OrderBy_Desc(t *testing.T) {
set := u.Range(0, 5)
want := u.Range(5, 0)
result := u.OrderBy(set, func(left int, right int) bool {
return left < right
})
assert.Equal(t, want, result)
}

16
range.go Normal file
View file

@ -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
}

24
range_test.go Normal file
View file

@ -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)
}
}

10
sum.go
View file

@ -9,3 +9,13 @@ func Sum[T constraints.Ordered](values []T) (sum T) {
} }
return sum 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
}

View file

@ -13,3 +13,21 @@ func TestSum(t *testing.T) {
assert.Equal(t, want, u.Sum(nums)) 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 }))
}

6
tuple.go Normal file
View file

@ -0,0 +1,6 @@
package underscore
type Tuple[L, R any] struct {
Left L
Right R
}

20
zip.go Normal file
View file

@ -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
}

65
zip_test.go Normal file
View file

@ -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)
}
}