├── .github
└── workflows
│ └── go.yaml
├── .gitignore
├── LICENSE
├── README.md
├── ch
├── ch.go
└── ch_test.go
├── docs
├── .nojekyll
├── README.md
├── _sidebar.md
├── channel.md
├── index.html
├── map.md
└── slice.md
├── go.mod
├── go.sum
├── kv
├── kv.go
└── kv_test.go
└── slice
├── slice.go
└── slice_test.go
/.github/workflows/go.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - main
5 | pull_request:
6 | branches:
7 | - main
8 |
9 | name: run tests
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v2
15 |
16 | - name: Set up Go
17 | uses: actions/setup-go@v2
18 | with:
19 | stable: false
20 | go-version: 1.18.0-beta1
21 |
22 | - name: Build
23 | run: go build -v ./...
24 |
25 | coverage:
26 | runs-on: ubuntu-latest
27 | steps:
28 | - name: Install Go
29 | if: success()
30 | uses: actions/setup-go@v2
31 | with:
32 | stable: false
33 | go-version: 1.18.0-beta1
34 | - name: Checkout code
35 | uses: actions/checkout@v2
36 | - name: Calc coverage
37 | run: |
38 | go test -v -covermode=count -coverprofile=coverage.out ./...
39 | - name: Convert coverage.out to coverage.lcov
40 | uses: jandelgado/gcov2lcov-action@v1.0.6
41 | - name: Coveralls
42 | uses: coverallsapp/github-action@v1.1.2
43 | with:
44 | github-token: ${{ secrets.github_token }}
45 | path-to-lcov: coverage.lcov
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Nikita Galushko
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fx
2 | [](https://github.com/nikgalushko/fx/actions/workflows/go.yaml) [](https://coveralls.io/github/nikgalushko/fx?branch=main)
3 |
4 |
5 | Fx is a useful functional programming helpers without using `interface{}` or reflect.
6 |
7 | Support **only** Go 1.18+.
8 |
9 | ## Features
10 | - Slice
11 | - Each
12 | - Collect
13 | - Reduce
14 | - Find
15 | - Filter
16 | - Every
17 | - Some
18 | - Contains
19 | - Max
20 | - Min
21 | - GroupBy
22 | - Sample
23 | - SampleN
24 | - Union
25 | - Intersection
26 | - Uniq
27 | - IndexOf
28 | - LastIndexOf
29 | - Reverse
30 | - Map
31 | - Keys
32 | - Values
33 | - Each
34 | - Filter
35 | - Channel
36 | - Merge
37 |
38 | ## Documentation
39 |
40 | Documentation with examples can be found here: https://nikgalushko.github.io/fx/
41 |
42 | ## Installation
43 |
44 | slice helpers `go get github.com/nikgalushko/fx/slice`
45 |
46 | map helpers `go get github.com/nikgalushko/fx/kv`
47 |
48 | channel helpers `go get github.com/nikgalushko/fx/ch`
49 |
50 | ## Usage
51 |
52 | ```go
53 | import (
54 | "fmt"
55 |
56 | "github.com/nikgalushko/fx/kv"
57 | "github.com/nikgalushko/fx/slice"
58 | )
59 |
60 | type (
61 | ID string
62 |
63 | Attribute struct {
64 | Value string
65 | }
66 | )
67 |
68 | func main() {
69 | m := map[ID]Attribute{
70 | ID("1"): {Value: "blah"},
71 | ID("1861"): {Value: "!"},
72 | ID("1234"): {Value: "yeah"},
73 | }
74 |
75 | fmt.Println("ids", kv.Keys(m), "contains special", slice.Contains(kv.Values(m), Attribute{Value: "!"}))
76 | }
77 | ```
78 |
--------------------------------------------------------------------------------
/ch/ch.go:
--------------------------------------------------------------------------------
1 | package ch
2 |
3 | import (
4 | "sync"
5 | )
6 |
7 | // Funnel returns a channel containing the values from all channels.
8 | func Funnel[T any](channels ...chan T) chan T {
9 | ret := make(chan T)
10 |
11 | var wg sync.WaitGroup
12 | wg.Add(len(channels))
13 |
14 | for _, c := range channels {
15 | go func(c chan T) {
16 | for v := range c {
17 | ret <- v
18 | }
19 |
20 | wg.Done()
21 | }(c)
22 | }
23 |
24 | go func() {
25 | wg.Wait()
26 | close(ret)
27 | }()
28 |
29 | return ret
30 | }
31 |
32 | // Split implements a Fan-Out pattern.
33 | func Split[T any](ch <-chan T, n int) []<-chan T {
34 | if n <= 0 {
35 | panic("splitting is only possible for a positive number of channels")
36 | }
37 |
38 | ret := make([]<-chan T, 0, n)
39 |
40 | for i := 0; i < n; i++ {
41 | c := make(chan T)
42 |
43 | go func() {
44 | defer close(c)
45 | for v := range ch {
46 | c <- v
47 | }
48 | }()
49 |
50 | ret = append(ret, c)
51 | }
52 |
53 | return ret
54 | }
55 |
--------------------------------------------------------------------------------
/ch/ch_test.go:
--------------------------------------------------------------------------------
1 | package ch
2 |
3 | import (
4 | "sync"
5 | "sync/atomic"
6 | "testing"
7 |
8 | "github.com/nikgalushko/fx/slice"
9 | "github.com/stretchr/testify/require"
10 | )
11 |
12 | func TestFunnel(t *testing.T) {
13 | var channels []chan int
14 | for i := 0; i < 3; i++ {
15 | var arr []int
16 | for j := 0; j < 5; j++ {
17 | arr = append(arr, j)
18 | }
19 |
20 | channels = append(channels, createChanFrom(arr))
21 | }
22 |
23 | ch := Funnel(channels...)
24 | sum := 0
25 |
26 | for v := range ch {
27 | sum += v
28 | }
29 |
30 | if sum != 30 {
31 | t.Fatalf("%d != 30", sum)
32 | }
33 | }
34 |
35 | func TestSplit(t *testing.T) {
36 | ch := createChanFrom([]int{1, 2, 3, 4, 5, 6, 7, 8, 9})
37 |
38 | splitted := Split(ch, 3)
39 | require.Equal(t, 3, len(splitted))
40 |
41 | var wg sync.WaitGroup
42 | wg.Add(3)
43 |
44 | sum := new(int32)
45 | slice.Each(splitted, func(c <-chan int) {
46 | for v := range c {
47 | atomic.AddInt32(sum, int32(v))
48 | }
49 | wg.Done()
50 | })
51 |
52 | wg.Wait()
53 | require.Equal(t, int32(45), *sum)
54 | }
55 |
56 | func TestSplitPanic(t *testing.T) {
57 | ch := createChanFrom([]int{1, 2})
58 |
59 | require.Panics(t, func() { Split(ch, 0) })
60 | require.Panics(t, func() { Split(ch, -1) })
61 | }
62 |
63 | func createChanFrom(arr []int) chan int {
64 | ret := make(chan int, len(arr))
65 | for _, v := range arr {
66 | ret <- v
67 | }
68 | close(ret)
69 |
70 | return ret
71 | }
72 |
--------------------------------------------------------------------------------
/docs/.nojekyll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nikgalushko/fx/14e235b06e42ada639e0b1681a47677723788db4/docs/.nojekyll
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | # fx
2 | Fx is a useful functional programming helpers.
3 | Support **only** Go 1.18+.
4 |
5 | ## Features
6 | - Slice
7 | - Each
8 | - Collect
9 | - Reduce
10 | - Find
11 | - Filter
12 | - Every
13 | - Some
14 | - Contains
15 | - Max
16 | - Min
17 | - GroupBy
18 | - Sample
19 | - SampleN
20 | - Union
21 | - Intersection
22 | - Uniq
23 | - IndexOf
24 | - LastIndexOf
25 | - Reverse
26 | - Map
27 | - Keys
28 | - Values
29 | - Each
30 | - Filter
31 | - Channel
32 | - Merge
33 |
34 | ## Documentation
35 |
36 | Documentation with examples can be found here: https://nikgalushko.github.io/fx/
37 |
38 | ## Installation
39 |
40 | slice helpers `go get github.com/nikgalushko/fx/slice`
41 |
42 | map helpers `go get github.com/nikgalushko/fx/kv`
43 |
44 | channel helpers `go get github.com/nikgalushko/fx/ch`
45 |
46 | ## Usage
47 |
48 | ```go
49 | import (
50 | "fmt"
51 |
52 | "github.com/nikgalushko/fx/kv"
53 | "github.com/nikgalushko/fx/slice"
54 | )
55 |
56 | type (
57 | ID string
58 |
59 | Attribute struct {
60 | Value string
61 | }
62 | )
63 |
64 | func main() {
65 | m := map[ID]Attribute{
66 | ID("1"): {Value: "blah"},
67 | ID("1861"): {Value: "!"},
68 | ID("1234"): {Value: "yeah"},
69 | }
70 |
71 | fmt.Println("ids", kv.Keys(m), "contains special", slice.Contains(kv.Values(m), Attribute{Value: "!"}))
72 | }
73 | ```
--------------------------------------------------------------------------------
/docs/_sidebar.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | * [Home](README.md)
4 | * [slice](slice.md)
5 | * [map](map.md)
6 | * [channel](channel.md)
7 |
--------------------------------------------------------------------------------
/docs/channel.md:
--------------------------------------------------------------------------------
1 | # Channel functions
2 |
3 | ## Funnel
4 | Funnel returns a channel containing the values from all channels.
5 |
6 | ## Split
7 | Split implements a Fan-Out pattern.
8 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Document
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/docs/map.md:
--------------------------------------------------------------------------------
1 | # Map functions
2 |
3 | ## Keys
4 | Keys reurns a slice of keys from map.
5 |
6 | ```
7 | keys := Keys(map[string]int{
8 | "one": 1,
9 | "two": 2,
10 | "three": 3,
11 | })
12 |
13 |
14 | // keys is []string{"one", "three", "two"}. The order may be different.
15 | ```
16 |
17 | ## Values
18 | Values reurns a slice of values from map.
19 |
20 | ```
21 | values := Values(map[int]int{
22 | 0: 10,
23 | 1: 11,
24 | 2: 12,
25 | })
26 |
27 | // values is []int{10, 11, 12}. The order may be different.
28 | ```
29 |
30 | ## Each
31 | Each calls the function on each key-value pair of map.
32 |
33 | ```
34 | m := map[int]string{
35 | 1: "a",
36 | 2: "b",
37 | 3: "c",
38 | 4: "d",
39 | }
40 |
41 | var (
42 | keys []int
43 | values []string
44 | )
45 | Each(m, func(key int, value string) {
46 | if key > 3 {
47 | return
48 | }
49 |
50 | keys = append(keys, key)
51 | values = append(values, value)
52 | })
53 |
54 | // keys is []int{1, 2, 3}. The order may be different.
55 | // values is []string{"a", "b", "c"}. The order may be different.
56 | ```
57 |
58 | ## Filter
59 | Filter returns a new map that contains key-value pairs that mathch the condition.
60 |
61 | ```
62 | m := map[int]string{
63 | 1: "one",
64 | 2: "two",
65 | 5: "five",
66 | 6: "six",
67 | 7: "seven",
68 | }
69 |
70 | filtered := Filter(m, func(key int, value string) bool {
71 | return key < 3 || len(value) < 4
72 | })
73 |
74 | // filtered is map[int]string{1: "one", 2: "two", 6: "six"}. The order may be different. Original map is not changed.
75 | ```
--------------------------------------------------------------------------------
/docs/slice.md:
--------------------------------------------------------------------------------
1 | # Slice functions
2 |
3 | ## Each
4 | Each calls the function on each item in the slice.
5 |
6 | ```
7 | sum := 0
8 | Each([]int{1, 2, 3, 4}, func(i int) {
9 | sum += i
10 | })
11 |
12 | // sum is 10
13 | ```
14 | ## Collect
15 | Collect returns a new slice of values by mapping each value of original slice through a transformation function.
16 |
17 | ```
18 | arr := Collect([]int{1, 2, 3, 4}, func(i int) int {
19 | return i * 2
20 | })
21 |
22 | // arr is []int{2, 4, 6, 8}
23 | ```
24 | ## Reduce
25 | Reduce reduces a slice of values to single value.
26 |
27 | ```
28 | join := Reduce([]string{"b", "l", "a", "h"}, func(memo, s string) string {
29 | return memo + s
30 | }, "")
31 |
32 | // join is "blah"
33 | ```
34 |
35 | ## Find
36 | Find returns the first element in the slice that matches the condition. If slice doesn't contain an element it returns a default type value and false as second value.
37 |
38 | ```
39 | element, ok := Find([]int{1, 2, 3, 4, 5}, func(i int) bool { return i == 4 })
40 |
41 | // element is 4; ok is true
42 | ```
43 |
44 | ## Filter
45 | Filter returns all elements in the slice that mathch the condition.
46 |
47 | ```
48 | ret := Filter([]int{10, 1, 4, 20, 5, 2}, func(i int) bool { return i < 10 })
49 |
50 | // ret is []int{1, 4, 5, 2}
51 | ```
52 |
53 | ## Every
54 | Every returns true if all elements match the condition.
55 |
56 | ```
57 | ret := Every([]int{10, 1, 4, 20, 5, 2}, func(i int) bool { return i >= 0 })
58 |
59 | // ret is true
60 | ```
61 |
62 | ## Some
63 | Some returns true if there is at least one element that satisfies the condition.
64 |
65 | ```
66 | ret := Some([]int{10, 1, 4, 20, 5, 2}, func(i int) bool { return i < 0 })
67 |
68 | // ret is false
69 | ```
70 |
71 | ## Contains
72 | Contains returns true if value is present in the slice.
73 |
74 | ```
75 | ret := Contains([]int{1, 2, 10, 23, 4}, 4)
76 |
77 | // ret is true
78 | ```
79 |
80 | ## Max
81 | Max returns the maximum value from the slice.
82 | If input slice is empty it returns a default value for input type.
83 |
84 | ```
85 | arr := []int{10, 2, 1, 4, 19}
86 | ret := Max(arr)
87 |
88 | // ret is 19
89 |
90 | zero := Max([]int{})
91 |
92 | // zero is 0
93 | ```
94 |
95 | ## Min
96 | Min returns the minimum value from the slice.
97 | If input slice is empty it returns a default value for input type.
98 |
99 | ```
100 | arr := []int{10, 2, 1, 4, 19}
101 | ret := Min(arr)
102 |
103 | // ret is 11
104 |
105 | zero := Max([]string{})
106 |
107 | // zero is ""
108 | ```
109 |
110 | ## GroupBy
111 | GroupBy splits the slice into groups, grouped by the result of the function call.
112 |
113 | ```
114 | group := GroupBy([]string{"one", "two", "three"}, func(s string) int { return len(s) })
115 |
116 | // group is map[int][]string{3: {"one", "two"}, 5: {"three"}}
117 | ```
118 |
119 | ## Sample
120 | Sample returns the random element from slice.
121 |
122 | ```
123 | v := Sample([]int{1, 2, 3, 4})
124 |
125 | // v is random element from {1, 2, 3, 4}
126 | ```
127 |
128 | ## SampleN
129 | SampleN returns the N random elements from slice.
130 |
131 | ```
132 | arr := []int{11, 12, 13, 14, 15, 16, 17, 18, 19}
133 | samples := SampleN(arr, 5)
134 |
135 | // samples is slice of random elements from arr
136 | ```
137 |
138 | ## Union
139 | Union returns a slice of unique values from passed slices.
140 |
141 | ```
142 | ret := Union([]string{"a", "b", "c"}, []string{"b", "c", "d"}}
143 |
144 | // ret is []string{"a", "b", "c", "d"}
145 | ```
146 |
147 | ## Intersection
148 | Intersection returns a slice of values that are in all passed slices.
149 |
150 | ```
151 | arr1 := []string{"a", "b", "c"}
152 | arr2 := []string{"b", "c", "d"}
153 |
154 | ret := Intersection(arr1, arr2)
155 |
156 | // ret is []string{"b", "c"}
157 | ```
158 |
159 | ## Uniq
160 | Uniq returns a slice of unique values.
161 |
162 | ```
163 | arr := []string{"a", "b", "a", "c", "b"}
164 | ret := Uniq(arr)
165 |
166 | // ret is []string{"a", "b", "c"}
167 | ```
168 |
169 | ## IndexOf
170 | IndexOf returns first index of the found element in the slice. If slice doesn't contain an element it returns -1.
171 |
172 | ```
173 | i := IndexOf([]int{1, 2, 3, 2}, 2)
174 |
175 | // i is 1
176 | ```
177 |
178 | ## LastIndexOf
179 | LastIndexOf like as IndexOf, but the search goes from the end.
180 |
181 | ```
182 | i := LastIndexOf([]int{1, 2, 3, 4}, 20)
183 |
184 | // i is -1
185 | ```
186 |
187 | ## Reduce
188 | Reverse reverses the order of the elements in place.
189 |
190 | ```
191 | arr := []int{0, 1, 2, 3}
192 | Reverse(arr)
193 |
194 | // arr is []int{3, 2, 1, 0}
195 | ```
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/nikgalushko/fx
2 |
3 | go 1.18
4 |
5 | require (
6 | github.com/davecgh/go-spew v1.1.0 // indirect
7 | github.com/pmezard/go-difflib v1.0.0 // indirect
8 | github.com/stretchr/testify v1.7.0 // indirect
9 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
10 | )
11 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
6 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
7 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
9 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
11 |
--------------------------------------------------------------------------------
/kv/kv.go:
--------------------------------------------------------------------------------
1 | package kv
2 |
3 | // Keys reurns a slice of keys from map.
4 | func Keys[K comparable, V any](m map[K]V) []K {
5 | ret := make([]K, 0, len(m))
6 |
7 | for k := range m {
8 | ret = append(ret, k)
9 | }
10 |
11 | return ret
12 | }
13 |
14 | // Values reurns a slice of values from map.
15 | func Values[K comparable, V any](m map[K]V) []V {
16 | ret := make([]V, 0, len(m))
17 |
18 | for _, v := range m {
19 | ret = append(ret, v)
20 | }
21 |
22 | return ret
23 | }
24 |
25 | // Each calls the function on each key-value pair of map.
26 | func Each[K comparable, V any](m map[K]V, f func(key K, value V)) {
27 | for k, v := range m {
28 | f(k, v)
29 | }
30 | }
31 |
32 | // Filter returns a new map that contains key-value pairs that mathch the condition.
33 | func Filter[K comparable, V any](m map[K]V, f func(key K, value V) bool) map[K]V {
34 | ret := make(map[K]V)
35 |
36 | for k, v := range m {
37 | if f(k, v) {
38 | ret[k] = v
39 | }
40 | }
41 |
42 | return ret
43 | }
44 |
--------------------------------------------------------------------------------
/kv/kv_test.go:
--------------------------------------------------------------------------------
1 | package kv
2 |
3 | import (
4 | "sort"
5 | "testing"
6 |
7 | "github.com/stretchr/testify/require"
8 | )
9 |
10 | func TestKeys(t *testing.T) {
11 | keys := Keys(map[string]int{
12 | "one": 1,
13 | "two": 2,
14 | "three": 3,
15 | })
16 |
17 | sort.Strings(keys)
18 |
19 | require.Equal(t, []string{"one", "three", "two"}, keys)
20 | }
21 |
22 | func TestValues(t *testing.T) {
23 | values := Values(map[int]int{
24 | 0: 10,
25 | 1: 11,
26 | 2: 12,
27 | })
28 |
29 | sort.Ints(values)
30 |
31 | require.Equal(t, []int{10, 11, 12}, values)
32 | }
33 |
34 | func TestEach(t *testing.T) {
35 | var (
36 | keys []int
37 | values []string
38 | )
39 |
40 | Each(map[int]string{
41 | 1: "a",
42 | 2: "b",
43 | 3: "c",
44 | 4: "d",
45 | }, func(key int, value string) {
46 | if key > 3 {
47 | return
48 | }
49 |
50 | keys = append(keys, key)
51 | values = append(values, value)
52 | })
53 |
54 | sort.Ints(keys)
55 | sort.Strings(values)
56 |
57 | require.Equal(t, []int{1, 2, 3}, keys)
58 | require.Equal(t, []string{"a", "b", "c"}, values)
59 | }
60 |
61 | func TestFilter(t *testing.T) {
62 | m := map[int]string{
63 | 1: "one",
64 | 2: "two",
65 | 5: "five",
66 | 6: "six",
67 | 7: "seven",
68 | }
69 |
70 | filtered := Filter(m, func(key int, value string) bool {
71 | return key < 3 || len(value) < 4
72 | })
73 |
74 | require.Equal(t, map[int]string{1: "one", 2: "two", 6: "six"}, filtered)
75 | require.Len(t, m, 5)
76 | }
77 |
--------------------------------------------------------------------------------
/slice/slice.go:
--------------------------------------------------------------------------------
1 | package slice
2 |
3 | import (
4 | "constraints"
5 | "math/rand"
6 | "time"
7 | )
8 |
9 | func init() {
10 | rand.Seed(time.Now().UnixNano())
11 | }
12 |
13 | // Each calls the function on each item in the slice.
14 | func Each[A ~[]T, T any](arr A, f func(T)) {
15 | for _, v := range arr {
16 | f(v)
17 | }
18 | }
19 |
20 | // Collect returns a new slice of values by mapping each value of original slice through a transformation function.
21 | func Collect[A ~[]T, T any, M any](arr A, f func(T) M) []M {
22 | ret := make([]M, len(arr))
23 |
24 | for i, v := range arr {
25 | ret[i] = f(v)
26 | }
27 |
28 | return ret
29 | }
30 |
31 | // Reduce reduces a slice of values to single value.
32 | func Reduce[A ~[]T, T any, M any](arr A, f func(M, T) M, initial M) M {
33 | for _, v := range arr {
34 | initial = f(initial, v)
35 | }
36 |
37 | return initial
38 | }
39 |
40 | // Find returns the first element in the slice that matches the condition.
41 | // If slice doesn't contain an element it returns a default type value and false as second value.
42 | func Find[A ~[]T, T any](arr A, f func(T) bool) (T, bool) {
43 | for _, v := range arr {
44 | if f(v) {
45 | return v, true
46 | }
47 | }
48 |
49 | return defaultvalue[T](), false
50 | }
51 |
52 | // Filter returns all elements in the slice that mathch the condition.
53 | func Filter[A ~[]T, T any](arr A, f func(T) bool) A {
54 | var ret A
55 |
56 | for _, v := range arr {
57 | if f(v) {
58 | ret = append(ret, v)
59 | }
60 | }
61 |
62 | return ret
63 | }
64 |
65 | // Every returns true if all elements match the condition.
66 | func Every[A ~[]T, T any](arr A, f func(T) bool) bool {
67 | for _, v := range arr {
68 | if !f(v) {
69 | return false
70 | }
71 | }
72 |
73 | return true
74 | }
75 |
76 | // Some returns true if there is at least one element that satisfies the condition.
77 | func Some[A ~[]T, T any](arr A, f func(T) bool) bool {
78 | for _, v := range arr {
79 | if f(v) {
80 | return true
81 | }
82 | }
83 |
84 | return false
85 | }
86 |
87 | // Contains returns true if value is present in the slice.
88 | func Contains[A ~[]T, T comparable](arr A, value T) bool {
89 | for _, v := range arr {
90 | if v == value {
91 | return true
92 | }
93 | }
94 |
95 | return false
96 | }
97 |
98 | // Max returns the maximum value from the slice.
99 | // If input slice is empty it returns a default value for input type.
100 | func Max[A ~[]T, T constraints.Ordered](arr A) T {
101 | if len(arr) == 0 {
102 | return defaultvalue[T]()
103 | }
104 |
105 | e := arr[0]
106 |
107 | for i := 1; i < len(arr); i++ {
108 | if arr[i] > e {
109 | e = arr[i]
110 | }
111 | }
112 |
113 | return e
114 | }
115 |
116 | // Min returns the minimum value from the slice.
117 | // If input slice is empty it returns a default value for input type.
118 | func Min[A ~[]T, T constraints.Ordered](arr A) T {
119 | if len(arr) == 0 {
120 | return defaultvalue[T]()
121 | }
122 |
123 | e := arr[0]
124 |
125 | for i := 1; i < len(arr); i++ {
126 | if arr[i] < e {
127 | e = arr[i]
128 | }
129 | }
130 |
131 | return e
132 | }
133 |
134 | // GroupBy splits the slice into groups, grouped by the result of the function call.
135 | func GroupBy[A ~[]T, T any, M comparable](arr A, f func(T) M) map[M]A {
136 | ret := make(map[M]A)
137 |
138 | for _, v := range arr {
139 | m := f(v)
140 | ret[m] = append(ret[m], v)
141 | }
142 |
143 | return ret
144 | }
145 |
146 | // Sample returns the random element from slice.
147 | func Sample[A ~[]T, T any](arr A) T {
148 | return arr[rand.Intn(len(arr))]
149 | }
150 |
151 | // SampleN returns the N random elements from slice.
152 | func SampleN[A ~[]T, T any](arr A, n int) []T {
153 | if n < 0 {
154 | return A{}
155 | }
156 |
157 | if n > len(arr) {
158 | n = len(arr)
159 | }
160 |
161 | ret := make([]T, n)
162 | for i, v := range rand.Perm(n) {
163 | ret[i] = arr[v]
164 | }
165 |
166 | return ret
167 | }
168 |
169 | // Union returns a slice of unique values from passed slices.
170 | func Union[A ~[]T, T comparable](arr ...A) A {
171 | if len(arr) == 0 {
172 | return A{}
173 | }
174 |
175 | if len(arr) == 1 {
176 | return arr[0]
177 | }
178 |
179 | ret := make(A, 0, len(arr[0]))
180 | m := make(map[T]struct{})
181 | for _, array := range arr {
182 | for i := 0; i < len(array); i++ {
183 | if _, ok := m[array[i]]; !ok {
184 | ret = append(ret, array[i])
185 | m[array[i]] = struct{}{}
186 | }
187 | }
188 | }
189 |
190 | return ret
191 | }
192 |
193 | // Intersection returns a slice of values that are in all passed slices.
194 | func Intersection[A ~[]T, T comparable](arr ...A) A {
195 | if len(arr) == 0 {
196 | return A{}
197 | }
198 |
199 | if len(arr) == 1 {
200 | return arr[0]
201 | }
202 |
203 | ret := arr[0]
204 | arr = arr[1:]
205 |
206 | for len(arr) != 0 {
207 | var nextPath A
208 |
209 | part2 := arr[0]
210 | m := make(map[T]struct{})
211 |
212 | for _, array := range []A{ret, part2} {
213 | for i := 0; i < len(array); i++ {
214 | if _, ok := m[array[i]]; ok {
215 | nextPath = append(nextPath, array[i])
216 | } else {
217 | m[array[i]] = struct{}{}
218 | }
219 | }
220 | }
221 |
222 | ret = nextPath
223 | arr = arr[1:]
224 | }
225 |
226 | return ret
227 | }
228 |
229 | // Uniq returns a slice of unique values.
230 | func Uniq[A ~[]T, T comparable](arr A) []T {
231 | ret := make(A, 0)
232 | m := make(map[T]struct{})
233 |
234 | for _, elem := range arr {
235 | if _, ok := m[elem]; !ok {
236 | m[elem] = struct{}{}
237 | ret = append(ret, elem)
238 | }
239 | }
240 |
241 | return ret
242 | }
243 |
244 | // IndexOf returns first index of the found element in the slice.
245 | // If slice doesn't contain an element it returns -1.
246 | func IndexOf[A ~[]T, T comparable](arr A, value T) int {
247 | for i := 0; i < len(arr); i++ {
248 | if arr[i] == value {
249 | return i
250 | }
251 | }
252 |
253 | return -1
254 | }
255 |
256 | // LastIndexOf like as IndexOf, but the search goes from the end.
257 | func LastIndexOf[A ~[]T, T comparable](arr A, value T) int {
258 | for i := len(arr) - 1; i >= 0; i-- {
259 | if arr[i] == value {
260 | return i
261 | }
262 | }
263 |
264 | return -1
265 | }
266 |
267 | // Reverse reverses the order of the elements in place.
268 | func Reverse[A ~[]T, T any](arr A) {
269 | for i := 0; i < len(arr)/2; i++ {
270 | arr[i], arr[len(arr)-i-1] = arr[len(arr)-i-1], arr[i]
271 | }
272 | }
273 |
274 | func defaultvalue[T any]() T {
275 | n := new(T)
276 | return *n
277 | }
--------------------------------------------------------------------------------
/slice/slice_test.go:
--------------------------------------------------------------------------------
1 | package slice
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/stretchr/testify/require"
7 | )
8 |
9 | func TestEach(t *testing.T) {
10 | sum := 0
11 | Each([]int{1, 2, 3, 4}, func(i int) {
12 | sum += i
13 | })
14 |
15 | require.Equal(t, 10, sum)
16 | }
17 |
18 | func TestCollect(t *testing.T) {
19 | arr := Collect([]int{1, 2, 3, 4}, func(i int) int {
20 | return i * 2
21 | })
22 |
23 | require.Equal(t, []int{2, 4, 6, 8}, arr)
24 | }
25 |
26 | func TestReduce(t *testing.T) {
27 | join := Reduce([]string{"b", "l", "a", "h"}, func(memo, s string) string {
28 | return memo + s
29 | }, "")
30 |
31 | require.Equal(t, "blah", join)
32 | }
33 |
34 | func TestFind(t *testing.T) {
35 | tests := map[string]struct {
36 | arr []int
37 | f func(int) bool
38 | element int
39 | ok bool
40 | }{
41 | "found": {
42 | arr: []int{1, 2, 3, 4, 5},
43 | f: func(i int) bool { return i == 4 },
44 | element: 4,
45 | ok: true,
46 | },
47 | "not found": {
48 | arr: []int{1, 2, 3, 4, 5},
49 | f: func(i int) bool { return i == 100 },
50 | element: 0,
51 | ok: false,
52 | },
53 | }
54 |
55 | for title, tt := range tests {
56 | element, ok := Find(tt.arr, tt.f)
57 |
58 | require.Equal(t, tt.element, element, title)
59 | require.Equal(t, tt.ok, ok, title)
60 | }
61 | }
62 |
63 | func TestFilter(t *testing.T) {
64 | ret := Filter([]int{10, 1, 4, 20, 5, 2}, func(i int) bool { return i < 10 })
65 |
66 | require.Equal(t, []int{1, 4, 5, 2}, ret)
67 | }
68 |
69 | func TestEvery(t *testing.T) {
70 | tests := map[string]struct {
71 | arr []int
72 | f func(int) bool
73 | expected bool
74 | }{
75 | "not ok": {
76 | arr: []int{10, 1, 4, 20, 5, 2},
77 | f: func(i int) bool { return i >= 10 },
78 | expected: false,
79 | },
80 | "ok": {
81 | arr: []int{10, 1, 4, 20, 5, 2},
82 | f: func(i int) bool { return i >= 0 },
83 | expected: true,
84 | },
85 | }
86 |
87 | for title, tt := range tests {
88 | require.Equal(t, tt.expected, Every(tt.arr, tt.f), title)
89 | }
90 | }
91 |
92 | func TestSome(t *testing.T) {
93 | tests := map[string]struct {
94 | arr []int
95 | f func(int) bool
96 | expected bool
97 | }{
98 | "not ok": {
99 | arr: []int{10, 1, 4, 20, 5, 2},
100 | f: func(i int) bool { return i < 0 },
101 | expected: false,
102 | },
103 | "ok": {
104 | arr: []int{10, 1, 4, 20, 5, 2},
105 | f: func(i int) bool { return i >= 10 },
106 | expected: true,
107 | },
108 | }
109 |
110 | for title, tt := range tests {
111 | require.Equal(t, tt.expected, Some(tt.arr, tt.f), title)
112 | }
113 | }
114 |
115 | func TestContains(t *testing.T) {
116 | tests := map[string]struct {
117 | arr []int
118 | value int
119 | expected bool
120 | }{
121 | "contains": {
122 | arr: []int{1, 2, 10, 23, 4},
123 | value: 4,
124 | expected: true,
125 | },
126 | "doesn't contain": {
127 | arr: []int{1, 2, 10, 23, 4},
128 | value: 423,
129 | expected: false,
130 | },
131 | }
132 |
133 | for title, tt := range tests {
134 | require.Equal(t, tt.expected, Contains(tt.arr, tt.value), title)
135 | }
136 | }
137 |
138 | func TestGroupBy(t *testing.T) {
139 | group := GroupBy([]string{"one", "two", "three"}, func(s string) int { return len(s) })
140 |
141 | require.Equal(t, map[int][]string{3: {"one", "two"}, 5: {"three"}}, group)
142 | }
143 |
144 | func TestSample(t *testing.T) {
145 | arr := []int{1, 2, 3, 4}
146 | v := Sample(arr)
147 |
148 | require.Equal(t, true, Contains(arr, v))
149 | }
150 |
151 | func TestSampleN(t *testing.T) {
152 | check := func(arr, samples []int){
153 | require.Equal(t, len(Uniq(samples)), len(samples))
154 |
155 | for _, v := range samples {
156 | require.Equal(t, true, Contains(arr, v))
157 | }
158 | }
159 |
160 | tests := []struct{
161 | input []int
162 | n int
163 | }{
164 | {
165 | input: []int{11, 12, 13, 14, 15, 16, 17, 18, 19},
166 | n: 5,
167 | },
168 | {
169 | input: []int{0, 1, 2},
170 | n: 1,
171 | },
172 | {
173 | input: []int{0, 1, 2},
174 | n: 100,
175 | },
176 | {
177 | input: []int{0, 1, 2},
178 | n: -10,
179 | },
180 | }
181 |
182 | for _, tt := range tests {
183 | check(tt.input, SampleN(tt.input, tt.n))
184 | }
185 | }
186 |
187 | func TestUnion(t *testing.T) {
188 | tests := map[string]struct {
189 | in [][]string
190 | expected []string
191 | }{
192 | "two arrays": {
193 | in: [][]string{{"a", "b", "c"}, {"b", "c", "d"}},
194 | expected: []string{"a", "b", "c", "d"},
195 | },
196 | "zero arrays": {
197 | in: nil,
198 | expected: []string{},
199 | },
200 | "one array": {
201 | in: [][]string{{"1", "2", "3"}},
202 | expected: []string{"1", "2", "3"},
203 | },
204 | "three arrays": {
205 | in: [][]string{{"a", "b", "c"}, {"b", "c", "d"}, {"c", "d", "e"}},
206 | expected: []string{"a", "b", "c", "d", "e"},
207 | },
208 | }
209 |
210 | for title, tt := range tests {
211 | require.Equal(t, tt.expected, Union(tt.in...), title)
212 | }
213 | }
214 |
215 | func TestIntersection(t *testing.T) {
216 | tests := map[string]struct{
217 | in [][]string
218 | expected []string
219 | }{
220 | "without arrays": {
221 | in: [][]string{},
222 | expected: []string{},
223 | },
224 | "single array": {
225 | in: [][]string{{"a", "a", "b"}},
226 | expected: []string{"a", "a", "b"},
227 | },
228 | "two arrays": {
229 | in: [][]string{{"a", "b", "c"}, {"b", "c", "d"}},
230 | expected: []string{"b", "c"},
231 | },
232 | "three arrays": {
233 | in: [][]string{{"a", "b", "c"}, {"b", "c", "d"}, {"a", "f", "g", "c"}},
234 | expected: []string{"c"},
235 | },
236 | "four arrays": {
237 | in: [][]string{{"a", "b", "c"}, {"b", "c", "d"}, {"a", "f", "g", "c"}, {"k", "v"}},
238 | expected: nil,
239 | },
240 | }
241 |
242 | for title, tt := range tests {
243 | require.Equal(t, tt.expected, Intersection(tt.in...), title)
244 | }
245 | }
246 |
247 | func TestIndexOf(t *testing.T) {
248 | require.Equal(t, 1, IndexOf([]int{1, 2, 3, 2}, 2), "found")
249 | require.Equal(t, -1, IndexOf([]int{1, 2, 3, 4}, 20), "not found")
250 | }
251 |
252 | func TestLastIndexOf(t *testing.T) {
253 | require.Equal(t, 3, LastIndexOf([]int{1, 2, 3, 2}, 2), "found")
254 | require.Equal(t, -1, LastIndexOf([]int{1, 2, 3, 4}, 20), "not found")
255 | }
256 |
257 | func TestMinMax(t *testing.T) {
258 | tests := map[string]struct{
259 | in []int
260 | max int
261 | min int
262 | }{
263 | "simple array": {
264 | in: []int{10, 2, 1, 4, 19},
265 | max: 19,
266 | min: 1,
267 | },
268 | "zero array": {
269 | in : nil,
270 | max: 0,
271 | min: 0,
272 | },
273 | }
274 |
275 | for title, tt := range tests {
276 | require.Equal(t, tt.max, Max(tt.in), title)
277 | require.Equal(t, tt.min, Min(tt.in), title)
278 | }
279 | }
280 |
281 | func TestReverse(t *testing.T) {
282 | tests := map[string]struct {
283 | in []int
284 | expected []int
285 | }{
286 | "even": {
287 | in: []int{0, 1, 2, 3},
288 | expected: []int{3, 2, 1, 0},
289 | },
290 | "odd": {
291 | in: []int{0, 1, 2, 3, 4},
292 | expected: []int{4, 3, 2, 1, 0},
293 | },
294 | }
295 |
296 | for title, tt := range tests {
297 | Reverse(tt.in)
298 | require.Equal(t, tt.expected, tt.in, title)
299 | }
300 | }
301 |
--------------------------------------------------------------------------------