feat(pkg): add initial source code
This commit is contained in:
parent
acae5dc4d5
commit
61db20e91c
378
collection.go
Normal file
378
collection.go
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
package collections
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"pkg.icikowski.pl/collections/functions"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Collection represents the collection of data
|
||||||
|
//
|
||||||
|
// By default, the collection uses parallel implementation of operators.
|
||||||
|
type Collection[T any] struct {
|
||||||
|
data []T
|
||||||
|
parallel bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count counts values in the collection
|
||||||
|
func (s *Collection[T]) Count() int {
|
||||||
|
return len(s.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Epty determines whether the collection is empty
|
||||||
|
func (s *Collection[T]) Empty() bool {
|
||||||
|
return len(s.data) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parallel sets the collection to use parallel implementation of operators
|
||||||
|
func (s *Collection[T]) Parallel() *Collection[T] {
|
||||||
|
s.parallel = true
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sequential sets the collection to use sequential implementation of operators
|
||||||
|
func (s *Collection[T]) Sequential() *Collection[T] {
|
||||||
|
s.parallel = false
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Collection[T]) filterParallel(p functions.Predicate[T]) *Collection[T] {
|
||||||
|
processed := []T{}
|
||||||
|
mux := sync.Mutex{}
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
|
||||||
|
wg.Add(len(s.data))
|
||||||
|
for _, e := range s.data {
|
||||||
|
e := e
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if p(e) {
|
||||||
|
mux.Lock()
|
||||||
|
defer mux.Unlock()
|
||||||
|
processed = append(processed, e)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
s.data = processed
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Collection[T]) filterSequential(p functions.Predicate[T]) *Collection[T] {
|
||||||
|
processed := []T{}
|
||||||
|
for _, e := range s.data {
|
||||||
|
if p(e) {
|
||||||
|
processed = append(processed, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.data = processed
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter filters the collection using given [functions.Predicate]
|
||||||
|
func (s *Collection[T]) Filter(p functions.Predicate[T]) *Collection[T] {
|
||||||
|
if s.parallel {
|
||||||
|
return s.filterParallel(p)
|
||||||
|
}
|
||||||
|
return s.filterSequential(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Collection[T]) allMatchParallel(p functions.Predicate[T]) bool {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
state := atomic.Bool{}
|
||||||
|
|
||||||
|
wg.Add(len(s.data))
|
||||||
|
state.Store(true)
|
||||||
|
|
||||||
|
for _, e := range s.data {
|
||||||
|
if !state.Load() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e := e
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if state.Load() && !p(e) {
|
||||||
|
state.Store(false)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Collection[T]) allMatchSequential(p functions.Predicate[T]) bool {
|
||||||
|
for _, e := range s.data {
|
||||||
|
if !p(e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllMatch checks whether all elements in collection match given [functions.Predicate]
|
||||||
|
func (s *Collection[T]) AllMatch(p functions.Predicate[T]) bool {
|
||||||
|
if s.parallel {
|
||||||
|
return s.allMatchParallel(p)
|
||||||
|
}
|
||||||
|
return s.allMatchSequential(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Collection[T]) anyMatchParallel(p functions.Predicate[T]) bool {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
state := atomic.Bool{}
|
||||||
|
|
||||||
|
wg.Add(len(s.data))
|
||||||
|
state.Store(false)
|
||||||
|
|
||||||
|
for _, e := range s.data {
|
||||||
|
if state.Load() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e := e
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if state.Load() && p(e) {
|
||||||
|
state.Store(true)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Collection[T]) anyMatchSequential(p functions.Predicate[T]) bool {
|
||||||
|
for _, e := range s.data {
|
||||||
|
if p(e) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnyMatch checks whether any elements in collection match given [functions.Predicate]
|
||||||
|
func (s *Collection[T]) AnyMatch(p functions.Predicate[T]) bool {
|
||||||
|
if s.parallel {
|
||||||
|
return s.anyMatchParallel(p)
|
||||||
|
}
|
||||||
|
return s.anyMatchSequential(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Collection[T]) noneMatchParallel(p functions.Predicate[T]) bool {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
state := atomic.Bool{}
|
||||||
|
|
||||||
|
wg.Add(len(s.data))
|
||||||
|
state.Store(true)
|
||||||
|
|
||||||
|
for _, e := range s.data {
|
||||||
|
if !state.Load() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
e := e
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if state.Load() && p(e) {
|
||||||
|
state.Store(false)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return state.Load()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Collection[T]) noneMatchSequential(p functions.Predicate[T]) bool {
|
||||||
|
for _, e := range s.data {
|
||||||
|
if p(e) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// NoneMatch checks whether no elements in collection match given [functions.Predicate]
|
||||||
|
func (s *Collection[T]) NoneMatch(p functions.Predicate[T]) bool {
|
||||||
|
if s.parallel {
|
||||||
|
return s.noneMatchParallel(p)
|
||||||
|
}
|
||||||
|
return s.noneMatchSequential(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sorted sorts the collection using given [functions.Comparator]
|
||||||
|
func (s *Collection[T]) Sorted(c functions.Comparator[T]) *Collection[T] {
|
||||||
|
sort.SliceStable(s.data, func(i, j int) bool {
|
||||||
|
return c(s.data[i], s.data[j])
|
||||||
|
})
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Collection[T]) peekParallel(c functions.Consumer[T]) *Collection[T] {
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(len(s.data))
|
||||||
|
|
||||||
|
for _, e := range s.data {
|
||||||
|
e := e
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
c(e)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Collection[T]) peekSequential(c functions.Consumer[T]) *Collection[T] {
|
||||||
|
for _, e := range s.data {
|
||||||
|
c(e)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Peek executes given [function.Consumer] on every value in collections
|
||||||
|
func (s *Collection[T]) Peek(c functions.Consumer[T]) *Collection[T] {
|
||||||
|
if s.parallel {
|
||||||
|
return s.peekParallel(c)
|
||||||
|
}
|
||||||
|
return s.peekSequential(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Collection[T]) transformParallel(u functions.UnaryOperator[T]) *Collection[T] {
|
||||||
|
processed := make([]T, len(s.data))
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
|
||||||
|
wg.Add(len(s.data))
|
||||||
|
for i, e := range s.data {
|
||||||
|
i, e := i, e
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
processed[i] = u(e)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
s.data = processed
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Collection[T]) transformSequential(u functions.UnaryOperator[T]) *Collection[T] {
|
||||||
|
processed := []T{}
|
||||||
|
for _, e := range s.data {
|
||||||
|
processed = append(processed, u(e))
|
||||||
|
}
|
||||||
|
s.data = processed
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform transforms all values in collection using given [functions.UnaryOperator]
|
||||||
|
func (s *Collection[T]) Transform(u functions.UnaryOperator[T]) *Collection[T] {
|
||||||
|
if s.parallel {
|
||||||
|
return s.transformParallel(u)
|
||||||
|
}
|
||||||
|
return s.transformSequential(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reduce reduces values in given collection using given [functions.BinaryOperator]
|
||||||
|
func (s *Collection[T]) Reduce(b functions.BinaryOperator[T]) T {
|
||||||
|
processed := *new(T)
|
||||||
|
if len(s.data) == 0 {
|
||||||
|
processed = s.data[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, e := range s.data[1:] {
|
||||||
|
processed = b(processed, e)
|
||||||
|
}
|
||||||
|
return processed
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit limits the collection to given number of values
|
||||||
|
func (s *Collection[T]) Limit(n int) *Collection[T] {
|
||||||
|
if len(s.data) < n {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
if n < 0 {
|
||||||
|
n = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
s.data = s.data[:n]
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip skips given number of values in the collection
|
||||||
|
func (s *Collection[T]) Skip(n int) *Collection[T] {
|
||||||
|
if len(s.data) == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
if n > len(s.data) {
|
||||||
|
n = len(s.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.data = s.data[n:]
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindFirst returns [Optional] with first item of the collection
|
||||||
|
func (s *Collection[T]) FindFirst() *Optional[T] {
|
||||||
|
e, present := *new(T), false
|
||||||
|
if len(s.data) != 0 {
|
||||||
|
e, present = s.data[0], true
|
||||||
|
}
|
||||||
|
return &Optional[T]{e, present}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindFirst returns [Optional] with last item of the collection
|
||||||
|
func (s *Collection[T]) FindLast() *Optional[T] {
|
||||||
|
e, present := *new(T), false
|
||||||
|
if len(s.data) != 0 {
|
||||||
|
e, present = s.data[len(s.data)-1], true
|
||||||
|
}
|
||||||
|
return &Optional[T]{e, present}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Min returns the lowest value from the collection using given [functions.Comparator]
|
||||||
|
func (s *Collection[T]) Min(c functions.Comparator[T]) *Optional[T] {
|
||||||
|
return s.Sorted(c).FindFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max returns the highest value from the collection using given [functions.Comparator]
|
||||||
|
func (s *Collection[T]) Max(c functions.Comparator[T]) *Optional[T] {
|
||||||
|
return s.Sorted(c).FindLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distinct ensures that all elements in the collecion are unique
|
||||||
|
func (s *Collection[T]) Distinct() *Collection[T] {
|
||||||
|
processed := []T{}
|
||||||
|
for i := 0; i < len(s.data); i++ {
|
||||||
|
hasCopy := false
|
||||||
|
for j := i + 1; j < len(s.data); j++ {
|
||||||
|
if reflect.DeepEqual(s.data[i], s.data[j]) {
|
||||||
|
hasCopy = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !hasCopy {
|
||||||
|
processed = append(processed, s.data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.data = processed
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapCollection maps collection of values of type T to collection of values of type U using given [functions.Function]
|
||||||
|
func MapCollection[T, U any](src *Collection[T], mapper functions.Function[T, U]) *Collection[U] {
|
||||||
|
data := []U{}
|
||||||
|
for _, e := range src.data {
|
||||||
|
data = append(data, mapper(e))
|
||||||
|
}
|
||||||
|
return &Collection[U]{data, src.parallel}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect returns all items from collection as a slice
|
||||||
|
func (s *Collection[T]) Collect() []T {
|
||||||
|
return s.data
|
||||||
|
}
|
1001
collection_test.csv
Normal file
1001
collection_test.csv
Normal file
File diff suppressed because it is too large
Load Diff
56
collection_test.go
Normal file
56
collection_test.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package collections
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed collection_test.csv
|
||||||
|
var testData embed.FS
|
||||||
|
|
||||||
|
type person struct {
|
||||||
|
ID int
|
||||||
|
LastName string
|
||||||
|
FirstName string
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollection(t *testing.T) {
|
||||||
|
rawData, _ := testData.ReadFile("collection_test.csv")
|
||||||
|
data := strings.Split(string(rawData), "\n")
|
||||||
|
|
||||||
|
lines := OfSlice(data).Skip(1).Filter(func(s string) bool {
|
||||||
|
return strings.Contains(s, ",")
|
||||||
|
})
|
||||||
|
|
||||||
|
firstGloryWithComMail := MapCollection(
|
||||||
|
lines,
|
||||||
|
func(line string) person {
|
||||||
|
elements := strings.Split(line, ",")
|
||||||
|
id, _ := strconv.Atoi(elements[0])
|
||||||
|
return person{
|
||||||
|
ID: id,
|
||||||
|
LastName: elements[1],
|
||||||
|
FirstName: elements[2],
|
||||||
|
Email: elements[3],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
).Filter(func(p person) bool {
|
||||||
|
return strings.HasSuffix(p.Email, ".com")
|
||||||
|
}).Filter(func(p person) bool {
|
||||||
|
return p.FirstName == "Glory"
|
||||||
|
}).Sorted(func(p1, p2 person) bool {
|
||||||
|
return p1.ID < p2.ID
|
||||||
|
}).FindFirst().Get()
|
||||||
|
|
||||||
|
require.EqualValues(t, person{
|
||||||
|
ID: 62,
|
||||||
|
LastName: "Joss",
|
||||||
|
FirstName: "Glory",
|
||||||
|
Email: "gjoss1p@taobao.com",
|
||||||
|
}, firstGloryWithComMail)
|
||||||
|
}
|
10
comparators/numeric.go
Normal file
10
comparators/numeric.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package comparators
|
||||||
|
|
||||||
|
import "pkg.icikowski.pl/collections/functions"
|
||||||
|
|
||||||
|
// CompareNumeric returns comparator for numeric values
|
||||||
|
func CompareNumeric[T ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64]() functions.Comparator[T] {
|
||||||
|
return func(a, b T) bool {
|
||||||
|
return a < b
|
||||||
|
}
|
||||||
|
}
|
4
comparators/package.go
Normal file
4
comparators/package.go
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
Handy comparators
|
||||||
|
*/
|
||||||
|
package comparators
|
15
comparators/string.go
Normal file
15
comparators/string.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package comparators
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/text/collate"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
"pkg.icikowski.pl/collections/functions"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CompareString returns [functions.Comparator] for string values in given language
|
||||||
|
func CompareString(lang language.Tag) functions.Comparator[string] {
|
||||||
|
c := collate.New(lang)
|
||||||
|
return func(a, b string) bool {
|
||||||
|
return c.CompareString(a, b) < 0
|
||||||
|
}
|
||||||
|
}
|
39
functions.go
Normal file
39
functions.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package collections
|
||||||
|
|
||||||
|
import "pkg.icikowski.pl/collections/functions"
|
||||||
|
|
||||||
|
// Of creates [Collection] of given items
|
||||||
|
func Of[T any](data ...T) *Collection[T] {
|
||||||
|
return &Collection[T]{data, true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OfSlice creates [Collection] of given slice elements
|
||||||
|
func OfSlice[T any](data []T) *Collection[T] {
|
||||||
|
return &Collection[T]{data, true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate creates [Collection] of given number of items generated from given seed and [functions.UnaryOperator]
|
||||||
|
func Iterate[T any](seed T, u functions.UnaryOperator[T], limit int) *Collection[T] {
|
||||||
|
if limit <= 0 {
|
||||||
|
limit = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []T{seed}
|
||||||
|
for i := 1; i < limit; i++ {
|
||||||
|
data = append(data, u(data[len(data)-1]))
|
||||||
|
}
|
||||||
|
return &Collection[T]{data, true}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate creates [Collection] of given number of items generated from given [functions.Supplier]
|
||||||
|
func Generate[T any](p functions.Supplier[T], limit int) *Collection[T] {
|
||||||
|
if limit < 0 {
|
||||||
|
limit = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []T{}
|
||||||
|
for i := 0; i < limit; i++ {
|
||||||
|
data = append(data, p())
|
||||||
|
}
|
||||||
|
return &Collection[T]{data, true}
|
||||||
|
}
|
4
functions/package.go
Normal file
4
functions/package.go
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
Function types
|
||||||
|
*/
|
||||||
|
package functions
|
24
functions/types.go
Normal file
24
functions/types.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package functions
|
||||||
|
|
||||||
|
type (
|
||||||
|
// Consumer takes an argument of type T and returns nothing
|
||||||
|
Consumer[T any] func(T)
|
||||||
|
|
||||||
|
// Function takes an argument of type T and returns a result of type U
|
||||||
|
Function[T, U any] func(T) U
|
||||||
|
|
||||||
|
// Predicate takes an argument of type T and returns a boolean result
|
||||||
|
Predicate[T any] func(T) bool
|
||||||
|
|
||||||
|
// Supplier takes no argument and returns a result of type T
|
||||||
|
Supplier[T any] func() T
|
||||||
|
|
||||||
|
// UnaryOperator takes an argument of type T and returns a result of type T
|
||||||
|
UnaryOperator[T any] func(T) T
|
||||||
|
|
||||||
|
// BinaryOperator takes two arguments of type T and returns a result of type T
|
||||||
|
BinaryOperator[T any] func(T, T) T
|
||||||
|
|
||||||
|
// Comparator takes two arguments of type T and returns a boolean result
|
||||||
|
Comparator[T any] func(T, T) bool
|
||||||
|
)
|
11
go.mod
Normal file
11
go.mod
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
module pkg.icikowski.pl/collections
|
||||||
|
|
||||||
|
go 1.20
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.8.4 // indirect
|
||||||
|
golang.org/x/text v0.11.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
11
go.sum
Normal file
11
go.sum
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
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/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/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||||
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
48
optional.go
Normal file
48
optional.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package collections
|
||||||
|
|
||||||
|
import "pkg.icikowski.pl/collections/functions"
|
||||||
|
|
||||||
|
// Optional represents an optional value of type T
|
||||||
|
type Optional[T any] struct {
|
||||||
|
e T
|
||||||
|
present bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsPresent determines whether the underlying value is present
|
||||||
|
func (o *Optional[T]) IsPresent() bool {
|
||||||
|
return o.present
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns the underlying value
|
||||||
|
func (o *Optional[T]) Get() T {
|
||||||
|
return o.e
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrElse returns the underlying value or given value if underlying value is not present
|
||||||
|
func (o *Optional[T]) OrElse(e T) T {
|
||||||
|
if !o.present {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return o.e
|
||||||
|
}
|
||||||
|
|
||||||
|
// OrElseGet returns the underlying value or value from given supplier if underlying value is not present
|
||||||
|
func (o *Optional[T]) OrElseGet(s functions.Supplier[T]) T {
|
||||||
|
if !o.present {
|
||||||
|
return s()
|
||||||
|
}
|
||||||
|
return o.e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform transforms the underlying value with given [functions.UnaryOperator] if the vale is present
|
||||||
|
func (o *Optional[T]) Transform(u functions.UnaryOperator[T]) *Optional[T] {
|
||||||
|
if o.present {
|
||||||
|
o.e = u(o.e)
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapOptional maps optional of type T to optional of type U using given [functions.Function]
|
||||||
|
func MapOptional[T, U any](src *Optional[T], mapper functions.Function[T, U]) *Optional[U] {
|
||||||
|
return &Optional[U]{mapper(src.e), src.present}
|
||||||
|
}
|
128
optional_test.go
Normal file
128
optional_test.go
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
package collections
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestIsPresent(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
optional Optional[string]
|
||||||
|
expectedResult bool
|
||||||
|
}{
|
||||||
|
"not present": {
|
||||||
|
optional: Optional[string]{},
|
||||||
|
expectedResult: false,
|
||||||
|
},
|
||||||
|
"present": {
|
||||||
|
optional: Optional[string]{
|
||||||
|
present: true,
|
||||||
|
},
|
||||||
|
expectedResult: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range tests {
|
||||||
|
name, tc := name, tc
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
require.Equal(t, tc.expectedResult, tc.optional.IsPresent())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
optional Optional[string]
|
||||||
|
expectedResult string
|
||||||
|
}{
|
||||||
|
"not present": {
|
||||||
|
optional: Optional[string]{},
|
||||||
|
expectedResult: "",
|
||||||
|
},
|
||||||
|
"present": {
|
||||||
|
optional: Optional[string]{
|
||||||
|
e: "foo",
|
||||||
|
present: true,
|
||||||
|
},
|
||||||
|
expectedResult: "foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range tests {
|
||||||
|
name, tc := name, tc
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
require.Equal(t, tc.expectedResult, tc.optional.Get())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrElse(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
optional Optional[string]
|
||||||
|
expectedResult string
|
||||||
|
}{
|
||||||
|
"not present": {
|
||||||
|
optional: Optional[string]{},
|
||||||
|
expectedResult: "bar",
|
||||||
|
},
|
||||||
|
"present": {
|
||||||
|
optional: Optional[string]{
|
||||||
|
e: "foo",
|
||||||
|
present: true,
|
||||||
|
},
|
||||||
|
expectedResult: "foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range tests {
|
||||||
|
name, tc := name, tc
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
require.Equal(t, tc.expectedResult, tc.optional.OrElse("bar"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrElseGet(t *testing.T) {
|
||||||
|
tests := map[string]struct {
|
||||||
|
optional Optional[string]
|
||||||
|
expectedResult string
|
||||||
|
}{
|
||||||
|
"not present": {
|
||||||
|
optional: Optional[string]{},
|
||||||
|
expectedResult: "bar",
|
||||||
|
},
|
||||||
|
"present": {
|
||||||
|
optional: Optional[string]{
|
||||||
|
e: "foo",
|
||||||
|
present: true,
|
||||||
|
},
|
||||||
|
expectedResult: "foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range tests {
|
||||||
|
name, tc := name, tc
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
require.Equal(t, tc.expectedResult, tc.optional.OrElseGet(func() string {
|
||||||
|
return "bar"
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransform(t *testing.T) {
|
||||||
|
given := &Optional[string]{
|
||||||
|
e: "foo",
|
||||||
|
present: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
transformer := func(s string) string {
|
||||||
|
return strings.ToUpper(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
result := MapOptional(given, transformer)
|
||||||
|
|
||||||
|
require.Equal(t, "FOO", result.Get())
|
||||||
|
}
|
4
package.go
Normal file
4
package.go
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/*
|
||||||
|
Collections-related types and functions
|
||||||
|
*/
|
||||||
|
package collections
|
Loading…
Reference in New Issue
Block a user