Skip to content

Commit e2c579c

Browse files
committed
Initial commit for OSS release
0 parents  commit e2c579c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+6009
-0
lines changed

.gitignore

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# OS Cruft
2+
.DS_Store
3+
thumbs.db
4+
5+
coverage.*
6+
*.log
7+
8+
# Profiling stuff
9+
*.tmp
10+
*.test
11+
*.pdf
12+
*.out

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2022 ZeroFlucs Labs Pty Ltd
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.

README.md

+226
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,226 @@
1+
# zeroflucs generics
2+
3+
[![GoDoc](https://godoc.org/github.com/zeroflucs-given/generics?status.svg)](https://godoc.org/github.com/zeroflucs-given/generics)
4+
5+
![GitHub issues](https://img.shields.io/github/issues/zeroflucs-given/generics)
6+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7+
![GitHub commit activity](https://img.shields.io/github/commit-activity/y/zeroflucs-given/charybdis)
8+
9+
---
10+
11+
### Overview
12+
The `generics` package is a series of helpers for writing generic Go code that provides a series of functions that don't exist
13+
in the mainline package set.
14+
15+
## About ZeroFlucs
16+
[ZeroFlucs](https://zeroflucs.io) is a B2B provider of pricing technology for Sportsbooks/wagering service providers globally. We
17+
use Open-Source software through our platform stack. This, along with other projects is made available through our _zeroflucs-given_
18+
Github profile on MIT licensing. To learn more you can visit:
19+
20+
- [The ZeroFlucs Website](https://zeroflucs.io) - For information about our products and services.
21+
- [The ZeroFlucs Team Blog](https://blog.zeroflucs.io/) - For more content and posts from the ZeroFlucs team.
22+
- [ZeroFlucs-Given](https://github.com/zeroflucs-given/) - For more OSS contributions.
23+
24+
## Why Does this Exist?
25+
26+
27+
When writing Go code for Go 1.17 or below, we've all written more than our fair share of methods to check "does this slice contain a thing", or "give me the first item matching a predicate". This package contains a roll-up of helpers and methods, as well as generic collection types that enable a lot of this boilerplate code to be removed.
28+
29+
Key attributes:
30+
31+
- Context support for filters/mappers (Optional)
32+
- Does not mutate input during operation.
33+
34+
All code is covered 100% by tests for expected behaviour. Where filters or mappers are used
35+
methods are provided with and without context support.
36+
37+
# Slice Queries
38+
39+
```
40+
package generics_test
41+
42+
import (
43+
"fmt"
44+
45+
"github.com/zeroflucs-given/generics/query"
46+
)
47+
48+
func main() {
49+
inputs := []int{
50+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
51+
}
52+
query := query.Slice(inputs)
53+
54+
result := query.
55+
Skip(5).
56+
TakeUntil(func(index int, v int) bool {
57+
return v >= 15
58+
}).
59+
Filter(func(index int, v int) bool {
60+
return v%2 == 0 // Evens only
61+
}).
62+
Reverse().
63+
ToSlice() // Back to a clean slice type
64+
65+
fmt.Println(result) // [14, 12, 10, 8, 6]
66+
}
67+
```
68+
69+
__But what about Contexts?__
70+
71+
Don't worry, we've got you. Use `.WithContext(ctx)` on the query chain and the entire subtree is now context aware. Any materialization functions that emit an actual value will now return an extra `error` argument. Operations are lazy, and will skip over the remainder of any query chain once the first error has occured.
72+
73+
```
74+
package generics_test
75+
76+
import (
77+
"context"
78+
"fmt"
79+
80+
"github.com/zeroflucs-given/generics/query"
81+
)
82+
83+
func main() {
84+
inputs := []int{
85+
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
86+
}
87+
query := query.Slice(inputs)
88+
ctx := context.Todo()
89+
90+
result, err := query.
91+
WithContext(ctx).
92+
Skip(5).
93+
TakeUntil(func(ctx context.Context, index int, v int) (bool, error) {
94+
return v >= 15, nil
95+
}).
96+
Filter(func(ctx context.Context, index int, v int) (bool, error) {
97+
return v%2 == 0, nil // Evens only
98+
}).
99+
Reverse().
100+
ToSlice() // Back to a clean slice type
101+
102+
fmt.Println(result) // [14, 12, 10, 8, 6]
103+
fmt.Println(err) // nil
104+
}
105+
```
106+
107+
When dealing with slice operations, the following design rules apply:
108+
109+
- Operations that return a single value will return the type default if no item matches (i.e. 0 for numeric types, empty string or nil for objects)
110+
- Operations that accept multiple filters combine these filters into a logical AND by default.
111+
- If no filters are applied, every item is assumed to pass.
112+
- Context aware operations only exist where it makes sense (i.e. Take first 5 doesn't need a context, whereas take with filtering does)
113+
114+
## Operation Detail
115+
### All / AllContext
116+
Returns true if all items in the slice match the filter. Will return true for an empty
117+
slice as no items fail the predicate.
118+
119+
### Any / AnyContext
120+
Returns true if any item in the slice passes.
121+
122+
### Concatenate
123+
Joins N slices of items together in the given order. Allocates a new slice.
124+
125+
### Contains
126+
Returns true if the slice contains the specified value T, false otherwise. Uses standard equality operator to compare types.
127+
128+
### Count / CountContext
129+
Returns the count of items matching the input filters.
130+
131+
### DefaultIfEmpty
132+
Given a slice, if the slice is empty or nil will create a slice of a single default item.
133+
134+
### Filter / FilterContext
135+
Creates a filtered set of the values in the slice, using a filter function.
136+
137+
### First / FirstWithContext
138+
Returns the first item of the slice that matches the filters. If no value matches, returns
139+
the type default.
140+
141+
### Group / GroupWithContext
142+
Uses a mapper function to assign input values to buckets.
143+
144+
### Last / LastWithContext
145+
Returns the last item of the slice that matches the filter.
146+
147+
### Map / MapWithContext
148+
Runs the specified mapper over each element of the input slice, creating an output slice of
149+
a different type.
150+
151+
### Reverse
152+
Creates a reverse-sorted version of the input slice.
153+
154+
### Skip
155+
Skip the first N elements of the slice.
156+
157+
### SkipUntil / SkipUntilWithContext
158+
Skip items from the front of the slice until the predicate returns true.
159+
160+
### SkipWhile / SkipWhileWithContext
161+
Skips items from the front of the slice until the predicate returns false.
162+
163+
### Take
164+
Take the next N elements of the slice.
165+
166+
### TakeUntil / TakeUntil
167+
Take items from the slice until the filter function returns true.
168+
169+
### TakeWhile / TakeWhileWithContext
170+
Take items from the slice until the filter function returns false.
171+
172+
### ToMap / ToMapWithContext
173+
Converts a slice of values to Go map, using mappers for the key and values respectively.
174+
175+
176+
## Slice Aggregations
177+
---------------
178+
### Min
179+
Returns the minimum value from the input slice. Returns 0 if no values.
180+
181+
### Max
182+
Returns the maximum value from the input slice. Returns 0 if no values.
183+
184+
### Sum
185+
Returns the total sum of the values. Note that when handling large values, you may overflow your input type.
186+
187+
# Map Operations
188+
The following map operation helpers exist in the `generics` package:
189+
190+
## KeyValuesToMap
191+
Assembles a map from a slice of key-value pairs.
192+
193+
## Keys
194+
Returns a slice of the key members of the map.
195+
196+
## ToKeyValues
197+
Converts a map into a slice of key-value pairs. As per Go handling of maps, the order of
198+
output here is not in a guaranteed order.
199+
200+
## Values
201+
Return a slice of the value members of the map.
202+
203+
# Error Checking
204+
## Must
205+
Returns the first value in a pair of (T, error) if there is no error. Otherwise will panic.
206+
207+
# Filter Building
208+
The `filtering` sub-package contains various constructs used to filter values.
209+
210+
### True / TrueWithContext
211+
A constant value filter that always returns true. Useful for testing.
212+
213+
### False / FalseWithContext
214+
A constant value filter that always returns false. Useful for testing.
215+
216+
### And(...filters) / AndWithContext
217+
Creates a composite AND filter for multiple conditions. An empty set of filters is a true.
218+
219+
### Or(...filters) / OrWithContext
220+
Creates a composite OR filter for multiple conditions. An empty set of filters is a false.
221+
222+
### Not(filter) / NotWithContext
223+
Creates a NOT/inverted version of the specified filter to allow for negative checking.
224+
225+
### Wrap
226+
Takes a non-context aware filter, but makes it compatible with code that expects contexts.

aggregates.go

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package generics
2+
3+
// Min gets the minumum of numeric values. If the slice is empty then
4+
// a value of 0 is returned.
5+
func Min[T Numeric](items []T) T {
6+
var result T
7+
if len(items) == 0 {
8+
return result
9+
} else {
10+
result = items[0]
11+
}
12+
13+
for _, v := range items {
14+
if v < result {
15+
result = v
16+
}
17+
}
18+
19+
return result
20+
}
21+
22+
// Max gets the maximum of numeric values. If the slice is empty then
23+
// a value of 0 is returned.
24+
func Max[T Numeric](items []T) T {
25+
var result T
26+
if len(items) == 0 {
27+
return result
28+
} else {
29+
result = items[0]
30+
}
31+
32+
for _, v := range items {
33+
if v > result {
34+
result = v
35+
}
36+
}
37+
38+
return result
39+
}
40+
41+
// Sum numeric values
42+
func Sum[T Numeric](items []T) T {
43+
var result T
44+
for _, v := range items {
45+
result += v
46+
}
47+
return result
48+
}

aggregates_test.go

+68
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package generics_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
"github.com/zeroflucs-given/generics"
8+
)
9+
10+
func TestMin(t *testing.T) {
11+
// Arrange
12+
input := []int{10, 14, 28, 5}
13+
target := 5
14+
15+
// Act
16+
result := generics.Min(input)
17+
18+
// Assert
19+
require.Equal(t, result, target, "Should have the correct value")
20+
}
21+
22+
func TestMinEmpty(t *testing.T) {
23+
// Arrange
24+
input := []int{}
25+
target := 0
26+
27+
// Act
28+
result := generics.Min(input)
29+
30+
// Assert
31+
require.Equal(t, result, target, "Should have the correct value")
32+
}
33+
34+
func TestMax(t *testing.T) {
35+
// Arrange
36+
input := []int{10, 14, 28, 5}
37+
target := 28
38+
39+
// Act
40+
result := generics.Max(input)
41+
42+
// Assert
43+
require.Equal(t, result, target, "Should have the correct value")
44+
}
45+
46+
func TestMaxEmpty(t *testing.T) {
47+
// Arrange
48+
input := []int{}
49+
target := 0
50+
51+
// Act
52+
result := generics.Max(input)
53+
54+
// Assert
55+
require.Equal(t, result, target, "Should have the correct value")
56+
}
57+
58+
func TestSum(t *testing.T) {
59+
// Arrange
60+
input := []int{10, 14, 28}
61+
target := 52
62+
63+
// Act
64+
result := generics.Sum(input)
65+
66+
// Assert
67+
require.Equal(t, result, target, "Should have the correct value")
68+
}

0 commit comments

Comments
 (0)