diff --git a/.vscode/settings.json b/.vscode/settings.json index 8851028..7c24837 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,6 +11,7 @@ "bodyclose", "cenkalti", "Chasinga", + "Clonable", "cmds", "Cobrass", "coverpkg", diff --git a/boost/annotated-wait-group.go b/boost/annotated-wait-group.go index 8d4d06c..6d5763a 100644 --- a/boost/annotated-wait-group.go +++ b/boost/annotated-wait-group.go @@ -6,7 +6,7 @@ import ( "sync" "sync/atomic" - "github.com/samber/lo" + "github.com/snivilised/lorax/internal/lo" "go.uber.org/zap/exp/zapslog" "go.uber.org/zap/zapcore" ) diff --git a/boost/worker-pool-legacy.go b/boost/worker-pool-legacy.go index 8ddab7d..a75fdd8 100644 --- a/boost/worker-pool-legacy.go +++ b/boost/worker-pool-legacy.go @@ -8,7 +8,7 @@ import ( "time" "github.com/google/uuid" - "github.com/samber/lo" + "github.com/snivilised/lorax/internal/lo" "go.uber.org/zap/exp/zapslog" "go.uber.org/zap/zapcore" ) diff --git a/boost/worker-pool-legacy_test.go b/boost/worker-pool-legacy_test.go index 9a2005f..4c816e6 100644 --- a/boost/worker-pool-legacy_test.go +++ b/boost/worker-pool-legacy_test.go @@ -9,7 +9,7 @@ import ( "github.com/fortytw2/leaktest" . "github.com/onsi/ginkgo/v2" //nolint:revive // ginkgo ok . "github.com/onsi/gomega" //nolint:revive // gomega ok - "github.com/samber/lo" + "github.com/snivilised/lorax/internal/lo" "github.com/snivilised/lorax/boost" "github.com/snivilised/lorax/internal/helpers" diff --git a/go.mod b/go.mod index 59de07f..2ff29d2 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.22.0 require ( github.com/onsi/ginkgo/v2 v2.20.0 github.com/onsi/gomega v1.34.1 - github.com/samber/lo v1.46.0 + github.com/samber/lo v1.46.0 // indirect github.com/snivilised/extendio v0.7.0 ) diff --git a/internal/lo/constraints.go b/internal/lo/constraints.go new file mode 100644 index 0000000..bf4a872 --- /dev/null +++ b/internal/lo/constraints.go @@ -0,0 +1,10 @@ +package lo + +// MIT License +// +// Copyright (c) 2022 Samuel Berthe + +// Clonable defines a constraint of types having Clone() T method. +type Clonable[T any] interface { + Clone() T +} diff --git a/internal/lo/intersect.go b/internal/lo/intersect.go new file mode 100644 index 0000000..b31f56a --- /dev/null +++ b/internal/lo/intersect.go @@ -0,0 +1,185 @@ +package lo + +// Contains returns true if an element is present in a collection. +func Contains[T comparable](collection []T, element T) bool { + for _, item := range collection { + if item == element { + return true + } + } + + return false +} + +// ContainsBy returns true if predicate function return true. +func ContainsBy[T any](collection []T, predicate func(item T) bool) bool { + for _, item := range collection { + if predicate(item) { + return true + } + } + + return false +} + +// Every returns true if all elements of a subset are contained into a collection or if the subset is empty. +func Every[T comparable](collection, subset []T) bool { + for _, elem := range subset { + if !Contains(collection, elem) { + return false + } + } + + return true +} + +// EveryBy returns true if the predicate returns true for all of the elements in the collection or if the collection is empty. +func EveryBy[T any](collection []T, predicate func(item T) bool) bool { + for _, v := range collection { + if !predicate(v) { + return false + } + } + + return true +} + +// Some returns true if at least 1 element of a subset is contained into a collection. +// If the subset is empty Some returns false. +func Some[T comparable](collection, subset []T) bool { + for _, elem := range subset { + if Contains(collection, elem) { + return true + } + } + + return false +} + +// SomeBy returns true if the predicate returns true for any of the elements in the collection. +// If the collection is empty SomeBy returns false. +func SomeBy[T any](collection []T, predicate func(item T) bool) bool { + for _, v := range collection { + if predicate(v) { + return true + } + } + + return false +} + +// None returns true if no element of a subset are contained into a collection or if the subset is empty. +func None[T comparable](collection, subset []T) bool { + for _, elem := range subset { + if Contains(collection, elem) { + return false + } + } + + return true +} + +// NoneBy returns true if the predicate returns true for none of the elements in the collection or if the collection is empty. +func NoneBy[T any](collection []T, predicate func(item T) bool) bool { + for _, v := range collection { + if predicate(v) { + return false + } + } + + return true +} + +// Intersect returns the intersection between two collections. +func Intersect[T comparable](list1, list2 []T) []T { + result := []T{} + seen := map[T]struct{}{} + + for _, elem := range list1 { + seen[elem] = struct{}{} + } + + for _, elem := range list2 { + if _, ok := seen[elem]; ok { + result = append(result, elem) + } + } + + return result +} + +// Difference returns the difference between two collections. +// The first value is the collection of element absent of list2. +// The second value is the collection of element absent of list1. +func Difference[T comparable](list1, list2 []T) (left, right []T) { + left = []T{} + right = []T{} + + seenLeft := map[T]struct{}{} + seenRight := map[T]struct{}{} + + for _, elem := range list1 { + seenLeft[elem] = struct{}{} + } + + for _, elem := range list2 { + seenRight[elem] = struct{}{} + } + + for _, elem := range list1 { + if _, ok := seenRight[elem]; !ok { + left = append(left, elem) + } + } + + for _, elem := range list2 { + if _, ok := seenLeft[elem]; !ok { + right = append(right, elem) + } + } + + return left, right +} + +// Union returns all distinct elements from given collections. +// result returns will not change the order of elements relatively. +func Union[T comparable](lists ...[]T) []T { + result := []T{} + seen := map[T]struct{}{} + + for _, list := range lists { + for _, e := range list { + if _, ok := seen[e]; !ok { + seen[e] = struct{}{} + result = append(result, e) + } + } + } + + return result +} + +// Without returns slice excluding all given values. +func Without[T comparable](collection []T, exclude ...T) []T { + result := make([]T, 0, len(collection)) + for _, e := range collection { + if !Contains(exclude, e) { + result = append(result, e) + } + } + return result +} + +// WithoutEmpty returns slice excluding empty values. +func WithoutEmpty[T comparable](collection []T) []T { + var empty T + + result := make([]T, 0, len(collection)) + for _, e := range collection { + if e != empty { + result = append(result, e) + } + } + + return result +} diff --git a/internal/lo/map.go b/internal/lo/map.go new file mode 100644 index 0000000..9c0ac48 --- /dev/null +++ b/internal/lo/map.go @@ -0,0 +1,224 @@ +package lo + +// Keys creates an array of the map keys. +// Play: https://go.dev/play/p/Uu11fHASqrU +func Keys[K comparable, V any](in map[K]V) []K { + result := make([]K, 0, len(in)) + + for k := range in { + result = append(result, k) + } + + return result +} + +// Values creates an array of the map values. +// Play: https://go.dev/play/p/nnRTQkzQfF6 +func Values[K comparable, V any](in map[K]V) []V { + result := make([]V, 0, len(in)) + + for _, v := range in { + result = append(result, v) + } + + return result +} + +// ValueOr returns the value of the given key or the fallback value if the key is not present. +// Play: https://go.dev/play/p/bAq9mHErB4V +func ValueOr[K comparable, V any](in map[K]V, key K, fallback V) V { + if v, ok := in[key]; ok { + return v + } + return fallback +} + +// PickBy returns same map type filtered by given predicate. +// Play: https://go.dev/play/p/kdg8GR_QMmf +func PickBy[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) map[K]V { + r := map[K]V{} + for k, v := range in { + if predicate(k, v) { + r[k] = v + } + } + return r +} + +// PickByKeys returns same map type filtered by given keys. +// Play: https://go.dev/play/p/R1imbuci9qU +func PickByKeys[K comparable, V any](in map[K]V, keys []K) map[K]V { + r := map[K]V{} + for k, v := range in { + if Contains(keys, k) { + r[k] = v + } + } + return r +} + +// PickByValues returns same map type filtered by given values. +// Play: https://go.dev/play/p/1zdzSvbfsJc +func PickByValues[K comparable, V comparable](in map[K]V, values []V) map[K]V { + r := map[K]V{} + for k, v := range in { + if Contains(values, v) { + r[k] = v + } + } + return r +} + +// OmitBy returns same map type filtered by given predicate. +// Play: https://go.dev/play/p/EtBsR43bdsd +func OmitBy[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) map[K]V { + r := map[K]V{} + for k, v := range in { + if !predicate(k, v) { + r[k] = v + } + } + return r +} + +// OmitByKeys returns same map type filtered by given keys. +// Play: https://go.dev/play/p/t1QjCrs-ysk +func OmitByKeys[K comparable, V any](in map[K]V, keys []K) map[K]V { + r := map[K]V{} + for k, v := range in { + if !Contains(keys, k) { + r[k] = v + } + } + return r +} + +// OmitByValues returns same map type filtered by given values. +// Play: https://go.dev/play/p/9UYZi-hrs8j +func OmitByValues[K comparable, V comparable](in map[K]V, values []V) map[K]V { + r := map[K]V{} + for k, v := range in { + if !Contains(values, v) { + r[k] = v + } + } + return r +} + +// Entries transforms a map into array of key/value pairs. +// Play: +func Entries[K comparable, V any](in map[K]V) []Entry[K, V] { + entries := make([]Entry[K, V], 0, len(in)) + + for k, v := range in { + entries = append(entries, Entry[K, V]{ + Key: k, + Value: v, + }) + } + + return entries +} + +// ToPairs transforms a map into array of key/value pairs. +// Alias of Entries(). +// Play: https://go.dev/play/p/3Dhgx46gawJ +func ToPairs[K comparable, V any](in map[K]V) []Entry[K, V] { + return Entries(in) +} + +// FromEntries transforms an array of key/value pairs into a map. +// Play: https://go.dev/play/p/oIr5KHFGCEN +func FromEntries[K comparable, V any](entries []Entry[K, V]) map[K]V { + out := make(map[K]V, len(entries)) + + for _, v := range entries { + out[v.Key] = v.Value + } + + return out +} + +// FromPairs transforms an array of key/value pairs into a map. +// Alias of FromEntries(). +// Play: https://go.dev/play/p/oIr5KHFGCEN +func FromPairs[K comparable, V any](entries []Entry[K, V]) map[K]V { + return FromEntries(entries) +} + +// Invert creates a map composed of the inverted keys and values. If map +// contains duplicate values, subsequent values overwrite property assignments +// of previous values. +// Play: https://go.dev/play/p/rFQ4rak6iA1 +func Invert[K comparable, V comparable](in map[K]V) map[V]K { + out := make(map[V]K, len(in)) + + for k, v := range in { + out[v] = k + } + + return out +} + +// Assign merges multiple maps from left to right. +// Play: https://go.dev/play/p/VhwfJOyxf5o +func Assign[K comparable, V any](maps ...map[K]V) map[K]V { + out := map[K]V{} + + for _, m := range maps { + for k, v := range m { + out[k] = v + } + } + + return out +} + +// MapKeys manipulates a map keys and transforms it to a map of another type. +// Play: https://go.dev/play/p/9_4WPIqOetJ +func MapKeys[K comparable, V any, R comparable](in map[K]V, iteratee func(value V, key K) R) map[R]V { + result := make(map[R]V, len(in)) + + for k, v := range in { + result[iteratee(v, k)] = v + } + + return result +} + +// MapValues manipulates a map values and transforms it to a map of another type. +// Play: https://go.dev/play/p/T_8xAfvcf0W +func MapValues[K comparable, V any, R any](in map[K]V, iteratee func(value V, key K) R) map[K]R { + result := make(map[K]R, len(in)) + + for k, v := range in { + result[k] = iteratee(v, k) + } + + return result +} + +// MapEntries manipulates a map entries and transforms it to a map of another type. +// Play: https://go.dev/play/p/VuvNQzxKimT +func MapEntries[K1 comparable, V1 any, K2 comparable, V2 any](in map[K1]V1, iteratee func(key K1, value V1) (K2, V2)) map[K2]V2 { + result := make(map[K2]V2, len(in)) + + for k1, v1 := range in { + k2, v2 := iteratee(k1, v1) + result[k2] = v2 + } + + return result +} + +// MapToSlice transforms a map into a slice based on specific iteratee +// Play: https://go.dev/play/p/ZuiCZpDt6LD +func MapToSlice[K comparable, V any, R any](in map[K]V, iteratee func(key K, value V) R) []R { + result := make([]R, 0, len(in)) + + for k, v := range in { + result = append(result, iteratee(k, v)) + } + + return result +} diff --git a/internal/lo/math.go b/internal/lo/math.go new file mode 100644 index 0000000..6f9647d --- /dev/null +++ b/internal/lo/math.go @@ -0,0 +1,86 @@ +package lo + +import ( + "golang.org/x/exp/constraints" +) + +// Range creates an array of numbers (positive and/or negative) with given length. +// Play: https://go.dev/play/p/0r6VimXAi9H +func Range(elementNum int) []int { + length := If(elementNum < 0, -elementNum).Else(elementNum) + result := make([]int, length) + step := If(elementNum < 0, -1).Else(1) + for i, j := 0, 0; i < length; i, j = i+1, j+step { + result[i] = j + } + return result +} + +// RangeFrom creates an array of numbers from start with specified length. +// Play: https://go.dev/play/p/0r6VimXAi9H +func RangeFrom[T constraints.Integer | constraints.Float](start T, elementNum int) []T { + length := If(elementNum < 0, -elementNum).Else(elementNum) + result := make([]T, length) + step := If(elementNum < 0, -1).Else(1) + for i, j := 0, start; i < length; i, j = i+1, j+T(step) { + result[i] = j + } + return result +} + +// RangeWithSteps creates an array of numbers (positive and/or negative) progressing from start up to, but not including end. +// step set to zero will return empty array. +// Play: https://go.dev/play/p/0r6VimXAi9H +func RangeWithSteps[T constraints.Integer | constraints.Float](start, end, step T) []T { + result := []T{} + if start == end || step == 0 { + return result + } + if start < end { + if step < 0 { + return result + } + for i := start; i < end; i += step { + result = append(result, i) + } + return result + } + if step > 0 { + return result + } + for i := start; i > end; i += step { + result = append(result, i) + } + return result +} + +// Clamp clamps number within the inclusive lower and upper bounds. +// Play: https://go.dev/play/p/RU4lJNC2hlI +func Clamp[T constraints.Ordered](value, min, max T) T { + if value < min { + return min + } else if value > max { + return max + } + return value +} + +// Sum sums the values in a collection. If collection is empty 0 is returned. +// Play: https://go.dev/play/p/upfeJVqs4Bt +func Sum[T constraints.Float | constraints.Integer | constraints.Complex](collection []T) T { + var sum T + for _, val := range collection { + sum += val + } + return sum +} + +// SumBy summarizes the values in a collection using the given return value from the iteration function. If collection is empty 0 is returned. +// Play: https://go.dev/play/p/Dz_a_7jN_ca +func SumBy[T any, R constraints.Float | constraints.Integer | constraints.Complex](collection []T, iteratee func(item T) R) R { + var sum R + for _, item := range collection { + sum += iteratee(item) + } + return sum +} diff --git a/internal/lo/slice.go b/internal/lo/slice.go new file mode 100644 index 0000000..e760559 --- /dev/null +++ b/internal/lo/slice.go @@ -0,0 +1,595 @@ +package lo + +// MIT License +// +// Copyright (c) 2022 Samuel Berthe + +import ( + "math/rand" + + "golang.org/x/exp/constraints" +) + +// Filter iterates over elements of collection, returning an array of all elements predicate returns truthy for. +// Play: https://go.dev/play/p/Apjg3WeSi7K +func Filter[V any](collection []V, predicate func(item V, index int) bool) []V { + result := make([]V, 0, len(collection)) + + for i, item := range collection { + if predicate(item, i) { + result = append(result, item) + } + } + + return result +} + +// Map manipulates a slice and transforms it to a slice of another type. +// Play: https://go.dev/play/p/OkPcYAhBo0D +func Map[T any, R any](collection []T, iteratee func(item T, index int) R) []R { + result := make([]R, len(collection)) + + for i, item := range collection { + result[i] = iteratee(item, i) + } + + return result +} + +// FilterMap returns a slice which obtained after both filtering and mapping using the given callback function. +// The callback function should return two values: +// - the result of the mapping operation and +// - whether the result element should be included or not. +// +// Play: https://go.dev/play/p/-AuYXfy7opz +func FilterMap[T any, R any](collection []T, callback func(item T, index int) (R, bool)) []R { + result := []R{} + + for i, item := range collection { + if r, ok := callback(item, i); ok { + result = append(result, r) + } + } + + return result +} + +// FlatMap manipulates a slice and transforms and flattens it to a slice of another type. +// The transform function can either return a slice or a `nil`, and in the `nil` case +// no value is added to the final slice. +// Play: https://go.dev/play/p/YSoYmQTA8-U +func FlatMap[T any, R any](collection []T, iteratee func(item T, index int) []R) []R { + result := make([]R, 0, len(collection)) + + for i, item := range collection { + result = append(result, iteratee(item, i)...) + } + + return result +} + +// Reduce reduces collection to a value which is the accumulated result of running each element in collection +// through accumulator, where each successive invocation is supplied the return value of the previous. +// Play: https://go.dev/play/p/R4UHXZNaaUG +func Reduce[T any, R any](collection []T, accumulator func(agg R, item T, index int) R, initial R) R { + for i, item := range collection { + initial = accumulator(initial, item, i) + } + + return initial +} + +// ReduceRight helper is like Reduce except that it iterates over elements of collection from right to left. +// Play: https://go.dev/play/p/Fq3W70l7wXF +func ReduceRight[T any, R any](collection []T, accumulator func(agg R, item T, index int) R, initial R) R { + for i := len(collection) - 1; i >= 0; i-- { + initial = accumulator(initial, collection[i], i) + } + + return initial +} + +// ForEach iterates over elements of collection and invokes iteratee for each element. +// Play: https://go.dev/play/p/oofyiUPRf8t +func ForEach[T any](collection []T, iteratee func(item T, index int)) { + for i, item := range collection { + iteratee(item, i) + } +} + +// Times invokes the iteratee n times, returning an array of the results of each invocation. +// The iteratee is invoked with index as argument. +// Play: https://go.dev/play/p/vgQj3Glr6lT +func Times[T any](count int, iteratee func(index int) T) []T { + result := make([]T, count) + + for i := 0; i < count; i++ { + result[i] = iteratee(i) + } + + return result +} + +// Uniq returns a duplicate-free version of an array, in which only the first occurrence of each element is kept. +// The order of result values is determined by the order they occur in the array. +// Play: https://go.dev/play/p/DTzbeXZ6iEN +func Uniq[T comparable](collection []T) []T { + result := make([]T, 0, len(collection)) + seen := make(map[T]struct{}, len(collection)) + + for _, item := range collection { + if _, ok := seen[item]; ok { + continue + } + + seen[item] = struct{}{} + result = append(result, item) + } + + return result +} + +// UniqBy returns a duplicate-free version of an array, in which only the first occurrence of each element is kept. +// The order of result values is determined by the order they occur in the array. It accepts `iteratee` which is +// invoked for each element in array to generate the criterion by which uniqueness is computed. +// Play: https://go.dev/play/p/g42Z3QSb53u +func UniqBy[T any, U comparable](collection []T, iteratee func(item T) U) []T { + result := make([]T, 0, len(collection)) + seen := make(map[U]struct{}, len(collection)) + + for _, item := range collection { + key := iteratee(item) + + if _, ok := seen[key]; ok { + continue + } + + seen[key] = struct{}{} + result = append(result, item) + } + + return result +} + +// GroupBy returns an object composed of keys generated from the results of running each element of collection through iteratee. +// Play: https://go.dev/play/p/XnQBd_v6brd +func GroupBy[T any, U comparable](collection []T, iteratee func(item T) U) map[U][]T { + result := map[U][]T{} + + for _, item := range collection { + key := iteratee(item) + + result[key] = append(result[key], item) + } + + return result +} + +// Chunk returns an array of elements split into groups the length of size. If array can't be split evenly, +// the final chunk will be the remaining elements. +// Play: https://go.dev/play/p/EeKl0AuTehH +func Chunk[T any](collection []T, size int) [][]T { + if size <= 0 { + panic("Second parameter must be greater than 0") + } + + chunksNum := len(collection) / size + if len(collection)%size != 0 { + chunksNum++ + } + + result := make([][]T, 0, chunksNum) + + for i := 0; i < chunksNum; i++ { + last := (i + 1) * size + if last > len(collection) { + last = len(collection) + } + result = append(result, collection[i*size:last]) + } + + return result +} + +// PartitionBy returns an array of elements split into groups. The order of grouped values is +// determined by the order they occur in collection. The grouping is generated from the results +// of running each element of collection through iteratee. +// Play: https://go.dev/play/p/NfQ_nGjkgXW +func PartitionBy[T any, K comparable](collection []T, iteratee func(item T) K) [][]T { + result := [][]T{} + seen := map[K]int{} + + for _, item := range collection { + key := iteratee(item) + + resultIndex, ok := seen[key] + if !ok { + resultIndex = len(result) + seen[key] = resultIndex + result = append(result, []T{}) + } + + result[resultIndex] = append(result[resultIndex], item) + } + + return result +} + +// Flatten returns an array a single level deep. +// Play: https://go.dev/play/p/rbp9ORaMpjw +func Flatten[T any](collection [][]T) []T { + totalLen := 0 + for i := range collection { + totalLen += len(collection[i]) + } + + result := make([]T, 0, totalLen) + for i := range collection { + result = append(result, collection[i]...) + } + + return result +} + +// Interleave round-robin alternating input slices and sequentially appending value at index into result +// Play: https://go.dev/play/p/DDhlwrShbwe +func Interleave[T any](collections ...[]T) []T { + if len(collections) == 0 { + return []T{} + } + + maxSize := 0 + totalSize := 0 + for _, c := range collections { + size := len(c) + totalSize += size + if size > maxSize { + maxSize = size + } + } + + if maxSize == 0 { + return []T{} + } + + result := make([]T, totalSize) + + resultIdx := 0 + for i := 0; i < maxSize; i++ { + for j := range collections { + if len(collections[j])-1 < i { + continue + } + + result[resultIdx] = collections[j][i] + resultIdx++ + } + } + + return result +} + +// Shuffle returns an array of shuffled values. Uses the Fisher-Yates shuffle algorithm. +// Play: https://go.dev/play/p/Qp73bnTDnc7 +func Shuffle[T any](collection []T) []T { + rand.Shuffle(len(collection), func(i, j int) { + collection[i], collection[j] = collection[j], collection[i] + }) + + return collection +} + +// Reverse reverses array so that the first element becomes the last, the second element becomes the second to last, and so on. +// Play: https://go.dev/play/p/fhUMLvZ7vS6 +func Reverse[T any](collection []T) []T { + const two = 2 + length := len(collection) + half := length / two + + for i := 0; i < half; i++ { + j := length - 1 - i + collection[i], collection[j] = collection[j], collection[i] + } + + return collection +} + +// Fill fills elements of array with `initial` value. +// Play: https://go.dev/play/p/VwR34GzqEub +func Fill[T Clonable[T]](collection []T, initial T) []T { + result := make([]T, 0, len(collection)) + + for range collection { + result = append(result, initial.Clone()) + } + + return result +} + +// Repeat builds a slice with N copies of initial value. +// Play: https://go.dev/play/p/g3uHXbmc3b6 +func Repeat[T Clonable[T]](count int, initial T) []T { + result := make([]T, 0, count) + + for i := 0; i < count; i++ { + result = append(result, initial.Clone()) + } + + return result +} + +// RepeatBy builds a slice with values returned by N calls of callback. +// Play: https://go.dev/play/p/ozZLCtX_hNU +func RepeatBy[T any](count int, predicate func(index int) T) []T { + result := make([]T, 0, count) + + for i := 0; i < count; i++ { + result = append(result, predicate(i)) + } + + return result +} + +// KeyBy transforms a slice or an array of structs to a map based on a pivot callback. +// Play: https://go.dev/play/p/mdaClUAT-zZ +func KeyBy[K comparable, V any](collection []V, iteratee func(item V) K) map[K]V { + result := make(map[K]V, len(collection)) + + for _, v := range collection { + k := iteratee(v) + result[k] = v + } + + return result +} + +// Associate returns a map containing key-value pairs provided by transform function applied to elements of the given slice. +// If any of two pairs would have the same key the last one gets added to the map. +// The order of keys in returned map is not specified and is not guaranteed to be the same from the original array. +// Play: https://go.dev/play/p/WHa2CfMO3Lr +func Associate[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K]V { + result := make(map[K]V, len(collection)) + + for _, t := range collection { + k, v := transform(t) + result[k] = v + } + + return result +} + +// SliceToMap returns a map containing key-value pairs provided by transform function applied to elements of the given slice. +// If any of two pairs would have the same key the last one gets added to the map. +// The order of keys in returned map is not specified and is not guaranteed to be the same from the original array. +// Alias of Associate(). +// Play: https://go.dev/play/p/WHa2CfMO3Lr +func SliceToMap[T any, K comparable, V any](collection []T, transform func(item T) (K, V)) map[K]V { + return Associate(collection, transform) +} + +// Drop drops n elements from the beginning of a slice or array. +// Play: https://go.dev/play/p/JswS7vXRJP2 +func Drop[T any](collection []T, n int) []T { + if len(collection) <= n { + return make([]T, 0) + } + + result := make([]T, 0, len(collection)-n) + + return append(result, collection[n:]...) +} + +// DropRight drops n elements from the end of a slice or array. +// Play: https://go.dev/play/p/GG0nXkSJJa3 +func DropRight[T any](collection []T, n int) []T { + if len(collection) <= n { + return []T{} + } + + result := make([]T, 0, len(collection)-n) + return append(result, collection[:len(collection)-n]...) +} + +// DropWhile drops elements from the beginning of a slice or array while the predicate returns true. +// Play: https://go.dev/play/p/7gBPYw2IK16 +func DropWhile[T any](collection []T, predicate func(item T) bool) []T { + i := 0 + for ; i < len(collection); i++ { + if !predicate(collection[i]) { + break + } + } + + result := make([]T, 0, len(collection)-i) + return append(result, collection[i:]...) +} + +// DropRightWhile drops elements from the end of a slice or array while the predicate returns true. +// Play: https://go.dev/play/p/3-n71oEC0Hz +func DropRightWhile[T any](collection []T, predicate func(item T) bool) []T { + i := len(collection) - 1 + for ; i >= 0; i-- { + if !predicate(collection[i]) { + break + } + } + + result := make([]T, 0, i+1) + return append(result, collection[:i+1]...) +} + +// Reject is the opposite of Filter, this method returns the elements of collection that predicate does not return truthy for. +// Play: https://go.dev/play/p/YkLMODy1WEL +func Reject[V any](collection []V, predicate func(item V, index int) bool) []V { + result := []V{} + + for i, item := range collection { + if !predicate(item, i) { + result = append(result, item) + } + } + + return result +} + +// Count counts the number of elements in the collection that compare equal to value. +// Play: https://go.dev/play/p/Y3FlK54yveC +func Count[T comparable](collection []T, value T) (count int) { + for _, item := range collection { + if item == value { + count++ + } + } + + return count +} + +// CountBy counts the number of elements in the collection for which predicate is true. +// Play: https://go.dev/play/p/ByQbNYQQi4X +func CountBy[T any](collection []T, predicate func(item T) bool) (count int) { + for _, item := range collection { + if predicate(item) { + count++ + } + } + + return count +} + +// CountValues counts the number of each element in the collection. +// Play: https://go.dev/play/p/-p-PyLT4dfy +func CountValues[T comparable](collection []T) map[T]int { + result := make(map[T]int) + + for _, item := range collection { + result[item]++ + } + + return result +} + +// CountValuesBy counts the number of each element return from mapper function. +// Is equivalent to chaining lo.Map and lo.CountValues. +// Play: https://go.dev/play/p/2U0dG1SnOmS +func CountValuesBy[T any, U comparable](collection []T, mapper func(item T) U) map[U]int { + result := make(map[U]int) + + for _, item := range collection { + result[mapper(item)]++ + } + + return result +} + +// Subset returns a copy of a slice from `offset` up to `length` elements. Like `slice[start:start+length]`, but does not panic on overflow. +// Play: https://go.dev/play/p/tOQu1GhFcog +func Subset[T any](collection []T, offset int, length uint) []T { + size := len(collection) + + if offset < 0 { + offset = size + offset + if offset < 0 { + offset = 0 + } + } + + if offset > size { + return []T{} + } + + if length > uint(size)-uint(offset) { + length = uint(size - offset) + } + + return collection[offset : offset+int(length)] +} + +// Slice returns a copy of a slice from `start` up to, but not including `end`. Like `slice[start:end]`, but does not panic on overflow. +// Play: https://go.dev/play/p/8XWYhfMMA1h +func Slice[T any](collection []T, start, end int) []T { + size := len(collection) + + if start >= end { + return []T{} + } + + if start > size { + start = size + } + if start < 0 { + start = 0 + } + + if end > size { + end = size + } + if end < 0 { + end = 0 + } + + return collection[start:end] +} + +// Replace returns a copy of the slice with the first n non-overlapping instances of old replaced by new. +// Play: https://go.dev/play/p/XfPzmf9gql6 +func Replace[T comparable](collection []T, oldVal, newVal T, n int) []T { + result := make([]T, len(collection)) + copy(result, collection) + + for i := range result { + if result[i] == oldVal && n != 0 { + result[i] = newVal + n-- + } + } + + return result +} + +// ReplaceAll returns a copy of the slice with all non-overlapping instances of old replaced by new. +// Play: https://go.dev/play/p/a9xZFUHfYcV +func ReplaceAll[T comparable](collection []T, oldVal, newVal T) []T { + return Replace(collection, oldVal, newVal, -1) +} + +// Compact returns a slice of all non-zero elements. +// Play: https://go.dev/play/p/tXiy-iK6PAc +func Compact[T comparable](collection []T) []T { + var zero T + + result := make([]T, 0, len(collection)) + + for _, item := range collection { + if item != zero { + result = append(result, item) + } + } + + return result +} + +// IsSorted checks if a slice is sorted. +// Play: https://go.dev/play/p/mc3qR-t4mcx +func IsSorted[T constraints.Ordered](collection []T) bool { + for i := 1; i < len(collection); i++ { + if collection[i-1] > collection[i] { + return false + } + } + + return true +} + +// IsSortedByKey checks if a slice is sorted by iteratee. +// Play: https://go.dev/play/p/wiG6XyBBu49 +func IsSortedByKey[T any, K constraints.Ordered](collection []T, iteratee func(item T) K) bool { + size := len(collection) + + for i := 0; i < size-1; i++ { + if iteratee(collection[i]) > iteratee(collection[i+1]) { + return false + } + } + + return true +} diff --git a/internal/lo/types.go b/internal/lo/types.go new file mode 100644 index 0000000..9de8862 --- /dev/null +++ b/internal/lo/types.go @@ -0,0 +1,123 @@ +package lo + +// Entry defines a key/value pairs. +type Entry[K comparable, V any] struct { + Key K + Value V +} + +// Tuple2 is a group of 2 elements (pair). +type Tuple2[A any, B any] struct { + A A + B B +} + +// Unpack returns values contained in tuple. +func (t Tuple2[A, B]) Unpack() (A, B) { //nolint:gocritic // foo + return t.A, t.B +} + +// Tuple3 is a group of 3 elements. +type Tuple3[A any, B any, C any] struct { + A A + B B + C C +} + +// Unpack returns values contained in tuple. +func (t Tuple3[A, B, C]) Unpack() (A, B, C) { //nolint:gocritic // foo + return t.A, t.B, t.C +} + +// Tuple4 is a group of 4 elements. +type Tuple4[A any, B any, C any, D any] struct { + A A + B B + C C + D D +} + +// Unpack returns values contained in tuple. +func (t Tuple4[A, B, C, D]) Unpack() (A, B, C, D) { //nolint:gocritic // foo + return t.A, t.B, t.C, t.D +} + +// Tuple5 is a group of 5 elements. +type Tuple5[A any, B any, C any, D any, E any] struct { + A A + B B + C C + D D + E E +} + +// Unpack returns values contained in tuple. +func (t Tuple5[A, B, C, D, E]) Unpack() (A, B, C, D, E) { //nolint:gocritic // foo + return t.A, t.B, t.C, t.D, t.E +} + +// Tuple6 is a group of 6 elements. +type Tuple6[A any, B any, C any, D any, E any, F any] struct { + A A + B B + C C + D D + E E + F F +} + +// Unpack returns values contained in tuple. +func (t Tuple6[A, B, C, D, E, F]) Unpack() (A, B, C, D, E, F) { //nolint:gocritic // foo + return t.A, t.B, t.C, t.D, t.E, t.F +} + +// Tuple7 is a group of 7 elements. +type Tuple7[A any, B any, C any, D any, E any, F any, G any] struct { + A A + B B + C C + D D + E E + F F + G G +} + +// Unpack returns values contained in tuple. +func (t Tuple7[A, B, C, D, E, F, G]) Unpack() (A, B, C, D, E, F, G) { //nolint:gocritic // foo + return t.A, t.B, t.C, t.D, t.E, t.F, t.G +} + +// Tuple8 is a group of 8 elements. +type Tuple8[A any, B any, C any, D any, E any, F any, G any, H any] struct { + A A + B B + C C + D D + E E + F F + G G + H H +} + +// Unpack returns values contained in tuple. +func (t Tuple8[A, B, C, D, E, F, G, H]) Unpack() (A, B, C, D, E, F, G, H) { //nolint:gocritic // foo + return t.A, t.B, t.C, t.D, t.E, t.F, t.G, t.H +} + +// Tuple9 is a group of 9 elements. +type Tuple9[A any, B any, C any, D any, E any, F any, G any, H any, I any] struct { + A A + B B + C C + D D + E E + F F + G G + H H + I I +} + +// Unpack returns values contained in tuple. +func (t Tuple9[A, B, C, D, E, F, G, H, I]) Unpack() (A, B, C, D, E, F, G, H, I) { //nolint:gocritic // foo + return t.A, t.B, t.C, t.D, t.E, t.F, t.G, t.H, t.I +} diff --git a/rx/factory.go b/rx/factory.go index 5f2b302..29251af 100644 --- a/rx/factory.go +++ b/rx/factory.go @@ -28,7 +28,7 @@ import ( "sync/atomic" "time" - "github.com/samber/lo" + "github.com/snivilised/lorax/internal/lo" ) // Amb takes several Observables, emit all of the items from only the first of these Observables diff --git a/rx/factory_test.go b/rx/factory_test.go index acbf8d6..f908cfb 100644 --- a/rx/factory_test.go +++ b/rx/factory_test.go @@ -31,7 +31,7 @@ import ( . "github.com/onsi/ginkgo/v2" //nolint:revive // ginkgo ok "github.com/onsi/ginkgo/v2/dsl/decorators" . "github.com/onsi/gomega" //nolint:revive // gomega ok - "github.com/samber/lo" + "github.com/snivilised/lorax/internal/lo" "github.com/snivilised/lorax/enums" "github.com/snivilised/lorax/rx" diff --git a/rx/util_test.go b/rx/util_test.go index bdc41f8..9358f98 100644 --- a/rx/util_test.go +++ b/rx/util_test.go @@ -27,7 +27,7 @@ import ( "errors" "fmt" - "github.com/samber/lo" + "github.com/snivilised/lorax/internal/lo" "github.com/snivilised/lorax/rx" )