Skip to content

Commit d5426ad

Browse files
committed
Merge branch 'main' of https://github.com/rjNemo/underscore
2 parents 64fcfee + 291df4f commit d5426ad

File tree

14 files changed

+377
-0
lines changed

14 files changed

+377
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,4 @@ Temporary Items
5959
.apdisk
6060
docs/public
6161
.trivycache/
62+
.vscode/launch.json

count.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package underscore
2+
3+
// Count returns the number of elements in the slice that satisfy the predicate.
4+
// example: Count([]int{1,2,3,4,5}, func(n int) bool { return n%2 == 0 }) // 2
5+
func Count[T comparable](slice []T, predicate func(T) bool) int {
6+
count := 0
7+
for _, item := range slice {
8+
if predicate(item) {
9+
count++
10+
}
11+
}
12+
return count
13+
}

count_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package underscore
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"strings"
6+
"testing"
7+
)
8+
9+
func Test_Count_Can_Count_Numbers(t *testing.T) {
10+
numbers := Range(1, 100)
11+
count := Count(numbers, func(n int) bool {
12+
return n%2 == 0
13+
})
14+
15+
assert.Equal(t, 50, count)
16+
}
17+
18+
type People struct {
19+
Name string
20+
Age int
21+
Gender string
22+
}
23+
24+
func Test_Count_Can_Count__People(t *testing.T) {
25+
people := []People{
26+
{Name: "Andy", Age: 43, Gender: "M"},
27+
{Name: "Fred", Age: 33, Gender: "M"},
28+
{Name: "Jack", Age: 23, Gender: "M"},
29+
{Name: "Jill", Age: 43, Gender: "F"},
30+
{Name: "Anna", Age: 33, Gender: "F"},
31+
{Name: "Arya", Age: 23, Gender: "F"},
32+
{Name: "Jane", Age: 13, Gender: "F"},
33+
}
34+
35+
a := Count(people, func(p People) bool {
36+
return strings.HasPrefix(p.Name, "A")
37+
})
38+
assert.Equal(t, 3, a)
39+
40+
females := Count(people, func(p People) bool {
41+
return p.Gender == "F"
42+
})
43+
assert.Equal(t, 4, females)
44+
45+
males := Count(people, func(p People) bool {
46+
return p.Gender == "M"
47+
})
48+
assert.Equal(t, 3, males)
49+
50+
over30 := Count(people, func(p People) bool {
51+
return p.Age > 30
52+
})
53+
assert.Equal(t, 4, over30)
54+
55+
under30 := Count(people, func(p People) bool {
56+
return p.Age < 30
57+
})
58+
assert.Equal(t, 3, under30)
59+
60+
under20 := Count(people, func(p People) bool {
61+
return p.Age < 20
62+
})
63+
assert.Equal(t, 1, under20)
64+
}

join.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package underscore
2+
3+
// Joins two slices together and returns a Tuple of [T, []P], the selectors allow you to pick the
4+
// keys you want to use from your struct's to join the sets together
5+
func Join[T, P any, S comparable](
6+
left []T,
7+
right []P,
8+
leftSelector func(T) S,
9+
rightSelector func(P) S) []Tuple[T, []P] {
10+
11+
var results = make([]Tuple[T, []P], 0, len(left))
12+
for _, l := range left {
13+
var matches = Filter(right, func(r P) bool { return leftSelector(l) == rightSelector(r) })
14+
var tuple = Tuple[T, []P]{Left: l, Right: matches}
15+
results = append(results, tuple)
16+
}
17+
18+
return results
19+
}
20+
21+
// Joins two slices together and returns a []O where O is defined by the output
22+
// of your projection function
23+
// The selectors allow you to pick the keys from your structure to use as the join keys
24+
// While the projection functions allows you to reformat joined datasets
25+
// (Tuple of [T, []P]) into your own struct or type
26+
func JoinProject[L, R, O any, S comparable](
27+
left []L,
28+
right []R,
29+
leftSelector func(L) S,
30+
rightSelector func(R) S,
31+
projection func(Tuple[L, []R]) O) (results []O) {
32+
33+
for _, x := range Join(left, right, leftSelector, rightSelector) {
34+
results = append(results, projection(x))
35+
}
36+
37+
return results
38+
}

join_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package underscore_test
2+
3+
import (
4+
"testing"
5+
6+
u "github.com/rjNemo/underscore"
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
var zero = u.Tuple[int, string]{Left: 0, Right: "Zero"}
11+
var one = u.Tuple[int, string]{Left: 1, Right: "One"}
12+
var two = u.Tuple[int, string]{Left: 2, Right: "Two"}
13+
var three = u.Tuple[int, string]{Left: 3, Right: "Three"}
14+
15+
func Test_Join_Can_Join_Two_Slices_Together(t *testing.T) {
16+
var left = []u.Tuple[int, string]{zero, one, two, three}
17+
var right = []u.Tuple[int, string]{one, three, two, three, two, three}
18+
19+
selector := func(x u.Tuple[int, string]) int { return x.Left }
20+
21+
var joined = u.Join(left, right, selector, selector)
22+
var want = []u.Tuple[u.Tuple[int, string], []u.Tuple[int, string]]{
23+
{Left: zero, Right: nil},
24+
{Left: one, Right: []u.Tuple[int, string]{one}},
25+
{Left: two, Right: []u.Tuple[int, string]{two, two}},
26+
{Left: three, Right: []u.Tuple[int, string]{three, three, three}},
27+
}
28+
29+
assert.Equal(t, want, joined)
30+
}
31+
32+
func Test_Join_Can_Join_and_Project_Two_Slices_Together(t *testing.T) {
33+
var left = []u.Tuple[int, string]{zero, one, two, three}
34+
var right = []u.Tuple[int, string]{one, three, two, three, two, three}
35+
36+
selector := func(x u.Tuple[int, string]) int { return x.Left }
37+
project := func(x u.Tuple[u.Tuple[int, string], []u.Tuple[int, string]]) int {
38+
return len(x.Right) // projecting to a could of how many
39+
}
40+
41+
var joined = u.JoinProject(left, right, selector, selector, project)
42+
var want = []int{0, 1, 2, 3}
43+
44+
assert.Equal(t, want, joined)
45+
}

orderBy.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package underscore
2+
3+
// Orders a slice by a field value within a struct, the predicate allows you
4+
// to pick the fields you want to orderBy. Use > for ASC or < for DESC
5+
// func (left Person, right Person) bool { return left.Age > right.Age }
6+
func OrderBy[T any](list []T, predicate func(T, T) bool) []T {
7+
swaps := true
8+
var tmp T
9+
10+
//todo: replace with a faster algorithm, this one is pretty simple
11+
for swaps {
12+
swaps = false
13+
14+
for i := 0; i < len(list)-1; i++ {
15+
if predicate(list[i], list[i+1]) {
16+
swaps = true
17+
tmp = list[i]
18+
19+
list[i] = list[i+1]
20+
list[i+1] = tmp
21+
}
22+
}
23+
}
24+
25+
return list
26+
}

orderBy_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package underscore_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
u "github.com/rjNemo/underscore"
9+
)
10+
11+
func Test_OrderBy_Asc(t *testing.T) {
12+
set := u.Range(5, 0)
13+
want := u.Range(0, 5)
14+
15+
result := u.OrderBy(set, func(left int, right int) bool {
16+
return left > right
17+
})
18+
19+
assert.Equal(t, want, result)
20+
}
21+
22+
func Test_OrderBy_Desc(t *testing.T) {
23+
set := u.Range(0, 5)
24+
want := u.Range(5, 0)
25+
26+
result := u.OrderBy(set, func(left int, right int) bool {
27+
return left < right
28+
})
29+
30+
assert.Equal(t, want, result)
31+
}

range.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package underscore
2+
3+
// Creates a sequence of numbers, i.e. u.Range(0, 3) = [0 1 2 3], while u.Range(3, 0) = [3 2 1 0]
4+
func Range(start int, end int) (result []int) {
5+
if start < end {
6+
for i := start; i <= end; i++ {
7+
result = append(result, i)
8+
}
9+
} else {
10+
for i := start; i >= end; i-- {
11+
result = append(result, i)
12+
}
13+
}
14+
15+
return result
16+
}

range_test.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package underscore_test
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
u "github.com/rjNemo/underscore"
8+
)
9+
10+
func Test_Range_Creates_Slices(t *testing.T) {
11+
range1 := u.Range(0, 5)
12+
want1 := []int{0, 1, 2, 3, 4, 5}
13+
14+
if !reflect.DeepEqual(range1, want1) {
15+
t.Errorf("Expected the result to be %v but we got %v", want1, range1)
16+
}
17+
18+
range2 := u.Range(5, 0)
19+
want2 := []int{5, 4, 3, 2, 1, 0}
20+
21+
if !reflect.DeepEqual(range2, want2) {
22+
t.Errorf("Expected the result to be %v but we got %v", want2, range2)
23+
}
24+
}

sum.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,13 @@ func Sum[T constraints.Ordered](values []T) (sum T) {
99
}
1010
return sum
1111
}
12+
13+
// Sums the values you select from your struct, basically a sort cut instead of
14+
// having to perform a u.Map followed by a u.Sum
15+
func SumMap[T any, R constraints.Ordered](list []T, selector func(T) R) (sum R) {
16+
for _, v := range list {
17+
sum += selector(v)
18+
}
19+
20+
return sum
21+
}

0 commit comments

Comments
 (0)