This commit is contained in:
Ruidy 2023-06-07 14:58:40 +02:00
commit d5426ad3cb
14 changed files with 377 additions and 0 deletions

1
.gitignore vendored
View file

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

13
count.go Normal file
View file

@ -0,0 +1,13 @@
package underscore
// Count returns the number of elements in the slice that satisfy the predicate.
// example: Count([]int{1,2,3,4,5}, func(n int) bool { return n%2 == 0 }) // 2
func Count[T comparable](slice []T, predicate func(T) bool) int {
count := 0
for _, item := range slice {
if predicate(item) {
count++
}
}
return count
}

64
count_test.go Normal file
View file

@ -0,0 +1,64 @@
package underscore
import (
"github.com/stretchr/testify/assert"
"strings"
"testing"
)
func Test_Count_Can_Count_Numbers(t *testing.T) {
numbers := Range(1, 100)
count := Count(numbers, func(n int) bool {
return n%2 == 0
})
assert.Equal(t, 50, count)
}
type People struct {
Name string
Age int
Gender string
}
func Test_Count_Can_Count__People(t *testing.T) {
people := []People{
{Name: "Andy", Age: 43, Gender: "M"},
{Name: "Fred", Age: 33, Gender: "M"},
{Name: "Jack", Age: 23, Gender: "M"},
{Name: "Jill", Age: 43, Gender: "F"},
{Name: "Anna", Age: 33, Gender: "F"},
{Name: "Arya", Age: 23, Gender: "F"},
{Name: "Jane", Age: 13, Gender: "F"},
}
a := Count(people, func(p People) bool {
return strings.HasPrefix(p.Name, "A")
})
assert.Equal(t, 3, a)
females := Count(people, func(p People) bool {
return p.Gender == "F"
})
assert.Equal(t, 4, females)
males := Count(people, func(p People) bool {
return p.Gender == "M"
})
assert.Equal(t, 3, males)
over30 := Count(people, func(p People) bool {
return p.Age > 30
})
assert.Equal(t, 4, over30)
under30 := Count(people, func(p People) bool {
return p.Age < 30
})
assert.Equal(t, 3, under30)
under20 := Count(people, func(p People) bool {
return p.Age < 20
})
assert.Equal(t, 1, under20)
}

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