Skip to content

Commit

Permalink
Merge pull request #8483 from ProofOfKeags/feature/for-loop-destroyer
Browse files Browse the repository at this point in the history
fn: add slice utilities
  • Loading branch information
guggero authored Mar 8, 2024
2 parents d9048d1 + 2bd9911 commit 4f06f58
Show file tree
Hide file tree
Showing 4 changed files with 328 additions and 0 deletions.
7 changes: 7 additions & 0 deletions fn/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,12 @@ go 1.19

require (
github.com/lightninglabs/neutrino/cache v1.1.2
github.com/stretchr/testify v1.8.1
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
13 changes: 13 additions & 0 deletions fn/go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g=
github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo=
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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4=
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
170 changes: 170 additions & 0 deletions fn/slice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package fn

// All returns true when the supplied predicate evaluates to true for all of
// the values in the slice.
func All[A any](pred func(A) bool, s []A) bool {
for _, val := range s {
if !pred(val) {
return false
}
}

return true
}

// Any returns true when the supplied predicate evaluates to true for any of
// the values in the slice.
func Any[A any](pred func(A) bool, s []A) bool {
for _, val := range s {
if pred(val) {
return true
}
}

return false
}

// Map applies the function argument to all members of the slice and returns a
// slice of those return values.
func Map[A, B any](f func(A) B, s []A) []B {
res := make([]B, 0, len(s))

for _, val := range s {
res = append(res, f(val))
}

return res
}

// Filter creates a new slice of values where all the members of the returned
// slice pass the predicate that is supplied in the argument.
func Filter[A any](pred func(A) bool, s []A) []A {
res := make([]A, 0)

for _, val := range s {
if pred(val) {
res = append(res, val)
}
}

return res
}

// Foldl iterates through all members of the slice left to right and reduces
// them pairwise with an accumulator value that is seeded with the seed value in
// the argument.
func Foldl[A, B any](f func(B, A) B, seed B, s []A) B {
acc := seed

for _, val := range s {
acc = f(acc, val)
}

return acc
}

// Foldr, is exactly like Foldl except that it iterates over the slice from
// right to left.
func Foldr[A, B any](f func(A, B) B, seed B, s []A) B {
acc := seed

for i := range s {
acc = f(s[len(s)-1-i], acc)
}

return acc
}

// Find returns the first value that passes the supplied predicate, or None if
// the value wasn't found.
func Find[A any](pred func(A) bool, s []A) Option[A] {
for _, val := range s {
if pred(val) {
return Some(val)
}
}

return None[A]()
}

// Flatten takes a slice of slices and returns a concatenation of those slices.
func Flatten[A any](s [][]A) []A {
sz := Foldr(
func(l []A, acc uint64) uint64 {
return uint64(len(l)) + acc
}, 0, s,
)

res := make([]A, 0, sz)

for _, val := range s {
res = append(res, val...)
}

return res
}

// Replicate generates a slice of values initialized by the prototype value.
func Replicate[A any](n uint, val A) []A {
res := make([]A, n)

for i := range res {
res[i] = val
}

return res
}

// Span, applied to a predicate and a slice, returns two slices where the first
// element is the longest prefix (possibly empty) of slice elements that
// satisfy the predicate and second element is the remainder of the slice.
func Span[A any](pred func(A) bool, s []A) ([]A, []A) {
for i := range s {
if !pred(s[i]) {
fst := make([]A, i)
snd := make([]A, len(s)-i)

copy(fst, s[:i])
copy(snd, s[i:])

return fst, snd
}
}

res := make([]A, len(s))
copy(res, s)

return res, []A{}
}

// SplitAt(n, s) returns a tuple where first element is s prefix of length n
// and second element is the remainder of the list.
func SplitAt[A any](n uint, s []A) ([]A, []A) {
fst := make([]A, n)
snd := make([]A, len(s)-int(n))

copy(fst, s[:n])
copy(snd, s[n:])

return fst, snd
}

// ZipWith combines slice elements with the same index using the function
// argument, returning a slice of the results.
func ZipWith[A, B, C any](f func(A, B) C, a []A, b []B) []C {
var l uint

if la, lb := len(a), len(b); la < lb {
l = uint(la)
} else {
l = uint(lb)
}

res := make([]C, l)

for i := 0; i < int(l); i++ {
res[i] = f(a[i], b[i])
}

return res
}
138 changes: 138 additions & 0 deletions fn/slice_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package fn

import (
"slices"
"testing"

"github.com/stretchr/testify/require"
)

func even(a int) bool { return a%2 == 0 }
func odd(a int) bool { return a%2 != 0 }

func TestAll(t *testing.T) {
x := []int{0, 2, 4, 6, 8}
require.True(t, All(even, x))
require.False(t, All(odd, x))

y := []int{1, 3, 5, 7, 9}
require.False(t, All(even, y))
require.True(t, All(odd, y))

z := []int{0, 2, 4, 6, 9}
require.False(t, All(even, z))
require.False(t, All(odd, z))
}

func TestAny(t *testing.T) {
x := []int{1, 3, 5, 7, 9}
require.False(t, Any(even, x))
require.True(t, Any(odd, x))

y := []int{0, 3, 5, 7, 9}
require.True(t, Any(even, y))
require.True(t, Any(odd, y))

z := []int{0, 2, 4, 6, 8}
require.True(t, Any(even, z))
require.False(t, Any(odd, z))
}

func TestMap(t *testing.T) {
inc := func(i int) int { return i + 1 }

x := []int{0, 2, 4, 6, 8}

y := Map(inc, x)

z := []int{1, 3, 5, 7, 9}

require.True(t, slices.Equal(y, z))
}

func TestFilter(t *testing.T) {
x := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

y := Filter(even, x)

require.True(t, All(even, y))

z := Filter(odd, y)

require.Zero(t, len(z))
}

func TestFoldl(t *testing.T) {
seed := []int{}
stupid := func(s []int, a int) []int { return append(s, a) }

x := []int{0, 1, 2, 3, 4}

r := Foldl(stupid, seed, x)

require.True(t, slices.Equal(x, r))
}

func TestFoldr(t *testing.T) {
seed := []int{}
stupid := func(a int, s []int) []int { return append(s, a) }

x := []int{0, 1, 2, 3, 4}

z := Foldr(stupid, seed, x)

slices.Reverse[[]int](x)

require.True(t, slices.Equal(x, z))
}

func TestFind(t *testing.T) {
x := []int{10, 11, 12, 13, 14, 15}

div3 := func(a int) bool { return a%3 == 0 }
div8 := func(a int) bool { return a%8 == 0 }

require.Equal(t, Find(div3, x), Some(12))

require.Equal(t, Find(div8, x), None[int]())
}

func TestFlatten(t *testing.T) {
x := [][]int{{0}, {1}, {2}}

y := Flatten(x)

require.True(t, slices.Equal(y, []int{0, 1, 2}))
}

func TestReplicate(t *testing.T) {
require.True(t, slices.Equal([]int{1, 1, 1, 1, 1}, Replicate(5, 1)))
}

func TestSpan(t *testing.T) {
x := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
lt5 := func(a int) bool { return a < 5 }

low, high := Span(lt5, x)

require.True(t, slices.Equal(low, []int{0, 1, 2, 3, 4}))
require.True(t, slices.Equal(high, []int{5, 6, 7, 8, 9}))
}

func TestSplitAt(t *testing.T) {
x := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fst, snd := SplitAt(5, x)

require.True(t, slices.Equal(fst, []int{0, 1, 2, 3, 4}))
require.True(t, slices.Equal(snd, []int{5, 6, 7, 8, 9}))
}

func TestZipWith(t *testing.T) {
eq := func(a, b int) bool { return a == b }
x := []int{0, 1, 2, 3, 4}
y := Replicate(5, 1)
z := ZipWith(eq, x, y)
require.True(t, slices.Equal(
z, []bool{false, true, false, false, false},
))
}

0 comments on commit 4f06f58

Please sign in to comment.