Initial commit of modash
This was taken from github.com/lmika/gopkgs/fp
This commit is contained in:
commit
a20530ddfd
23
.forgeio/workflows/ci.yaml
Normal file
23
.forgeio/workflows/ci.yaml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
name: 'ci'
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: docker
|
||||||
|
steps:
|
||||||
|
- name: Cloning repo
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: '1.22.4'
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
go test ./...
|
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.idea
|
10
go.mod
Normal file
10
go.mod
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
module lmika.dev/pkg/modash
|
||||||
|
|
||||||
|
go 1.23.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.10.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
9
go.sum
Normal file
9
go.sum
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
30
momap/fromslice.go
Normal file
30
momap/fromslice.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package momap
|
||||||
|
|
||||||
|
func FromSliceGroups[T any, K comparable](ts []T, fn func(t T) K) map[K][]T {
|
||||||
|
kvs := make(map[K][]T)
|
||||||
|
for _, t := range ts {
|
||||||
|
k := fn(t)
|
||||||
|
kvs[k] = append(kvs[k], t)
|
||||||
|
}
|
||||||
|
return kvs
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromSlice[T any, K comparable, V any](ts []T, fn func(t T) (K, V)) map[K]V {
|
||||||
|
m, _ := FromSliceWithError(ts, func(t T) (k K, v V, _ error) {
|
||||||
|
k, v = fn(t)
|
||||||
|
return k, v, nil
|
||||||
|
})
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromSliceWithError[T any, K comparable, V any](ts []T, fn func(t T) (K, V, error)) (map[K]V, error) {
|
||||||
|
kvs := make(map[K]V)
|
||||||
|
for _, t := range ts {
|
||||||
|
k, v, err := fn(t)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
kvs[k] = v
|
||||||
|
}
|
||||||
|
return kvs, nil
|
||||||
|
}
|
9
momap/keys.go
Normal file
9
momap/keys.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package momap
|
||||||
|
|
||||||
|
func Keys[K comparable, V any](m map[K]V) []K {
|
||||||
|
ks := make([]K, 0, len(m))
|
||||||
|
for k := range m {
|
||||||
|
ks = append(ks, k)
|
||||||
|
}
|
||||||
|
return ks
|
||||||
|
}
|
29
momap/toslice.go
Normal file
29
momap/toslice.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package momap
|
||||||
|
|
||||||
|
func ToSlice[K comparable, V, T any](m map[K]V, fn func(k K, v V) T) []T {
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := make([]T, 0, len(m))
|
||||||
|
for k, v := range m {
|
||||||
|
ts = append(ts, fn(k, v))
|
||||||
|
}
|
||||||
|
return ts
|
||||||
|
}
|
||||||
|
|
||||||
|
func ToSliceWithError[K comparable, V, T any](m map[K]V, fn func(k K, v V) (T, error)) ([]T, error) {
|
||||||
|
if m == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ts := make([]T, 0, len(m))
|
||||||
|
for k, v := range m {
|
||||||
|
w, err := fn(k, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ts = append(ts, w)
|
||||||
|
}
|
||||||
|
return ts, nil
|
||||||
|
}
|
28
momap/toslice_test.go
Normal file
28
momap/toslice_test.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package momap_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lmika.dev/pkg/modash/momap"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToSlice(t *testing.T) {
|
||||||
|
type pair struct {
|
||||||
|
left int
|
||||||
|
right string
|
||||||
|
}
|
||||||
|
|
||||||
|
ms := map[int]string{
|
||||||
|
1: "one",
|
||||||
|
2: "two",
|
||||||
|
3: "three",
|
||||||
|
}
|
||||||
|
|
||||||
|
pairs := momap.ToSlice(ms, func(k int, v string) pair { return pair{k, v} })
|
||||||
|
|
||||||
|
assert.Len(t, pairs, 3)
|
||||||
|
assert.Contains(t, pairs, pair{1, "one"})
|
||||||
|
assert.Contains(t, pairs, pair{2, "two"})
|
||||||
|
assert.Contains(t, pairs, pair{3, "three"})
|
||||||
|
}
|
17
momap/values.go
Normal file
17
momap/values.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package momap
|
||||||
|
|
||||||
|
func Values[K comparable, V any](m map[K]V) []V {
|
||||||
|
vs := make([]V, 0, len(m))
|
||||||
|
for _, v := range m {
|
||||||
|
vs = append(vs, v)
|
||||||
|
}
|
||||||
|
return vs
|
||||||
|
}
|
||||||
|
|
||||||
|
func MapValues[K comparable, V any, W any](m map[K]V, fn func(v V, k K) W) map[K]W {
|
||||||
|
ws := make(map[K]W)
|
||||||
|
for k, v := range m {
|
||||||
|
ws[k] = fn(v, k)
|
||||||
|
}
|
||||||
|
return ws
|
||||||
|
}
|
18
moslice/filter.go
Normal file
18
moslice/filter.go
Normal 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
29
moslice/filter_test.go
Normal 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
21
moslice/find.go
Normal 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
25
moslice/find_test.go
Normal 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
19
moslice/flatten.go
Normal 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
13
moslice/foreach.go
Normal 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
33
moslice/map.go
Normal 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
58
moslice/map_test.go
Normal 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
19
moslice/uniq.go
Normal 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
31
moslice/uniq_test.go
Normal 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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in a new issue