Skip to content

Commit 4f06f58

Browse files
authored
Merge pull request #8483 from ProofOfKeags/feature/for-loop-destroyer
fn: add slice utilities
2 parents d9048d1 + 2bd9911 commit 4f06f58

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)