Initial commit of modash

This was taken from github.com/lmika/gopkgs/fp
This commit is contained in:
Leon Mika 2025-01-27 13:19:52 +11:00
commit a20530ddfd
20 changed files with 425 additions and 0 deletions

18
moslice/filter.go Normal file
View file

@ -0,0 +1,18 @@
package moslice
// Filter returns a slice containing all the elements of ts for which the passed in
// predicate returns true. If no items match the predicate, the function will return
// an empty slice. If ts is nil, the function will also return nil.
func Filter[T any](ts []T, predicate func(t T) bool) []T {
if ts == nil {
return nil
}
filteredTs := make([]T, 0)
for _, t := range ts {
if predicate(t) {
filteredTs = append(filteredTs, t)
}
}
return filteredTs
}

29
moslice/filter_test.go Normal file
View file

@ -0,0 +1,29 @@
package moslice_test
import (
"lmika.dev/pkg/modash/moslice"
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFilter(t *testing.T) {
var (
ints = []int{1, 2, 3, 4, 5}
strs = []string{"foo", "bar", "baz"}
)
t.Run("should filter items matching the predicate", func(t *testing.T) {
assert.Equal(t, []int{2, 4}, moslice.Filter(ints, func(x int) bool { return x%2 == 0 }))
assert.Equal(t, []string{"bar", "baz"}, moslice.Filter(strs, func(x string) bool { return strings.Contains(x, "b") }))
})
t.Run("should moslice nil if the passed in slice is nil", func(t *testing.T) {
assert.Nil(t, moslice.Filter(nil, func(x int) bool { return x%2 == 0 }))
})
t.Run("should return empty slice if the passed in slice is empty slice", func(t *testing.T) {
assert.Equal(t, []int{}, moslice.Filter([]int{}, func(x int) bool { return x%2 == 0 }))
})
}

21
moslice/find.go Normal file
View file

@ -0,0 +1,21 @@
package moslice
func Contains[T comparable](ts []T, needle T) bool {
for _, t := range ts {
if t == needle {
return true
}
}
return false
}
func FindWhere[T comparable](ts []T, predicate func(t T) bool) (T, bool) {
var zeroT T
for _, t := range ts {
if predicate(t) {
return t, true
}
}
return zeroT, false
}

25
moslice/find_test.go Normal file
View file

@ -0,0 +1,25 @@
package moslice_test
import (
"lmika.dev/pkg/modash/moslice"
"testing"
"github.com/stretchr/testify/assert"
)
func TestContains(t *testing.T) {
var (
ints = []int{1, 2, 3}
strs = []string{"a", "b", "c"}
)
t.Run("should find items in the slice", func(t *testing.T) {
assert.True(t, moslice.Contains(ints, 2))
assert.True(t, moslice.Contains(strs, "c"))
})
t.Run("should return false if items not in slice", func(t *testing.T) {
assert.False(t, moslice.Contains(ints, 131))
assert.False(t, moslice.Contains(strs, "bla"))
})
}

19
moslice/flatten.go Normal file
View file

@ -0,0 +1,19 @@
package moslice
func Flatten[T any](tss [][]T) []T {
if len(tss) == 0 {
return nil
}
entireLen := 0
for _, ts := range tss {
entireLen += len(ts)
}
newTs := make([]T, 0, entireLen)
for _, ts := range tss {
newTs = append(newTs, ts...)
}
return newTs
}

13
moslice/foreach.go Normal file
View file

@ -0,0 +1,13 @@
package moslice
// ForEachWithError runs the passed in function for each element of T. If an error
// is encountered, the error is returned immediately and any subsequence elements
// will not be processed.
func ForEachWithError[T any](ts []T, fn func(t T) error) error {
for _, t := range ts {
if err := fn(t); err != nil {
return err
}
}
return nil
}

33
moslice/map.go Normal file
View file

@ -0,0 +1,33 @@
package moslice
// Map returns a new slice containing the elements of ts transformed by the passed in function.
func Map[T, U any](ts []T, fn func(t T) U) (us []U) {
if ts == nil {
return nil
}
us = make([]U, len(ts))
for i, t := range ts {
us[i] = fn(t)
}
return us
}
// Map returns a new slice containing the elements of ts transformed by the passed in function, which
// can either either a mapped value of U, or an error. If the mapping function returns an error, MapWithError
// will return nil and the returned error.
func MapWithError[T, U any](ts []T, fn func(t T) (U, error)) (us []U, err error) {
if ts == nil {
return nil, nil
}
us = make([]U, len(ts))
for i, t := range ts {
var e error
us[i], e = fn(t)
if e != nil {
return nil, e
}
}
return us, nil
}

58
moslice/map_test.go Normal file
View file

@ -0,0 +1,58 @@
package moslice_test
import (
"errors"
"lmika.dev/pkg/modash/moslice"
"testing"
"github.com/stretchr/testify/assert"
)
func TestMap(t *testing.T) {
t.Run("should return a mapped slice", func(t *testing.T) {
ts := []int{1, 2, 3}
us := moslice.Map(ts, func(x int) int { return x + 2 })
assert.Equal(t, []int{3, 4, 5}, us)
})
t.Run("should return nil if passed in nil", func(t *testing.T) {
ts := []int(nil)
us := moslice.Map(ts, func(x int) int { return x + 2 })
assert.Nil(t, us)
})
}
func TestMapWithError(t *testing.T) {
t.Run("should return a mapped slice with no error", func(t *testing.T) {
ts := []int{1, 2, 3}
us, err := moslice.MapWithError(ts, func(x int) (int, error) { return x + 2, nil })
assert.Equal(t, []int{3, 4, 5}, us)
assert.NoError(t, err)
})
t.Run("should return nil with an error when mapping function returns an error", func(t *testing.T) {
ts := []int{1, 2, 3}
us, err := moslice.MapWithError(ts, func(x int) (int, error) {
if x == 2 {
return 0, errors.New("bang")
}
return x + 2, nil
})
assert.Nil(t, us)
assert.Error(t, err)
})
t.Run("should return nil if passed in nil", func(t *testing.T) {
ts := []int(nil)
us, err := moslice.MapWithError(ts, func(x int) (int, error) { return x + 2, nil })
assert.Nil(t, us)
assert.NoError(t, err)
})
}

19
moslice/uniq.go Normal file
View file

@ -0,0 +1,19 @@
package moslice
func Uniq[T comparable](ts []T) []T {
if len(ts) < 2 {
return ts
}
outT := make([]T, 0)
seenT := make(map[T]struct{})
for _, t := range ts {
if _, ok := seenT[t]; !ok {
outT = append(outT, t)
seenT[t] = struct{}{}
}
}
return outT
}

31
moslice/uniq_test.go Normal file
View file

@ -0,0 +1,31 @@
package moslice_test
import (
"fmt"
"lmika.dev/pkg/modash/moslice"
"testing"
"github.com/stretchr/testify/assert"
)
func TestUniq(t *testing.T) {
t.Run("should return a slice with unique elements", func(t *testing.T) {
scenarios := []struct {
in []int
want []int
}{
{in: nil, want: nil},
{in: []int{}, want: []int{}},
{in: []int{2}, want: []int{2}},
{in: []int{1, 2}, want: []int{1, 2}},
{in: []int{2, 2}, want: []int{2}},
{in: []int{3, 1, 4, 2, 3, 5, 1, 4}, want: []int{3, 1, 4, 2, 5}},
}
for i, s := range scenarios {
t.Run(fmt.Sprint(i), func(t *testing.T) {
assert.Equal(t, s.want, moslice.Uniq(s.in))
})
}
})
}