mirror of
https://github.com/rjNemo/underscore
synced 2026-06-06 02:26:42 +00:00
Merge branch 'main' of https://github.com/rjNemo/underscore
This commit is contained in:
commit
d5426ad3cb
14 changed files with 377 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -59,3 +59,4 @@ Temporary Items
|
|||
.apdisk
|
||||
docs/public
|
||||
.trivycache/
|
||||
.vscode/launch.json
|
||||
|
|
|
|||
13
count.go
Normal file
13
count.go
Normal 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
64
count_test.go
Normal 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
38
join.go
Normal 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
45
join_test.go
Normal 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
26
orderBy.go
Normal 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
31
orderBy_test.go
Normal 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
16
range.go
Normal 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
24
range_test.go
Normal 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
10
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
|
||||
}
|
||||
|
|
|
|||
18
sum_test.go
18
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 }))
|
||||
}
|
||||
|
|
|
|||
6
tuple.go
Normal file
6
tuple.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package underscore
|
||||
|
||||
type Tuple[L, R any] struct {
|
||||
Left L
|
||||
Right R
|
||||
}
|
||||
20
zip.go
Normal file
20
zip.go
Normal 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
65
zip_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue