Skip to content

Commit 2bd9911

Browse files
committed
fn: add slice utilities
In the year of our lord 2024 we should not be writing for loops for standard operations. Here we introduce named slice operations not found in the golang slices package. Note all these functions are pure.
1 parent 7a31017 commit 2bd9911

File tree

4 files changed

+328
-0
lines changed

4 files changed

+328
-0
lines changed

fn/go.mod

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,12 @@ go 1.19
44

55
require (
66
github.com/lightninglabs/neutrino/cache v1.1.2
7+
github.com/stretchr/testify v1.8.1
78
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b
89
)
10+
11+
require (
12+
github.com/davecgh/go-spew v1.1.1 // indirect
13+
github.com/pmezard/go-difflib v1.0.0 // indirect
14+
gopkg.in/yaml.v3 v3.0.1 // indirect
15+
)

fn/go.sum

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
1+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
12
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
24
github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g=
35
github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo=
46
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
7+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
8+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
9+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
10+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
11+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
12+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
513
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
14+
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
615
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4=
716
golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
17+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
18+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
19+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
820
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
21+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

fn/slice.go

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package fn
2+
3+
// All returns true when the supplied predicate evaluates to true for all of
4+
// the values in the slice.
5+
func All[A any](pred func(A) bool, s []A) bool {
6+
for _, val := range s {
7+
if !pred(val) {
8+
return false
9+
}
10+
}
11+
12+
return true
13+
}
14+
15+
// Any returns true when the supplied predicate evaluates to true for any of
16+
// the values in the slice.
17+
func Any[A any](pred func(A) bool, s []A) bool {
18+
for _, val := range s {
19+
if pred(val) {
20+
return true
21+
}
22+
}
23+
24+
return false
25+
}
26+
27+
// Map applies the function argument to all members of the slice and returns a
28+
// slice of those return values.
29+
func Map[A, B any](f func(A) B, s []A) []B {
30+
res := make([]B, 0, len(s))
31+
32+
for _, val := range s {
33+
res = append(res, f(val))
34+
}
35+
36+
return res
37+
}
38+
39+
// Filter creates a new slice of values where all the members of the returned
40+
// slice pass the predicate that is supplied in the argument.
41+
func Filter[A any](pred func(A) bool, s []A) []A {
42+
res := make([]A, 0)
43+
44+
for _, val := range s {
45+
if pred(val) {
46+
res = append(res, val)
47+
}
48+
}
49+
50+
return res
51+
}
52+
53+
// Foldl iterates through all members of the slice left to right and reduces
54+
// them pairwise with an accumulator value that is seeded with the seed value in
55+
// the argument.
56+
func Foldl[A, B any](f func(B, A) B, seed B, s []A) B {
57+
acc := seed
58+
59+
for _, val := range s {
60+
acc = f(acc, val)
61+
}
62+
63+
return acc
64+
}
65+
66+
// Foldr, is exactly like Foldl except that it iterates over the slice from
67+
// right to left.
68+
func Foldr[A, B any](f func(A, B) B, seed B, s []A) B {
69+
acc := seed
70+
71+
for i := range s {
72+
acc = f(s[len(s)-1-i], acc)
73+
}
74+
75+
return acc
76+
}
77+
78+
// Find returns the first value that passes the supplied predicate, or None if
79+
// the value wasn't found.
80+
func Find[A any](pred func(A) bool, s []A) Option[A] {
81+
for _, val := range s {
82+
if pred(val) {
83+
return Some(val)
84+
}
85+
}
86+
87+
return None[A]()
88+
}
89+
90+
// Flatten takes a slice of slices and returns a concatenation of those slices.
91+
func Flatten[A any](s [][]A) []A {
92+
sz := Foldr(
93+
func(l []A, acc uint64) uint64 {
94+
return uint64(len(l)) + acc
95+
}, 0, s,
96+
)
97+
98+
res := make([]A, 0, sz)
99+
100+
for _, val := range s {
101+
res = append(res, val...)
102+
}
103+
104+
return res
105+
}
106+
107+
// Replicate generates a slice of values initialized by the prototype value.
108+
func Replicate[A any](n uint, val A) []A {
109+
res := make([]A, n)
110+
111+
for i := range res {
112+
res[i] = val
113+
}
114+
115+
return res
116+
}
117+
118+
// Span, applied to a predicate and a slice, returns two slices where the first
119+
// element is the longest prefix (possibly empty) of slice elements that
120+
// satisfy the predicate and second element is the remainder of the slice.
121+
func Span[A any](pred func(A) bool, s []A) ([]A, []A) {
122+
for i := range s {
123+
if !pred(s[i]) {
124+
fst := make([]A, i)
125+
snd := make([]A, len(s)-i)
126+
127+
copy(fst, s[:i])
128+
copy(snd, s[i:])
129+
130+
return fst, snd
131+
}
132+
}
133+
134+
res := make([]A, len(s))
135+
copy(res, s)
136+
137+
return res, []A{}
138+
}
139+
140+
// SplitAt(n, s) returns a tuple where first element is s prefix of length n
141+
// and second element is the remainder of the list.
142+
func SplitAt[A any](n uint, s []A) ([]A, []A) {
143+
fst := make([]A, n)
144+
snd := make([]A, len(s)-int(n))
145+
146+
copy(fst, s[:n])
147+
copy(snd, s[n:])
148+
149+
return fst, snd
150+
}
151+
152+
// ZipWith combines slice elements with the same index using the function
153+
// argument, returning a slice of the results.
154+
func ZipWith[A, B, C any](f func(A, B) C, a []A, b []B) []C {
155+
var l uint
156+
157+
if la, lb := len(a), len(b); la < lb {
158+
l = uint(la)
159+
} else {
160+
l = uint(lb)
161+
}
162+
163+
res := make([]C, l)
164+
165+
for i := 0; i < int(l); i++ {
166+
res[i] = f(a[i], b[i])
167+
}
168+
169+
return res
170+
}

fn/slice_test.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package fn
2+
3+
import (
4+
"slices"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func even(a int) bool { return a%2 == 0 }
11+
func odd(a int) bool { return a%2 != 0 }
12+
13+
func TestAll(t *testing.T) {
14+
x := []int{0, 2, 4, 6, 8}
15+
require.True(t, All(even, x))
16+
require.False(t, All(odd, x))
17+
18+
y := []int{1, 3, 5, 7, 9}
19+
require.False(t, All(even, y))
20+
require.True(t, All(odd, y))
21+
22+
z := []int{0, 2, 4, 6, 9}
23+
require.False(t, All(even, z))
24+
require.False(t, All(odd, z))
25+
}
26+
27+
func TestAny(t *testing.T) {
28+
x := []int{1, 3, 5, 7, 9}
29+
require.False(t, Any(even, x))
30+
require.True(t, Any(odd, x))
31+
32+
y := []int{0, 3, 5, 7, 9}
33+
require.True(t, Any(even, y))
34+
require.True(t, Any(odd, y))
35+
36+
z := []int{0, 2, 4, 6, 8}
37+
require.True(t, Any(even, z))
38+
require.False(t, Any(odd, z))
39+
}
40+
41+
func TestMap(t *testing.T) {
42+
inc := func(i int) int { return i + 1 }
43+
44+
x := []int{0, 2, 4, 6, 8}
45+
46+
y := Map(inc, x)
47+
48+
z := []int{1, 3, 5, 7, 9}
49+
50+
require.True(t, slices.Equal(y, z))
51+
}
52+
53+
func TestFilter(t *testing.T) {
54+
x := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
55+
56+
y := Filter(even, x)
57+
58+
require.True(t, All(even, y))
59+
60+
z := Filter(odd, y)
61+
62+
require.Zero(t, len(z))
63+
}
64+
65+
func TestFoldl(t *testing.T) {
66+
seed := []int{}
67+
stupid := func(s []int, a int) []int { return append(s, a) }
68+
69+
x := []int{0, 1, 2, 3, 4}
70+
71+
r := Foldl(stupid, seed, x)
72+
73+
require.True(t, slices.Equal(x, r))
74+
}
75+
76+
func TestFoldr(t *testing.T) {
77+
seed := []int{}
78+
stupid := func(a int, s []int) []int { return append(s, a) }
79+
80+
x := []int{0, 1, 2, 3, 4}
81+
82+
z := Foldr(stupid, seed, x)
83+
84+
slices.Reverse[[]int](x)
85+
86+
require.True(t, slices.Equal(x, z))
87+
}
88+
89+
func TestFind(t *testing.T) {
90+
x := []int{10, 11, 12, 13, 14, 15}
91+
92+
div3 := func(a int) bool { return a%3 == 0 }
93+
div8 := func(a int) bool { return a%8 == 0 }
94+
95+
require.Equal(t, Find(div3, x), Some(12))
96+
97+
require.Equal(t, Find(div8, x), None[int]())
98+
}
99+
100+
func TestFlatten(t *testing.T) {
101+
x := [][]int{{0}, {1}, {2}}
102+
103+
y := Flatten(x)
104+
105+
require.True(t, slices.Equal(y, []int{0, 1, 2}))
106+
}
107+
108+
func TestReplicate(t *testing.T) {
109+
require.True(t, slices.Equal([]int{1, 1, 1, 1, 1}, Replicate(5, 1)))
110+
}
111+
112+
func TestSpan(t *testing.T) {
113+
x := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
114+
lt5 := func(a int) bool { return a < 5 }
115+
116+
low, high := Span(lt5, x)
117+
118+
require.True(t, slices.Equal(low, []int{0, 1, 2, 3, 4}))
119+
require.True(t, slices.Equal(high, []int{5, 6, 7, 8, 9}))
120+
}
121+
122+
func TestSplitAt(t *testing.T) {
123+
x := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
124+
fst, snd := SplitAt(5, x)
125+
126+
require.True(t, slices.Equal(fst, []int{0, 1, 2, 3, 4}))
127+
require.True(t, slices.Equal(snd, []int{5, 6, 7, 8, 9}))
128+
}
129+
130+
func TestZipWith(t *testing.T) {
131+
eq := func(a, b int) bool { return a == b }
132+
x := []int{0, 1, 2, 3, 4}
133+
y := Replicate(5, 1)
134+
z := ZipWith(eq, x, y)
135+
require.True(t, slices.Equal(
136+
z, []bool{false, true, false, false, false},
137+
))
138+
}

0 commit comments

Comments
 (0)