mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-06-17 04:18:53 -04:00
structs: add tests and documentation
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
package structs
|
||||
|
||||
import (
|
||||
"iter"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
@@ -17,6 +18,29 @@ func CopyOrZeroValue[T any](s *T) *T {
|
||||
return cp
|
||||
}
|
||||
|
||||
// Create an iterator from a slice, iterating over every single element of the slice, in order.
|
||||
func Seq[T any](s []T) iter.Seq[T] {
|
||||
return func(yield func(T) bool) {
|
||||
for _, elem := range s {
|
||||
if !yield(elem) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create an iterator from a slice that yields the position and the value,
|
||||
// iterating over every single element of the slice, in order.
|
||||
func Seq2[T any](s []T) iter.Seq2[int, T] {
|
||||
return func(yield func(int, T) bool) {
|
||||
for i, elem := range s {
|
||||
if !yield(i, elem) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a copy of an array with a unique set of elements.
|
||||
//
|
||||
// Element order is retained.
|
||||
@@ -34,6 +58,7 @@ func Uniq[T comparable](source []T) []T {
|
||||
return set
|
||||
}
|
||||
|
||||
// Returns a slice containing the keys of the map.
|
||||
func Keys[K comparable, V any](source map[K]V) []K {
|
||||
if source == nil {
|
||||
var zero []K
|
||||
@@ -42,6 +67,8 @@ func Keys[K comparable, V any](source map[K]V) []K {
|
||||
return slices.Collect(maps.Keys(source))
|
||||
}
|
||||
|
||||
// Creates a map from a slice, using the indexer func to determine the key for each value,
|
||||
// and the value being as-is.
|
||||
func Index[K comparable, V any](source []V, indexer func(V) K) map[K]V {
|
||||
if source == nil {
|
||||
var zero map[K]V
|
||||
@@ -55,6 +82,8 @@ func Index[K comparable, V any](source []V, indexer func(V) K) map[K]V {
|
||||
return result
|
||||
}
|
||||
|
||||
// Creates a slice from a slice, putting each value from the source slice through the
|
||||
// mapper function to determine the value to store into the resulting slice.
|
||||
func Map[E any, R any](source []E, mapper func(E) R) []R {
|
||||
if source == nil {
|
||||
var zero []R
|
||||
@@ -67,67 +96,21 @@ func Map[E any, R any](source []E, mapper func(E) R) []R {
|
||||
return result
|
||||
}
|
||||
|
||||
func MapValues[K comparable, S any, T any](m map[K]S, mapper func(S) T) map[K]T {
|
||||
r := make(map[K]T, len(m))
|
||||
for k, s := range m {
|
||||
r[k] = mapper(s)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func MapValues2[K comparable, S any, T any](m map[K]S, mapper func(K, S) T) map[K]T {
|
||||
r := make(map[K]T, len(m))
|
||||
for k, s := range m {
|
||||
r[k] = mapper(k, s)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func MapKeys[S comparable, T comparable, V any](m map[S]V, mapper func(S) T) map[T]V {
|
||||
r := make(map[T]V, len(m))
|
||||
for s, v := range m {
|
||||
r[mapper(s)] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func MapKeys2[S comparable, T comparable, V any](m map[S]V, mapper func(S, V) T) map[T]V {
|
||||
r := make(map[T]V, len(m))
|
||||
for s, v := range m {
|
||||
r[mapper(s, v)] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func ToMap[E any, K comparable, V any](source []E, mapper func(E) (K, V)) map[K]V {
|
||||
m := map[K]V{}
|
||||
for _, e := range source {
|
||||
k, v := mapper(e)
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func ToBoolMap[E comparable](source []E) map[E]bool {
|
||||
m := make(map[E]bool, len(source))
|
||||
for _, v := range source {
|
||||
m[v] = true
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func ToIntMap[E comparable](source []E) map[E]int {
|
||||
m := make(map[E]int, len(source))
|
||||
for _, v := range source {
|
||||
if e, ok := m[v]; ok {
|
||||
m[v] = e + 1
|
||||
} else {
|
||||
m[v] = 1
|
||||
// Wraps an iterator with a transformer function.
|
||||
func MapSeq[A, B any](it iter.Seq[A], transformer func(A) B) iter.Seq[B] {
|
||||
return func(yield func(b B) bool) {
|
||||
for v := range it {
|
||||
t := transformer(v)
|
||||
if !yield(t) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Creates a slice from a slice, putting each value from the source slice through the
|
||||
// mapper function to determine the value to store into the resulting slice, but skipping
|
||||
// the result of the mapper function if it returns nil.
|
||||
func MapN[E any, R any](source []E, indexer func(E) *R) []R {
|
||||
if source == nil {
|
||||
var zero []R
|
||||
@@ -143,6 +126,99 @@ func MapN[E any, R any](source []E, indexer func(E) *R) []R {
|
||||
return result
|
||||
}
|
||||
|
||||
// Creates a slice from a slice, putting each value from the source slice through the
|
||||
// mapper function to determine the value to store into the resulting slice, but skipping
|
||||
// the result of the mapper function if it returns false as its second return value.
|
||||
func MapO[E any, R any](source []E, indexer func(E) (R, bool)) []R {
|
||||
if source == nil {
|
||||
var zero []R
|
||||
return zero
|
||||
}
|
||||
result := []R{}
|
||||
for _, e := range source {
|
||||
if value, keep := indexer(e); keep {
|
||||
result = append(result, value)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Creates a map from a map, keeping each key as-is, and using the mapper
|
||||
// function to determine the value to store into the resulting map.
|
||||
func MapValues[K comparable, S any, T any](m map[K]S, mapper func(S) T) map[K]T {
|
||||
r := make(map[K]T, len(m))
|
||||
for k, s := range m {
|
||||
r[k] = mapper(s)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Creates a map from a map, keeping each key as-is, and using the mapper function
|
||||
// that takes both the key and the value to determine the value to store into the resulting map.
|
||||
func MapValues2[K comparable, S any, T any](m map[K]S, mapper func(K, S) T) map[K]T {
|
||||
r := make(map[K]T, len(m))
|
||||
for k, s := range m {
|
||||
r[k] = mapper(k, s)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Creates a map from a map, keeping each value as-is, and using the mapper
|
||||
// function to determine the key to store into the resulting map.
|
||||
func MapKeys[S comparable, T comparable, V any](m map[S]V, mapper func(S) T) map[T]V {
|
||||
r := make(map[T]V, len(m))
|
||||
for s, v := range m {
|
||||
r[mapper(s)] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Creates a map from a map, keeping each value as-is, and using the mapper function
|
||||
// that takes both the key and the value to determine the key to store into the resulting map.
|
||||
func MapKeys2[S comparable, T comparable, V any](m map[S]V, mapper func(S, V) T) map[T]V {
|
||||
r := make(map[T]V, len(m))
|
||||
for s, v := range m {
|
||||
r[mapper(s, v)] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Creates a map from a slice, using the mapper function to determine the key and value
|
||||
// pair to use for each slice element in the resulting map.
|
||||
func ToMap[E any, K comparable, V any](source []E, mapper func(E) (K, V)) map[K]V {
|
||||
m := map[K]V{}
|
||||
for _, e := range source {
|
||||
k, v := mapper(e)
|
||||
m[k] = v
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Creates a map of booleans, using the values of the source slice as keys in the
|
||||
// resulting map.
|
||||
func ToBoolMap[E comparable](source []E) map[E]bool {
|
||||
m := make(map[E]bool, len(source))
|
||||
for _, v := range source {
|
||||
m[v] = true
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Creates a map of ints, using the values of the source slice as keys in the
|
||||
// resulting map, and storing the number of occurences of every given value
|
||||
// as the int value in the map.
|
||||
func ToIntMap[E comparable](source []E) map[E]int {
|
||||
m := make(map[E]int, len(source))
|
||||
for _, v := range source {
|
||||
if e, ok := m[v]; ok {
|
||||
m[v] = e + 1
|
||||
} else {
|
||||
m[v] = 1
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Check whether two slices contain the same elements, ignoring order.
|
||||
func SameSlices[E comparable](x, y []E) bool {
|
||||
// https://stackoverflow.com/a/36000696
|
||||
@@ -168,73 +244,9 @@ func SameSlices[E comparable](x, y []E) bool {
|
||||
return len(diff) == 0
|
||||
}
|
||||
|
||||
func Missing[E comparable](expected, actual []E) []E {
|
||||
missing := []E{}
|
||||
actualIndex := ToBoolMap(actual)
|
||||
for _, e := range expected {
|
||||
if _, ok := actualIndex[e]; !ok {
|
||||
missing = append(missing, e)
|
||||
}
|
||||
}
|
||||
return missing
|
||||
}
|
||||
|
||||
func FirstKey[K comparable, V any](m map[K]V) (K, bool) {
|
||||
for k := range m {
|
||||
return k, true
|
||||
}
|
||||
var zero K
|
||||
return zero, false
|
||||
}
|
||||
|
||||
func Any[E any](s []E, predicate func(E) bool) bool {
|
||||
if len(s) < 1 {
|
||||
return false
|
||||
}
|
||||
for _, e := range s {
|
||||
if predicate(e) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AnyKey[K comparable, V any](m map[K]V, predicate func(K) bool) bool {
|
||||
if len(m) < 1 {
|
||||
return false
|
||||
}
|
||||
for k := range m {
|
||||
if predicate(k) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AnyValue[K comparable, V any](m map[K]V, predicate func(V) bool) bool {
|
||||
if len(m) < 1 {
|
||||
return false
|
||||
}
|
||||
for _, v := range m {
|
||||
if predicate(v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AnyItem[K comparable, V any](m map[K]V, predicate func(K, V) bool) bool {
|
||||
if len(m) < 1 {
|
||||
return false
|
||||
}
|
||||
for k, v := range m {
|
||||
if predicate(k, v) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Concatenate the elements of multiple slices into a single slice.
|
||||
//
|
||||
// Element order is preserved.
|
||||
func Concat[E any](arys ...[]E) []E {
|
||||
l := 0
|
||||
for _, ary := range arys {
|
||||
@@ -251,7 +263,18 @@ func Concat[E any](arys ...[]E) []E {
|
||||
return r
|
||||
}
|
||||
|
||||
// Create a new slice from a slice, determining whether each element should
|
||||
// be added to the new slice by passing it to the predicate function.
|
||||
//
|
||||
// When the predicate function returns true, the element is stored in the
|
||||
// new slice.
|
||||
// When the predicate functoin returns false, the element is skipped and not
|
||||
// stored in the new slice.
|
||||
func Filter[E any](s []E, predicate func(E) bool) []E {
|
||||
if s == nil {
|
||||
var zero []E
|
||||
return zero
|
||||
}
|
||||
r := []E{}
|
||||
for _, e := range s {
|
||||
if predicate(e) {
|
||||
@@ -260,3 +283,17 @@ func Filter[E any](s []E, predicate func(E) bool) []E {
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// Wrap an iterator with a conditional/filtering predicate function.
|
||||
func FilterSeq[T any](it iter.Seq[T], predicate func(T) bool) iter.Seq[T] {
|
||||
return func(yield func(s T) bool) {
|
||||
for v := range it {
|
||||
b := predicate(v)
|
||||
if b {
|
||||
if !yield(v) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package structs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -52,7 +53,7 @@ func TestUniqWithInts(t *testing.T) {
|
||||
{[]int{1, 1, 1}, []int{1}},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%d: testing %v", i+1, tt.input), func(t *testing.T) {
|
||||
t.Run(fmt.Sprintf("%d: testing %v", i+1, tt.input), func(t *testing.T) { //NOSONAR
|
||||
result := Uniq(tt.input)
|
||||
assert.EqualValues(t, tt.expected, result)
|
||||
})
|
||||
@@ -101,68 +102,84 @@ func TestKeys(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissing(t *testing.T) {
|
||||
func TestIndex(t *testing.T) {
|
||||
tests := []struct {
|
||||
source []string
|
||||
input []string
|
||||
expected []string
|
||||
indexer func(string) string
|
||||
expected map[string]string
|
||||
}{
|
||||
{[]string{"a", "b", "c"}, []string{"c", "b", "a"}, []string{}},
|
||||
{[]string{"a", "b", "c"}, []string{"c", "b"}, []string{"a"}},
|
||||
{[]string{"a", "b", "c"}, []string{"c", "b", "a", "d"}, []string{}},
|
||||
{[]string{}, []string{"c", "b"}, []string{}},
|
||||
{[]string{"a", "b", "c"}, []string{}, []string{"a", "b", "c"}},
|
||||
{[]string{"a", "b", "b", "c"}, []string{"a", "b"}, []string{"c"}},
|
||||
{
|
||||
[]string{"un", "deux", "trois"},
|
||||
strings.ToUpper,
|
||||
map[string]string{"UN": "un", "DEUX": "deux", "TROIS": "trois"},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%d: testing [%v] <-> [%v] == [%v]", i+1, strings.Join(tt.source, ", "), strings.Join(tt.input, ", "), strings.Join(tt.expected, ", ")), func(t *testing.T) {
|
||||
result := Missing(tt.source, tt.input)
|
||||
t.Run(fmt.Sprintf("%d: testing %v", i+1, tt.input), func(t *testing.T) {
|
||||
result := Index(tt.input, tt.indexer)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAny(t *testing.T) {
|
||||
always := func(s string) bool { return true }
|
||||
never := func(s string) bool { return false }
|
||||
assert.True(t, Any([]string{"a", "b", "c"}, always))
|
||||
assert.False(t, Any([]string{}, always))
|
||||
assert.False(t, Any(nil, always))
|
||||
assert.False(t, Any([]string{"a", "b", "c"}, never))
|
||||
assert.False(t, Any(nil, never))
|
||||
func TestMap(t *testing.T) {
|
||||
tests := []struct {
|
||||
input []string
|
||||
mapper func(string) int
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
nil,
|
||||
func(s string) int { return len(s) },
|
||||
nil,
|
||||
},
|
||||
{
|
||||
[]string{},
|
||||
func(s string) int { return len(s) },
|
||||
[]int{},
|
||||
},
|
||||
{
|
||||
[]string{"un", "deux", "trois"},
|
||||
func(s string) int { return len(s) },
|
||||
[]int{2, 4, 5},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%d: testing %v", i+1, tt.input), func(t *testing.T) {
|
||||
result := Map(tt.input, tt.mapper)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnyKey(t *testing.T) {
|
||||
always := func(s string) bool { return true }
|
||||
never := func(s string) bool { return false }
|
||||
|
||||
assert.True(t, AnyKey(map[string]bool{"a": true, "b": false}, always))
|
||||
assert.False(t, AnyKey(map[string]bool{}, always))
|
||||
assert.False(t, AnyKey[string, bool](nil, always))
|
||||
assert.False(t, AnyKey(map[string]bool{"a": true, "b": false}, never))
|
||||
assert.False(t, AnyKey[string, bool](nil, never))
|
||||
}
|
||||
|
||||
func TestAnyValue(t *testing.T) {
|
||||
always := func(b bool) bool { return true }
|
||||
never := func(b bool) bool { return false }
|
||||
|
||||
assert.True(t, AnyValue(map[string]bool{"a": true, "b": false}, always))
|
||||
assert.False(t, AnyValue(map[string]bool{}, always))
|
||||
assert.False(t, AnyValue[string](nil, always))
|
||||
assert.False(t, AnyValue(map[string]bool{"a": true, "b": false}, never))
|
||||
assert.False(t, AnyValue[string](nil, never))
|
||||
}
|
||||
|
||||
func TestAnyItem(t *testing.T) {
|
||||
always := func(s string, b bool) bool { return true }
|
||||
never := func(s string, b bool) bool { return false }
|
||||
|
||||
assert.True(t, AnyItem(map[string]bool{"a": true, "b": false}, always))
|
||||
assert.False(t, AnyItem(map[string]bool{}, always))
|
||||
assert.False(t, AnyItem(nil, always))
|
||||
assert.False(t, AnyItem(map[string]bool{"a": true, "b": false}, never))
|
||||
assert.False(t, AnyItem(nil, never))
|
||||
func TestMapSeq(t *testing.T) {
|
||||
tests := []struct {
|
||||
input []string
|
||||
mapper func(string) int
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
nil,
|
||||
func(s string) int { return len(s) },
|
||||
nil,
|
||||
},
|
||||
{
|
||||
[]string{},
|
||||
func(s string) int { return len(s) },
|
||||
nil,
|
||||
},
|
||||
{
|
||||
[]string{"un", "deux", "trois"},
|
||||
func(s string) int { return len(s) },
|
||||
[]int{2, 4, 5},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%d: testing %v", i+1, tt.input), func(t *testing.T) {
|
||||
result := slices.Collect(MapSeq(Seq(tt.input), tt.mapper))
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConcat(t *testing.T) {
|
||||
@@ -171,3 +188,63 @@ func TestConcat(t *testing.T) {
|
||||
assert.Equal(t, []string{"a"}, Concat([]string{}, nil, []string{"a"}))
|
||||
assert.Equal(t, []string{}, Concat[string]())
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
tests := []struct {
|
||||
input []int
|
||||
predicate func(int) bool
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
nil,
|
||||
func(i int) bool { return i%2 == 0 },
|
||||
nil,
|
||||
},
|
||||
{
|
||||
[]int{},
|
||||
func(i int) bool { return i%2 == 0 },
|
||||
[]int{},
|
||||
},
|
||||
{
|
||||
[]int{1, 2, 3, 4, 5},
|
||||
func(i int) bool { return i%2 == 0 },
|
||||
[]int{2, 4},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%d: testing %v", i+1, tt.input), func(t *testing.T) {
|
||||
result := Filter(tt.input, tt.predicate)
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterSeq(t *testing.T) {
|
||||
tests := []struct {
|
||||
input []int
|
||||
predicate func(int) bool
|
||||
expected []int
|
||||
}{
|
||||
{
|
||||
nil,
|
||||
func(i int) bool { return i%2 == 0 },
|
||||
nil,
|
||||
},
|
||||
{
|
||||
[]int{},
|
||||
func(i int) bool { return i%2 == 0 },
|
||||
nil,
|
||||
},
|
||||
{
|
||||
[]int{1, 2, 3, 4, 5},
|
||||
func(i int) bool { return i%2 == 0 },
|
||||
[]int{2, 4},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%d: testing %v", i+1, tt.input), func(t *testing.T) {
|
||||
result := slices.Collect(FilterSeq(Seq(tt.input), tt.predicate))
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user