From f22552d84ea970edd5c7834a94cd70a94b98857b Mon Sep 17 00:00:00 2001 From: gram Date: Tue, 19 Sep 2023 14:41:07 +0200 Subject: [PATCH 1/5] sets and maps: ensure all funcs are sorted --- maps/maps.go | 24 +++++++-------- maps/maps_inplace.go | 64 +++++++++++++++++++-------------------- maps/maps_inplace_test.go | 64 +++++++++++++++++++-------------------- maps/maps_test.go | 26 ++++++++-------- scripts/test_docs.py | 50 +++++++++++++++++++----------- sets/examples_test.go | 32 ++++++++++---------- 6 files changed, 137 insertions(+), 123 deletions(-) diff --git a/maps/maps.go b/maps/maps.go index ea917b9..f490ce8 100644 --- a/maps/maps.go +++ b/maps/maps.go @@ -56,6 +56,18 @@ func HasValue[M ~map[K]V, K, V comparable](items M, val V) bool { return false } +// Keys returns the keys of the given map. +// +// The resulting keys order is unknown. You cannot rely on it in any way, +// even on it being random. +func Keys[M ~map[K]V, K comparable, V any](items M) []K { + result := make([]K, 0, len(items)) + for key := range items { + result = append(result, key) + } + return result +} + // Map calls the given function `f` with each key and value and makes a new map // out of key-value pairs returned by the function. func Map[M ~map[K]V, K, RK comparable, V, RV any](items M, f func(K, V) (RK, RV)) map[RK]RV { @@ -119,18 +131,6 @@ func MergeBy[M1, M2 ~map[K]V, K, V comparable](items1 M1, items2 M2, f func(K, V return result } -// Keys returns the keys of the given map. -// -// The resulting keys order is unknown. You cannot rely on it in any way, -// even on it being random. -func Keys[M ~map[K]V, K comparable, V any](items M) []K { - result := make([]K, 0, len(items)) - for key := range items { - result = append(result, key) - } - return result -} - // Take returns a copy of the given map containing only the given keys. // // Keys that aren't in the map are simply ignored. diff --git a/maps/maps_inplace.go b/maps/maps_inplace.go index 62fe349..b989dab 100644 --- a/maps/maps_inplace.go +++ b/maps/maps_inplace.go @@ -21,6 +21,38 @@ func Drop[M ~map[K]V, K comparable, V any](items M, keys ...K) { } } +// IMapValues applies the function to the map values. +// +// This is an in-place operation. It modifies `items` map in-place. +// If you want to create a new map, use [MapValues] instead. +func IMapValues[M ~map[K]V, K comparable, V any](items M, f func(V) V) { + for key, value := range items { + items[key] = f(value) + } +} + +// IMerge adds items from the second map to the first one. +// +// This is an in-place operation. It modifies `target` map in-place. +func IMerge[M1, M2 ~map[K]V, K, V comparable](target M1, items M2) { + for key, value := range items { + target[key] = value + } +} + +// IMergeBy is like [IMerge] but conflicts are resolved by the function `f`. +// +// This is an in-place operation. It modifies `target` map in-place. +func IMergeBy[M1, M2 ~map[K]V, K, V comparable](target M1, items M2, f func(K, V, V) V) { + for key, value2 := range items { + value1, contains := target[key] + if contains { + value2 = f(key, value1, value2) + } + target[key] = value2 + } +} + // LeaveOnly leaves in the given map only the given keys. // // It is in-place operation, it modifies the original map. @@ -70,35 +102,3 @@ func Update[M1, M2 ~map[K]V, K, V comparable](items M1, with M2) { items[key] = value } } - -// IMerge adds items from the second map to the first one. -// -// This is an in-place operation. It modifies `target` map in-place. -func IMerge[M1, M2 ~map[K]V, K, V comparable](target M1, items M2) { - for key, value := range items { - target[key] = value - } -} - -// IMergeBy is like [IMerge] but conflicts are resolved by the function `f`. -// -// This is an in-place operation. It modifies `target` map in-place. -func IMergeBy[M1, M2 ~map[K]V, K, V comparable](target M1, items M2, f func(K, V, V) V) { - for key, value2 := range items { - value1, contains := target[key] - if contains { - value2 = f(key, value1, value2) - } - target[key] = value2 - } -} - -// IMapValues applies the function to the map values. -// -// This is an in-place operation. It modifies `items` map in-place. -// If you want to create a new map, use [MapValues] instead. -func IMapValues[M ~map[K]V, K comparable, V any](items M, f func(V) V) { - for key, value := range items { - items[key] = f(value) - } -} diff --git a/maps/maps_inplace_test.go b/maps/maps_inplace_test.go index 9bc73c0..bab5286 100644 --- a/maps/maps_inplace_test.go +++ b/maps/maps_inplace_test.go @@ -26,6 +26,38 @@ func TestDrop(t *testing.T) { is.Equal(m, map[int]int{3: 4}) } +func TestIMapValues(t *testing.T) { + is := is.New(t) + m := map[int]int32{1: 2, 3: 4, 5: 6} + f := func(v int32) int32 { + return v * 2 + } + maps.IMapValues(m, f) + is.Equal(m, map[int]int32{1: 4, 3: 8, 5: 12}) +} + +func TestIMerge(t *testing.T) { + is := is.New(t) + m1 := map[int]string{1: "one", 2: "two"} + m2 := map[int]string{2: "new two", 3: "three"} + exp := map[int]string{1: "one", 2: "new two", 3: "three"} + maps.IMerge(m1, m2) + is.Equal(m1, exp) +} + +func TestIMergeBy(t *testing.T) { + is := is.New(t) + m1 := map[int]string{1: "one", 2: "two"} + m2 := map[int]string{2: "new two", 3: "three"} + f := func(k int, a, b string) string { + is.Equal(k, 2) + return fmt.Sprintf("%s|%s", a, b) + } + exp := map[int]string{1: "one", 2: "two|new two", 3: "three"} + maps.IMergeBy(m1, m2, f) + is.Equal(m1, exp) +} + func TestLeaveOnly(t *testing.T) { is := is.New(t) m := map[int]int{1: 2, 3: 4, 5: 6} @@ -61,35 +93,3 @@ func TestUpdate(t *testing.T) { maps.Update(m1, m2) is.Equal(m1, map[int]int{1: 2, 3: 5, 6: 7}) } - -func TestIMerge(t *testing.T) { - is := is.New(t) - m1 := map[int]string{1: "one", 2: "two"} - m2 := map[int]string{2: "new two", 3: "three"} - exp := map[int]string{1: "one", 2: "new two", 3: "three"} - maps.IMerge(m1, m2) - is.Equal(m1, exp) -} - -func TestIMergeBy(t *testing.T) { - is := is.New(t) - m1 := map[int]string{1: "one", 2: "two"} - m2 := map[int]string{2: "new two", 3: "three"} - f := func(k int, a, b string) string { - is.Equal(k, 2) - return fmt.Sprintf("%s|%s", a, b) - } - exp := map[int]string{1: "one", 2: "two|new two", 3: "three"} - maps.IMergeBy(m1, m2, f) - is.Equal(m1, exp) -} - -func TestIMapValues(t *testing.T) { - is := is.New(t) - m := map[int]int32{1: 2, 3: 4, 5: 6} - f := func(v int32) int32 { - return v * 2 - } - maps.IMapValues(m, f) - is.Equal(m, map[int]int32{1: 4, 3: 8, 5: 12}) -} diff --git a/maps/maps_test.go b/maps/maps_test.go index 68f5ec9..bf085c8 100644 --- a/maps/maps_test.go +++ b/maps/maps_test.go @@ -35,6 +35,13 @@ func TestEqual(t *testing.T) { is.True(!maps.Equal(map[int]int{1: 2}, map[int]int{2: 1})) } +func TestFromKeys(t *testing.T) { + is := is.New(t) + arr := []int{4, 5, 6} + exp := map[int]int{4: 7, 5: 7, 6: 7} + is.Equal(maps.FromKeys(arr, 7), exp) +} + func TestHasKey(t *testing.T) { is := is.New(t) m := map[int]int{1: 2, 3: 4} @@ -59,11 +66,11 @@ func TestHasValue(t *testing.T) { is.True(!maps.HasValue[map[int]int](nil, 3)) } -func TestFromKeys(t *testing.T) { +func TestKeys(t *testing.T) { is := is.New(t) - arr := []int{4, 5, 6} - exp := map[int]int{4: 7, 5: 7, 6: 7} - is.Equal(maps.FromKeys(arr, 7), exp) + m := map[int]int{1: 2, 3: 4, 5: 6} + act := maps.Keys(m) + is.Equal(maps.FromKeys(act, 0), map[int]int{1: 0, 3: 0, 5: 0}) } func TestMap(t *testing.T) { @@ -113,11 +120,10 @@ func TestMergeBy(t *testing.T) { is.Equal(maps.MergeBy(m1, m2, f), exp) } -func TestKeys(t *testing.T) { +func TestTake(t *testing.T) { is := is.New(t) m := map[int]int{1: 2, 3: 4, 5: 6} - act := maps.Keys(m) - is.Equal(maps.FromKeys(act, 0), map[int]int{1: 0, 3: 0, 5: 0}) + is.Equal(maps.Take(m, 1, 5), map[int]int{1: 2, 5: 6}) } func TestValues(t *testing.T) { @@ -127,12 +133,6 @@ func TestValues(t *testing.T) { is.Equal(maps.FromKeys(act, 0), map[int]int{2: 0, 4: 0, 6: 0}) } -func TestTake(t *testing.T) { - is := is.New(t) - m := map[int]int{1: 2, 3: 4, 5: 6} - is.Equal(maps.Take(m, 1, 5), map[int]int{1: 2, 5: 6}) -} - func TestWithout(t *testing.T) { is := is.New(t) m := map[int]int{1: 2, 3: 4, 5: 6} diff --git a/scripts/test_docs.py b/scripts/test_docs.py index 3baf1f2..6c2aaa9 100644 --- a/scripts/test_docs.py +++ b/scripts/test_docs.py @@ -4,25 +4,29 @@ import pytest -def get_funcs(pkg: str) -> Iterator[str]: +def get_funcs_for_pkg(pkg: str) -> Iterator[str]: for fpath in Path(pkg).iterdir(): if fpath.suffix != '.go': continue if fpath.stem.endswith('_test'): continue - content = fpath.read_text() - deprecated = False - for line in content.splitlines(): - if not line.startswith('func '): - deprecated = line.startswith('// DEPRECATED') - continue - line = line.removeprefix('func ') - fname = line.split('[')[0].split('(')[0] - if deprecated: - continue - if not fname[0].isupper(): - continue - yield fname + yield from get_funcs_for_file(fpath) + + +def get_funcs_for_file(fpath: Path) -> Iterator[str]: + content = fpath.read_text() + deprecated = False + for line in content.splitlines(): + if not line.startswith('func '): + deprecated = line.startswith('// DEPRECATED') + continue + line = line.removeprefix('func ') + fname = line.split('[')[0].split('(')[0] + if deprecated: + continue + if not fname[0].isupper(): + continue + yield fname def get_tests(pkg: str) -> Iterator[str]: @@ -58,7 +62,7 @@ def get_examples(pkg: str) -> Iterator[str]: def test_all_have_examples(pkg: str) -> None: """Every function must have an example. """ - funcs = set(get_funcs(pkg)) + funcs = set(get_funcs_for_pkg(pkg)) examples = set(get_examples(pkg)) assert funcs == examples @@ -74,14 +78,24 @@ def test_all_have_examples(pkg: str) -> None: def test_all_have_tests(pkg: str) -> None: """Every function must have unit tests. """ - funcs = set(get_funcs(pkg)) + funcs = set(get_funcs_for_pkg(pkg)) tests = set(get_tests(pkg)) assert funcs diff = funcs - tests assert not diff -@pytest.mark.parametrize('func', get_funcs('slices')) +@pytest.mark.parametrize('pkg', [ + 'maps', + 'sets', +]) +def test_all_funcs_sorted(pkg: str) -> None: + for fpath in Path(pkg).iterdir(): + funcs = list(get_funcs_for_file(fpath)) + assert funcs == sorted(funcs) + + +@pytest.mark.parametrize('func', get_funcs_for_pkg('slices')) def test_slices_func_linked_in_docs(func: str) -> None: """Every func in the slices package must be listed in the package docs. """ @@ -89,7 +103,7 @@ def test_slices_func_linked_in_docs(func: str) -> None: assert f'// - [{func}](' in docs -@pytest.mark.parametrize('func', get_funcs('channels')) +@pytest.mark.parametrize('func', get_funcs_for_pkg('channels')) def test_channels_func_linked_in_docs(func: str) -> None: """Every func in the channels package must be listed in the package docs. """ diff --git a/sets/examples_test.go b/sets/examples_test.go index e434a94..f2d6f55 100644 --- a/sets/examples_test.go +++ b/sets/examples_test.go @@ -40,14 +40,6 @@ func ExampleCopy() { // map[3:{} 4:{} 6:{}] } -func ExampleDiscard() { - s := sets.New(3, 4) - sets.Discard(s, 4) - sets.Discard(s, 5) - fmt.Println(s) - // Output: map[3:{}] -} - func ExampleDifference() { a := sets.New(3, 4, 5) b := sets.New(5, 6, 7) @@ -56,6 +48,14 @@ func ExampleDifference() { // Output: map[3:{} 4:{}] } +func ExampleDiscard() { + s := sets.New(3, 4) + sets.Discard(s, 4) + sets.Discard(s, 5) + fmt.Println(s) + // Output: map[3:{}] +} + func ExampleDisjoint() { a := sets.New(3, 4, 5) b := sets.New(5, 6, 7) @@ -186,6 +186,14 @@ func ExampleSum() { // Output: 12 } +func ExampleSuperset() { + a := sets.New(3, 4, 5, 6) + b := sets.New(4, 5) + result := sets.Superset(a, b) + fmt.Println(result) + // Output: true +} + func ExampleSymmetricDifference() { a := sets.New(3, 4, 5) b := sets.New(5, 6, 7) @@ -201,14 +209,6 @@ func ExampleToSlice() { // Output: [3] } -func ExampleSuperset() { - a := sets.New(3, 4, 5, 6) - b := sets.New(4, 5) - result := sets.Superset(a, b) - fmt.Println(result) - // Output: true -} - func ExampleUnion() { a := sets.New(3, 4, 5) b := sets.New(5, 6, 7) From 9fca89e4c625fef4522f23affc37cf27b94476d2 Mon Sep 17 00:00:00 2001 From: gram Date: Tue, 19 Sep 2023 14:50:16 +0200 Subject: [PATCH 2/5] lambdas: sort all functions --- lambdas/checks.go | 80 ++++++++++++------------ lambdas/checks_test.go | 133 ++++++++++++++++++++-------------------- lambdas/errors.go | 32 +++++----- lambdas/errors_test.go | 30 ++++----- lambdas/example_test.go | 28 ++++----- lambdas/glambda.go | 32 +++++----- lambdas/glambda_test.go | 26 ++++---- scripts/test_docs.py | 1 + 8 files changed, 181 insertions(+), 181 deletions(-) diff --git a/lambdas/checks.go b/lambdas/checks.go index e307209..4634cc0 100644 --- a/lambdas/checks.go +++ b/lambdas/checks.go @@ -9,40 +9,6 @@ func EqualTo[T comparable](a T) func(T) bool { } } -// LessThan returns lambda that checks if an item is less than the given value. -func LessThan[T constraints.Ordered](a T) func(T) bool { - return func(b T) bool { - return b < a - } -} - -// Not negates the result of a lambda -func Not[T constraints.Ordered](f func(T) bool) func(T) bool { - return func(item T) bool { - return !f(item) - } -} - -// IsZero checks if the given number is zero -func IsZero[T Number](n T) bool { - return n == 0 -} - -// IsNotZero checks if the given number is not zero -func IsNotZero[T Number](n T) bool { - return n != 0 -} - -// IsEmpty checks if the given slice is empty -func IsEmpty[T any](items []T) bool { - return len(items) == 0 -} - -// Empty checks if the given slice is not empty -func IsNotEmpty[T any](items []T) bool { - return len(items) != 0 -} - // IsDefault checks if the given value is the default for this type. // // A few examples: @@ -55,6 +21,19 @@ func IsDefault[T comparable](value T) bool { return value == def } +// IsEmpty checks if the given slice is empty +func IsEmpty[T any](items []T) bool { + return len(items) == 0 +} + +func IsNaN[T comparable](value T) bool { + return value != value +} + +func IsNil[T any](value *T) bool { + return value == nil +} + // IsNotDefault checks if the given value is not the default for this type. // // A few examples: @@ -67,18 +46,39 @@ func IsNotDefault[T comparable](value T) bool { return value != def } -func IsNaN[T comparable](value T) bool { - return value != value +// Empty checks if the given slice is not empty +func IsNotEmpty[T any](items []T) bool { + return len(items) != 0 } func IsNotNaN[T comparable](value T) bool { return value == value } -func IsNil[T any](value *T) bool { - return value == nil -} - func IsNotNil[T any](value *T) bool { return value != nil } + +// IsNotZero checks if the given number is not zero +func IsNotZero[T Number](n T) bool { + return n != 0 +} + +// IsZero checks if the given number is zero +func IsZero[T Number](n T) bool { + return n == 0 +} + +// LessThan returns lambda that checks if an item is less than the given value. +func LessThan[T constraints.Ordered](a T) func(T) bool { + return func(b T) bool { + return b < a + } +} + +// Not negates the result of a lambda +func Not[T constraints.Ordered](f func(T) bool) func(T) bool { + return func(item T) bool { + return !f(item) + } +} diff --git a/lambdas/checks_test.go b/lambdas/checks_test.go index 383a755..030832c 100644 --- a/lambdas/checks_test.go +++ b/lambdas/checks_test.go @@ -19,49 +19,13 @@ func TestEqualTo(t *testing.T) { is.True(lambdas.EqualTo("ab")("ab")) } -func TestLessThan(t *testing.T) { - is := is.New(t) - is.True(!lambdas.LessThan(2)(3)) - is.True(!lambdas.LessThan(2)(2)) - is.True(lambdas.LessThan(3)(2)) -} - -func TestNot(t *testing.T) { - is := is.New(t) - l := lambdas.Not(lambdas.EqualTo(2)) - is.True(!l(2)) - is.True(l(3)) - is.True(l(-1)) -} - -func TestIsZero(t *testing.T) { - is := is.New(t) - is.True(lambdas.IsZero(int(0))) - is.True(lambdas.IsZero(int32(0))) - is.True(lambdas.IsZero(int64(0))) - is.True(lambdas.IsZero(uint64(0))) - is.True(lambdas.IsZero(float64(0))) - is.True(lambdas.IsZero(float32(0))) - - is.True(!lambdas.IsZero(int(1))) - is.True(!lambdas.IsZero(int64(1))) - is.True(!lambdas.IsZero(float64(13))) - is.True(!lambdas.IsZero(-1)) -} - -func TestIsNotZero(t *testing.T) { +func TestIsDefault(t *testing.T) { is := is.New(t) - is.True(!lambdas.IsNotZero(int(0))) - is.True(!lambdas.IsNotZero(int32(0))) - is.True(!lambdas.IsNotZero(int64(0))) - is.True(!lambdas.IsNotZero(uint64(0))) - is.True(!lambdas.IsNotZero(float64(0))) - is.True(!lambdas.IsNotZero(float32(0))) + is.True(!lambdas.IsDefault(1)) + is.True(!lambdas.IsDefault("hi")) - is.True(lambdas.IsNotZero(int(1))) - is.True(lambdas.IsNotZero(int64(1))) - is.True(lambdas.IsNotZero(float64(13))) - is.True(lambdas.IsNotZero(-1)) + is.True(lambdas.IsDefault("")) + is.True(lambdas.IsDefault(0)) } func TestIsEmpty(t *testing.T) { @@ -71,20 +35,23 @@ func TestIsEmpty(t *testing.T) { is.True(lambdas.IsEmpty[[]any](nil)) } -func TestIsNotEmpty(t *testing.T) { +func TestIsNaN(t *testing.T) { is := is.New(t) - is.True(lambdas.IsNotEmpty([]any{1})) - is.True(!lambdas.IsNotEmpty([]any{})) - is.True(!lambdas.IsNotEmpty[[]any](nil)) + is.True(!lambdas.IsNaN(1.3)) + is.True(!lambdas.IsNaN(1.3)) + is.True(!lambdas.IsNaN(1)) + is.True(!lambdas.IsNaN(int64(1))) + is.True(lambdas.IsNaN(float64(math.NaN()))) } -func TestIsDefault(t *testing.T) { +func TestIsNil(t *testing.T) { is := is.New(t) - is.True(!lambdas.IsDefault(1)) - is.True(!lambdas.IsDefault("hi")) - - is.True(lambdas.IsDefault("")) - is.True(lambdas.IsDefault(0)) + var v1 *int + is.True(lambdas.IsNil(v1)) + var v2 int = 2 + is.True(!lambdas.IsNil(&v2)) + var v3 []int + is.True(!lambdas.IsNil(&v3)) } func TestIsNotDefault(t *testing.T) { @@ -96,23 +63,11 @@ func TestIsNotDefault(t *testing.T) { is.True(!lambdas.IsNotDefault(0)) } -func TestIsNil(t *testing.T) { - is := is.New(t) - var v1 *int - is.True(lambdas.IsNil(v1)) - var v2 int = 2 - is.True(!lambdas.IsNil(&v2)) - var v3 []int - is.True(!lambdas.IsNil(&v3)) -} - -func TestIsNaN(t *testing.T) { +func TestIsNotEmpty(t *testing.T) { is := is.New(t) - is.True(!lambdas.IsNaN(1.3)) - is.True(!lambdas.IsNaN(1.3)) - is.True(!lambdas.IsNaN(1)) - is.True(!lambdas.IsNaN(int64(1))) - is.True(lambdas.IsNaN(float64(math.NaN()))) + is.True(lambdas.IsNotEmpty([]any{1})) + is.True(!lambdas.IsNotEmpty([]any{})) + is.True(!lambdas.IsNotEmpty[[]any](nil)) } func TestIsNotNaN(t *testing.T) { @@ -133,3 +88,47 @@ func TestIsNotNil(t *testing.T) { var v3 []int is.True(lambdas.IsNotNil(&v3)) } + +func TestIsNotZero(t *testing.T) { + is := is.New(t) + is.True(!lambdas.IsNotZero(int(0))) + is.True(!lambdas.IsNotZero(int32(0))) + is.True(!lambdas.IsNotZero(int64(0))) + is.True(!lambdas.IsNotZero(uint64(0))) + is.True(!lambdas.IsNotZero(float64(0))) + is.True(!lambdas.IsNotZero(float32(0))) + + is.True(lambdas.IsNotZero(int(1))) + is.True(lambdas.IsNotZero(int64(1))) + is.True(lambdas.IsNotZero(float64(13))) + is.True(lambdas.IsNotZero(-1)) +} +func TestIsZero(t *testing.T) { + is := is.New(t) + is.True(lambdas.IsZero(int(0))) + is.True(lambdas.IsZero(int32(0))) + is.True(lambdas.IsZero(int64(0))) + is.True(lambdas.IsZero(uint64(0))) + is.True(lambdas.IsZero(float64(0))) + is.True(lambdas.IsZero(float32(0))) + + is.True(!lambdas.IsZero(int(1))) + is.True(!lambdas.IsZero(int64(1))) + is.True(!lambdas.IsZero(float64(13))) + is.True(!lambdas.IsZero(-1)) +} + +func TestLessThan(t *testing.T) { + is := is.New(t) + is.True(!lambdas.LessThan(2)(3)) + is.True(!lambdas.LessThan(2)(2)) + is.True(lambdas.LessThan(3)(2)) +} + +func TestNot(t *testing.T) { + is := is.New(t) + l := lambdas.Not(lambdas.EqualTo(2)) + is.True(!l(2)) + is.True(l(3)) + is.True(l(-1)) +} diff --git a/lambdas/errors.go b/lambdas/errors.go index 049eb8f..475daf7 100644 --- a/lambdas/errors.go +++ b/lambdas/errors.go @@ -1,11 +1,14 @@ package lambdas -// Must wraps a function invicotaion and panic if it returned an error. -func Must[T any](val T, err error) T { - if err != nil { - panic(err) +// DefaultTo wraps a function invicotaion and returns the specified default value +// if it returned an error. +func DefaultTo[T any](def T) func(val T, err error) T { + return func(val T, err error) T { + if err != nil { + return def + } + return val } - return val } // Ensure wraps a function invicotaion and panic if it returned an error. @@ -16,6 +19,14 @@ func Ensure(err error) { } } +// Must wraps a function invicotaion and panic if it returned an error. +func Must[T any](val T, err error) T { + if err != nil { + panic(err) + } + return val +} + // Safe wraps a function invicotaion and returns the empty value if it returned an error. func Safe[T any](val T, err error) T { if err != nil { @@ -24,14 +35,3 @@ func Safe[T any](val T, err error) T { } return val } - -// DefaultTo wraps a function invicotaion and returns the specified default value -// if it returned an error. -func DefaultTo[T any](def T) func(val T, err error) T { - return func(val T, err error) T { - if err != nil { - return def - } - return val - } -} diff --git a/lambdas/errors_test.go b/lambdas/errors_test.go index 67a494e..20f3a8d 100644 --- a/lambdas/errors_test.go +++ b/lambdas/errors_test.go @@ -16,14 +16,12 @@ func panics(is *is.I, f func()) { f() } -func TestMust(t *testing.T) { +func TestDefaultTo(t *testing.T) { is := is.New(t) - f := func() (int, error) { return 13, nil } - res := lambdas.Must(f()) - is.Equal(res, 13) - - f = func() (int, error) { return 13, errors.New("oh no") } - panics(is, func() { lambdas.Must(f()) }) + f1 := func() (int, error) { return 13, nil } + is.Equal(lambdas.DefaultTo(4)(f1()), 13) + f2 := func() (int, error) { return 13, errors.New("hi") } + is.Equal(lambdas.DefaultTo(4)(f2()), 4) } func TestEnsure(t *testing.T) { @@ -35,18 +33,20 @@ func TestEnsure(t *testing.T) { panics(is, func() { lambdas.Ensure(f()) }) } -func TestSafe(t *testing.T) { +func TestMust(t *testing.T) { is := is.New(t) - f1 := func() (int, error) { return 13, nil } - is.Equal(lambdas.Safe(f1()), 13) - f2 := func() (int, error) { return 13, errors.New("hi") } - is.Equal(lambdas.Safe(f2()), 0) + f := func() (int, error) { return 13, nil } + res := lambdas.Must(f()) + is.Equal(res, 13) + + f = func() (int, error) { return 13, errors.New("oh no") } + panics(is, func() { lambdas.Must(f()) }) } -func TestDefaultTo(t *testing.T) { +func TestSafe(t *testing.T) { is := is.New(t) f1 := func() (int, error) { return 13, nil } - is.Equal(lambdas.DefaultTo(4)(f1()), 13) + is.Equal(lambdas.Safe(f1()), 13) f2 := func() (int, error) { return 13, errors.New("hi") } - is.Equal(lambdas.DefaultTo(4)(f2()), 4) + is.Equal(lambdas.Safe(f2()), 0) } diff --git a/lambdas/example_test.go b/lambdas/example_test.go index c351320..21f9010 100644 --- a/lambdas/example_test.go +++ b/lambdas/example_test.go @@ -7,11 +7,11 @@ import ( "github.com/life4/genesis/slices" ) -func ExampleMust() { - res := lambdas.Must(slices.Min([]int{42, 7, 13})) +func ExampleAbs() { + res := lambdas.Abs(-13) fmt.Println(res) //Output: - // 7 + // 13 } func ExampleDefaultTo() { @@ -21,30 +21,30 @@ func ExampleDefaultTo() { // 13 } -func ExampleSafe() { - res := lambdas.Safe(slices.Min([]int{})) +func ExampleMax() { + res := lambdas.Max(10, 13) fmt.Println(res) //Output: - // 0 + // 13 } -func ExampleAbs() { - res := lambdas.Abs(-13) +func ExampleMin() { + res := lambdas.Min(15, 13) fmt.Println(res) //Output: // 13 } -func ExampleMin() { - res := lambdas.Min(15, 13) +func ExampleMust() { + res := lambdas.Must(slices.Min([]int{42, 7, 13})) fmt.Println(res) //Output: - // 13 + // 7 } -func ExampleMax() { - res := lambdas.Max(10, 13) +func ExampleSafe() { + res := lambdas.Safe(slices.Min([]int{})) fmt.Println(res) //Output: - // 13 + // 0 } diff --git a/lambdas/glambda.go b/lambdas/glambda.go index bac3243..c2c7994 100644 --- a/lambdas/glambda.go +++ b/lambdas/glambda.go @@ -14,22 +14,6 @@ func Abs[T Number](a T) T { return a } -// Min returns minimal value -func Min[T constraints.Ordered](a T, b T) T { - if a <= b { - return a - } - return b -} - -// Max returns maximal value -func Max[T constraints.Ordered](a T, b T) T { - if a > b { - return a - } - return b -} - // Default returns the default value of the same type as the given value. // // A few examples: @@ -41,3 +25,19 @@ func Default[T any](T) T { var def T return def } + +// Max returns maximal value +func Max[T constraints.Ordered](a T, b T) T { + if a > b { + return a + } + return b +} + +// Min returns minimal value +func Min[T constraints.Ordered](a T, b T) T { + if a <= b { + return a + } + return b +} diff --git a/lambdas/glambda_test.go b/lambdas/glambda_test.go index 7abbc7a..4965ecd 100644 --- a/lambdas/glambda_test.go +++ b/lambdas/glambda_test.go @@ -15,13 +15,14 @@ func TestAbs(t *testing.T) { is.Equal(lambdas.Abs(-1.2), 1.2) } -func TestMin(t *testing.T) { +func TestDefault(t *testing.T) { is := is.New(t) - is.Equal(lambdas.Min(2, 3), 2) - is.Equal(lambdas.Min(3, 2), 2) - is.Equal(lambdas.Min(-2, 3), -2) - is.Equal(lambdas.Min(2, -3), -3) - is.Equal(lambdas.Min(2, 2), 2) + is.Equal(lambdas.Default(3), 0) + is.Equal(lambdas.Default(int32(3)), int32(0)) + is.Equal(lambdas.Default(int64(3)), int64(0)) + is.Equal(lambdas.Default(0), 0) + is.Equal(lambdas.Default(3.5), 0.0) + is.Equal(lambdas.Default("hi"), "") } func TestMax(t *testing.T) { @@ -33,12 +34,11 @@ func TestMax(t *testing.T) { is.Equal(lambdas.Max(2, 2), 2) } -func TestDefault(t *testing.T) { +func TestMin(t *testing.T) { is := is.New(t) - is.Equal(lambdas.Default(3), 0) - is.Equal(lambdas.Default(int32(3)), int32(0)) - is.Equal(lambdas.Default(int64(3)), int64(0)) - is.Equal(lambdas.Default(0), 0) - is.Equal(lambdas.Default(3.5), 0.0) - is.Equal(lambdas.Default("hi"), "") + is.Equal(lambdas.Min(2, 3), 2) + is.Equal(lambdas.Min(3, 2), 2) + is.Equal(lambdas.Min(-2, 3), -2) + is.Equal(lambdas.Min(2, -3), -3) + is.Equal(lambdas.Min(2, 2), 2) } diff --git a/scripts/test_docs.py b/scripts/test_docs.py index 6c2aaa9..ad803b9 100644 --- a/scripts/test_docs.py +++ b/scripts/test_docs.py @@ -88,6 +88,7 @@ def test_all_have_tests(pkg: str) -> None: @pytest.mark.parametrize('pkg', [ 'maps', 'sets', + 'lambdas', ]) def test_all_funcs_sorted(pkg: str) -> None: for fpath in Path(pkg).iterdir(): From f54aed5dbaf9ab0dd2ec8dd4381bc022ff931c27 Mon Sep 17 00:00:00 2001 From: gram Date: Tue, 19 Sep 2023 15:02:06 +0200 Subject: [PATCH 3/5] slices: sort all functions --- scripts/test_docs.py | 1 + slices/async_slice_test.go | 26 ++--- slices/examples_test.go | 122 ++++++++++----------- slices/slice.go | 98 ++++++++--------- slices/slice_func.go | 60 +++++------ slices/slice_func_test.go | 24 ++--- slices/slice_test.go | 213 ++++++++++++++++++------------------- slices/slices_test.go | 34 +++--- 8 files changed, 289 insertions(+), 289 deletions(-) diff --git a/scripts/test_docs.py b/scripts/test_docs.py index ad803b9..67d5f07 100644 --- a/scripts/test_docs.py +++ b/scripts/test_docs.py @@ -89,6 +89,7 @@ def test_all_have_tests(pkg: str) -> None: 'maps', 'sets', 'lambdas', + 'slices', ]) def test_all_funcs_sorted(pkg: str) -> None: for fpath in Path(pkg).iterdir(): diff --git a/slices/async_slice_test.go b/slices/async_slice_test.go index 2cdf21d..82aeb3e 100644 --- a/slices/async_slice_test.go +++ b/slices/async_slice_test.go @@ -8,37 +8,37 @@ import ( "github.com/matryer/is" ) -func TestAnyAsync(t *testing.T) { +func TestAllAsync(t *testing.T) { is := is.New(t) f := func(check func(t int) bool, given []int, expected bool) { - is.Equal(slices.AnyAsync(given, 2, check), expected) + is.Equal(slices.AllAsync(given, 2, check), expected) } isEven := func(t int) bool { return (t % 2) == 0 } - f(isEven, []int{}, false) + f(isEven, []int{}, true) f(isEven, []int{1}, false) f(isEven, []int{1, 3}, false) f(isEven, []int{2}, true) - f(isEven, []int{1, 2}, true) - f(isEven, []int{1, 3, 5, 7, 9, 11}, false) - f(isEven, []int{1, 3, 5, 7, 9, 12}, true) + f(isEven, []int{2, 4}, true) + f(isEven, []int{2, 3}, false) + f(isEven, []int{2, 4, 6, 8, 10, 12}, true) + f(isEven, []int{2, 4, 6, 8, 10, 11}, false) } -func TestAllAsync(t *testing.T) { +func TestAnyAsync(t *testing.T) { is := is.New(t) f := func(check func(t int) bool, given []int, expected bool) { - is.Equal(slices.AllAsync(given, 2, check), expected) + is.Equal(slices.AnyAsync(given, 2, check), expected) } isEven := func(t int) bool { return (t % 2) == 0 } - f(isEven, []int{}, true) + f(isEven, []int{}, false) f(isEven, []int{1}, false) f(isEven, []int{1, 3}, false) f(isEven, []int{2}, true) - f(isEven, []int{2, 4}, true) - f(isEven, []int{2, 3}, false) - f(isEven, []int{2, 4, 6, 8, 10, 12}, true) - f(isEven, []int{2, 4, 6, 8, 10, 11}, false) + f(isEven, []int{1, 2}, true) + f(isEven, []int{1, 3, 5, 7, 9, 11}, false) + f(isEven, []int{1, 3, 5, 7, 9, 12}, true) } func TestEachAsync(t *testing.T) { diff --git a/slices/examples_test.go b/slices/examples_test.go index 39fcafc..3ff2c8f 100644 --- a/slices/examples_test.go +++ b/slices/examples_test.go @@ -51,28 +51,6 @@ func ExampleChoice() { // Output: 3 } -func ExampleCopy() { - orig := []int{3, 4} - copy := slices.Copy(orig) - orig = append(orig, 5) - copy = append(copy, 6) - fmt.Println(orig) - fmt.Println(copy) - // Output: - // [3 4 5] - // [3 4 6] -} - -func ExampleFindIndex() { - type UserId int - index := slices.FindIndex( - []UserId{1, 2, 3, 4, 5}, - func(el UserId) bool { return el == 3 }, - ) - fmt.Println(index) - // Output: 2 -} - func ExampleChunkBy() { s := []int{1, 3, 4, 6, 8, 9} remainder := func(item int) int { return item % 2 } @@ -108,6 +86,18 @@ func ExampleContains() { // false } +func ExampleCopy() { + orig := []int{3, 4} + copy := slices.Copy(orig) + orig = append(orig, 5) + copy = append(copy, 6) + fmt.Println(orig) + fmt.Println(copy) + // Output: + // [3 4 5] + // [3 4 6] +} + func ExampleCount() { s := []int{1, 0, 1, 0, 0, 1, 1, 0, 1, 0} result := slices.Count(s, 1) @@ -159,18 +149,18 @@ func ExampleDelete() { // Output: [3 5 3 4 5] } -func ExampleDeleteAt() { +func ExampleDeleteAll() { s := []int{3, 4, 5, 3, 4, 5} - result, _ := slices.DeleteAt(s, 1, 3) + result := slices.DeleteAll(s, 3) fmt.Println(result) - // Output: [3 5 4 5] + // Output: [4 5 4 5] } -func ExampleDeleteAll() { +func ExampleDeleteAt() { s := []int{3, 4, 5, 3, 4, 5} - result := slices.DeleteAll(s, 3) + result, _ := slices.DeleteAt(s, 1, 3) fmt.Println(result) - // Output: [4 5 4 5] + // Output: [3 5 4 5] } func ExampleDifference() { @@ -296,6 +286,16 @@ func ExampleFind() { // Output: 4 } +func ExampleFindIndex() { + type UserId int + index := slices.FindIndex( + []UserId{1, 2, 3, 4, 5}, + func(el UserId) bool { return el == 3 }, + ) + fmt.Println(index) + // Output: 2 +} + func ExampleGroupBy() { s := []int{3, 4, 5, 13, 14, 15, 23, 33} even := func(x int) int { return x % 10 } @@ -344,14 +344,6 @@ func ExampleIntersect() { // Output: [4 5 6] } -func ExampleUnion() { - s1 := []int{3, 4, 5, 5, 6, 6, 7} - s2 := []int{6, 5, 5, 4, 8} - result := slices.Union(s1, s2) - fmt.Println(result) - // Output: [3 4 5 6 7 8] -} - func ExampleIntersperse() { s := []int{3, 4, 5} result := slices.Intersperse(s, 1) @@ -373,20 +365,6 @@ func ExampleLast() { // Output: 5 } -func ExampleMin() { - s := []int{42, 7, 13} - min, _ := slices.Min(s) - fmt.Println(min) - // Output: 7 -} - -func ExampleMax() { - s := []int{7, 42, 13} - max, _ := slices.Max(s) - fmt.Println(max) - // Output: 42 -} - func ExampleMap() { s := []int{4, 8, 15, 16, 23, 42} double := func(el int) int { return el * 2 } @@ -395,6 +373,19 @@ func ExampleMap() { // Output: [8 16 30 32 46 84] } +func ExampleMapAsync() { + pages := slices.MapAsync( + []string{"google.com", "go.dev", "golang.org"}, + 0, + func(url string) string { + return fmt.Sprintf("", url) + }, + ) + fmt.Println(pages) + // Output: + // [ ] +} + func ExampleMapFilter() { s := []int{4, 8, 15, 16, 23, 42} isEven := func(t int) (string, bool) { @@ -411,17 +402,18 @@ func ExampleMapFilter() { // Output: [4 8 16 42] } -func ExampleMapAsync() { - pages := slices.MapAsync( - []string{"google.com", "go.dev", "golang.org"}, - 0, - func(url string) string { - return fmt.Sprintf("", url) - }, - ) - fmt.Println(pages) - // Output: - // [ ] +func ExampleMax() { + s := []int{7, 42, 13} + max, _ := slices.Max(s) + fmt.Println(max) + // Output: 42 +} + +func ExampleMin() { + s := []int{42, 7, 13} + min, _ := slices.Min(s) + fmt.Println(min) + // Output: 7 } func ExamplePartition() { @@ -683,6 +675,14 @@ func ExampleToMap() { // Output: map[0:3 1:4 2:5] } +func ExampleUnion() { + s1 := []int{3, 4, 5, 5, 6, 6, 7} + s2 := []int{6, 5, 5, 4, 8} + result := slices.Union(s1, s2) + fmt.Println(result) + // Output: [3 4 5 6 7 8] +} + func ExampleUniq() { s := []int{3, 3, 4, 5, 4, 3, 3} result := slices.Uniq(s) diff --git a/slices/slice.go b/slices/slice.go index 197e326..e5c0e58 100644 --- a/slices/slice.go +++ b/slices/slice.go @@ -58,6 +58,15 @@ func Contains[S ~[]T, T comparable](items S, el T) bool { return false } +// Copy creates a copy of the given slice. +func Copy[S ~[]T, T any](items S) S { + if items == nil { + return nil + } + var res S + return append(res, items...) +} + // Count return count of el occurrences in arr. func Count[S ~[]T, T comparable](items S, el T) int { count := 0 @@ -69,15 +78,6 @@ func Count[S ~[]T, T comparable](items S, el T) int { return count } -// Copy creates a copy of the given slice. -func Copy[S ~[]T, T any](items S) S { - if items == nil { - return nil - } - var res S - return append(res, items...) -} - // Cycle is an infinite loop over slice func Cycle[S ~[]T, T any](items S) chan T { c := make(chan T) @@ -252,22 +252,6 @@ func Grow[S ~[]T, T any](items S, n int) S { return append(items, make(S, n)...)[:len(items)] } -// Shrink removes unused capacity from the slice. -// -// In other words, the returned slice has capacity equal to length. -func Shrink[S ~[]T, T any](items S) S { - return items[:len(items):len(items)] -} - -// Join concatenates elements of the slice to create a single string. -func Join[S ~[]T, T any](items S, sep string) string { - strs := make([]string, 0, len(items)) - for _, el := range items { - strs = append(strs, fmt.Sprintf("%v", el)) - } - return strings.Join(strs, sep) -} - // Index returns the index of the first occurrence of item in items. func Index[S ~[]T, T comparable](items S, item T) (int, error) { for i, val := range items { @@ -318,6 +302,15 @@ func Intersperse[S ~[]T, T any](items S, el T) S { return result } +// Join concatenates elements of the slice to create a single string. +func Join[S ~[]T, T any](items S, sep string) string { + strs := make([]string, 0, len(items)) + for _, el := range items { + strs = append(strs, fmt.Sprintf("%v", el)) + } + return strings.Join(strs, sep) +} + // Last returns the last element from the slice func Last[S ~[]T, T any](items S) (T, error) { if len(items) == 0 { @@ -432,18 +425,6 @@ func product[S ~[]T, T any](items S, c chan []T, repeat int, left []T, pos int) } } -// Reverse returns given arr in reversed order -func Reverse[S ~[]T, T any](items S) S { - if len(items) <= 1 { - return items - } - result := make([]T, 0, len(items)) - for i := len(items) - 1; i >= 0; i-- { - result = append(result, items[i]) - } - return result -} - // Repeat repeats items slice n times. func Repeat[S ~[]T, T any](items S, n int) S { result := make([]T, 0, len(items)*n) @@ -482,6 +463,18 @@ func Replace[S ~[]T, T comparable, I constraints.Integer](items S, start, end I, return result, nil } +// Reverse returns given arr in reversed order +func Reverse[S ~[]T, T any](items S) S { + if len(items) <= 1 { + return items + } + result := make([]T, 0, len(items)) + for i := len(items) - 1; i >= 0; i-- { + result = append(result, items[i]) + } + return result +} + // Same returns true if all element in arr the same func Same[S ~[]T, T comparable](items S) bool { if len(items) <= 1 { @@ -495,6 +488,13 @@ func Same[S ~[]T, T comparable](items S) bool { return true } +// Shrink removes unused capacity from the slice. +// +// In other words, the returned slice has capacity equal to length. +func Shrink[S ~[]T, T any](items S) S { + return items[:len(items):len(items)] +} + // Shuffle in random order the given elements // // This is an in-place operation, it modifies the passed slice. @@ -640,28 +640,28 @@ func ToChannel[S ~[]T, T any](items S) chan T { return c } -// ToMap converts the given slice into a map where keys are indices and values are items -// from the given slice. -func ToMap[S ~[]V, V any](items S) map[int]V { +// ToKeys converts the given slice into a map where items from the slice are the keys +// of the resulting map and all values are equal to the given `val` value. +func ToKeys[S ~[]K, K comparable, V any](items S, val V) map[K]V { if items == nil { return nil } - result := make(map[int]V) - for index, item := range items { - result[index] = item + result := make(map[K]V) + for _, item := range items { + result[item] = val } return result } -// ToKeys converts the given slice into a map where items from the slice are the keys -// of the resulting map and all values are equal to the given `val` value. -func ToKeys[S ~[]K, K comparable, V any](items S, val V) map[K]V { +// ToMap converts the given slice into a map where keys are indices and values are items +// from the given slice. +func ToMap[S ~[]V, V any](items S) map[int]V { if items == nil { return nil } - result := make(map[K]V) - for _, item := range items { - result[item] = val + result := make(map[int]V) + for index, item := range items { + result[index] = item } return result } diff --git a/slices/slice_func.go b/slices/slice_func.go index 0bc56fe..9662095 100644 --- a/slices/slice_func.go +++ b/slices/slice_func.go @@ -6,16 +6,6 @@ import ( "github.com/life4/genesis/constraints" ) -// Any returns true if f returns true for any element in arr -func Any[S ~[]T, T any](items S, f func(el T) bool) bool { - for _, el := range items { - if f(el) { - return true - } - } - return false -} - // All returns true if f returns true for all elements in arr func All[S ~[]T, T any](items S, f func(el T) bool) bool { for _, el := range items { @@ -26,30 +16,14 @@ func All[S ~[]T, T any](items S, f func(el T) bool) bool { return true } -// CountBy returns how many times f returns true. -func CountBy[S ~[]T, T any](items S, f func(el T) bool) int { - count := 0 +// Any returns true if f returns true for any element in arr +func Any[S ~[]T, T any](items S, f func(el T) bool) bool { for _, el := range items { if f(el) { - count++ - } - } - return count -} - -// EqualBy returns true if the cmp function returns true for any elements of the slices -// in the matching positions. If len of the slices is different, false is returned. -// It is similar to Any except it Zip's by two slices. -func EqualBy[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) bool) bool { - if len(s1) != len(s2) { - return false - } - for i, v1 := range s1 { - if !eq(v1, s2[i]) { - return false + return true } } - return true + return false } // ChunkBy splits arr on every element for which f returns a new value. @@ -78,6 +52,17 @@ func ChunkBy[S ~[]T, T comparable, G comparable](items S, f func(el T) G) []S { return chunks } +// CountBy returns how many times f returns true. +func CountBy[S ~[]T, T any](items S, f func(el T) bool) int { + count := 0 + for _, el := range items { + if f(el) { + count++ + } + } + return count +} + // DedupBy returns a given slice without consecutive elements // For which f returns the same result func DedupBy[S ~[]T, T comparable, G comparable](items S, f func(el T) G) S { @@ -127,6 +112,21 @@ func EachErr[S ~[]E, E any](items S, f func(el E) error) error { return err } +// EqualBy returns true if the cmp function returns true for any elements of the slices +// in the matching positions. If len of the slices is different, false is returned. +// It is similar to Any except it Zip's by two slices. +func EqualBy[S1 ~[]E1, S2 ~[]E2, E1, E2 any](s1 S1, s2 S2, eq func(E1, E2) bool) bool { + if len(s1) != len(s2) { + return false + } + for i, v1 := range s1 { + if !eq(v1, s2[i]) { + return false + } + } + return true +} + // Filter returns slice of T for which F returned true func Filter[S ~[]T, T any](items S, f func(el T) bool) S { result := make([]T, 0, len(items)) diff --git a/slices/slice_func_test.go b/slices/slice_func_test.go index ce57cd0..1005c86 100644 --- a/slices/slice_func_test.go +++ b/slices/slice_func_test.go @@ -9,32 +9,32 @@ import ( "github.com/matryer/is" ) -func TestAny(t *testing.T) { +func TestAll(t *testing.T) { is := is.New(t) f := func(given []int, expected bool) { even := func(t int) bool { return (t % 2) == 0 } - actual := slices.Any(given, even) + actual := slices.All(given, even) is.Equal(actual, expected) } - f([]int{}, false) - f([]int{1, 3}, false) + f([]int{}, true) f([]int{2}, true) - f([]int{1, 2}, true) + f([]int{1}, false) + f([]int{2, 4}, true) + f([]int{2, 4, 1}, false) + f([]int{1, 2, 4}, false) } -func TestAll(t *testing.T) { +func TestAny(t *testing.T) { is := is.New(t) f := func(given []int, expected bool) { even := func(t int) bool { return (t % 2) == 0 } - actual := slices.All(given, even) + actual := slices.Any(given, even) is.Equal(actual, expected) } - f([]int{}, true) + f([]int{}, false) + f([]int{1, 3}, false) f([]int{2}, true) - f([]int{1}, false) - f([]int{2, 4}, true) - f([]int{2, 4, 1}, false) - f([]int{1, 2, 4}, false) + f([]int{1, 2}, true) } func TestChunkBy(t *testing.T) { diff --git a/slices/slice_test.go b/slices/slice_test.go index 0899914..11d47fc 100644 --- a/slices/slice_test.go +++ b/slices/slice_test.go @@ -55,6 +55,20 @@ func TestContains(t *testing.T) { f(1, []int{2, 3, 1, 1, 4, 5}, true) } +func TestCopy(t *testing.T) { + is := is.New(t) + a := make([]int, 0, 10) + a = append(a, 4) + a = append(a, 5) + b := slices.Copy(a) + a = append(a, 8) + b = append(b, 9) + is.Equal(a, []int{4, 5, 8}) + is.Equal(b, []int{4, 5, 9}) + + is.Equal(slices.Copy[[]int](nil), nil) +} + func TestCount(t *testing.T) { is := is.New(t) f := func(el int, given []int, expected int) { @@ -70,20 +84,6 @@ func TestCount(t *testing.T) { f(1, []int{1, 1, 1, 1, 1}, 5) } -func TestCopy(t *testing.T) { - is := is.New(t) - a := make([]int, 0, 10) - a = append(a, 4) - a = append(a, 5) - b := slices.Copy(a) - a = append(a, 8) - b = append(b, 9) - is.Equal(a, []int{4, 5, 8}) - is.Equal(b, []int{4, 5, 9}) - - is.Equal(slices.Copy[[]int](nil), nil) -} - func TestCycle(t *testing.T) { is := is.New(t) f := func(count int, given []int, expected []int) { @@ -126,14 +126,6 @@ func TestDelete(t *testing.T) { f([]int{1, 2, 2, 3, 2}, 2, []int{1, 2, 3, 2}) } -func TestDropZero(t *testing.T) { - is := is.New(t) - is.Equal(slices.DropZero([]int{}), []int{}) - is.Equal(slices.DropZero([]int{4, 5, 0, 6, 0, 0}), []int{4, 5, 6}) - is.Equal(slices.DropZero([]string{"a", "", "bc"}), []string{"a", "bc"}) - is.Equal(slices.DropZero([]*int{nil}), []*int{}) -} - func TestDeleteAll(t *testing.T) { is := is.New(t) f := func(given []int, el int, expected []int) { @@ -197,28 +189,12 @@ func TestDropEvery(t *testing.T) { is.Equal(err, slices.ErrNegativeValue) } -func TestGrow(t *testing.T) { - is := is.New(t) - arr := make([]int, 3, 5) - is.Equal(len(arr), 3) - is.Equal(cap(arr), 5) - res := slices.Grow(arr, 4) - is.Equal(len(arr), 3) - is.Equal(cap(arr), 5) - is.Equal(len(res), 3) - is.True(cap(res) >= 9) -} - -func TestShrink(t *testing.T) { +func TestDropZero(t *testing.T) { is := is.New(t) - arr := make([]int, 3, 5) - is.Equal(len(arr), 3) - is.Equal(cap(arr), 5) - res := slices.Shrink(arr) - is.Equal(len(arr), 3) - is.Equal(cap(arr), 5) - is.Equal(len(res), 3) - is.Equal(cap(res), 3) + is.Equal(slices.DropZero([]int{}), []int{}) + is.Equal(slices.DropZero([]int{4, 5, 0, 6, 0, 0}), []int{4, 5, 6}) + is.Equal(slices.DropZero([]string{"a", "", "bc"}), []string{"a", "bc"}) + is.Equal(slices.DropZero([]*int{nil}), []*int{}) } func TestEndsWith(t *testing.T) { @@ -265,18 +241,16 @@ func TestEqual(t *testing.T) { f([]int{1, 2, 3}, []int{}, false) } -func TestJoin(t *testing.T) { +func TestGrow(t *testing.T) { is := is.New(t) - is.Equal(slices.Join([]int{}, ""), "") - is.Equal(slices.Join([]int{}, "|"), "") - - is.Equal(slices.Join([]int{1}, ""), "1") - is.Equal(slices.Join([]int{1}, "|"), "1") - - is.Equal(slices.Join([]int{1, 2, 3}, ""), "123") - is.Equal(slices.Join([]int{1, 2, 3}, "|"), "1|2|3") - is.Equal(slices.Join([]int{1, 2, 3}, ""), "123") - is.Equal(slices.Join([]string{"hello", "world"}, " "), "hello world") + arr := make([]int, 3, 5) + is.Equal(len(arr), 3) + is.Equal(cap(arr), 5) + res := slices.Grow(arr, 4) + is.Equal(len(arr), 3) + is.Equal(cap(arr), 5) + is.Equal(len(res), 3) + is.True(cap(res) >= 9) } func TestIndex(t *testing.T) { @@ -328,6 +302,20 @@ func TestIntersperse(t *testing.T) { f(0, []int{1, 2, 3}, []int{1, 0, 2, 0, 3}) } +func TestJoin(t *testing.T) { + is := is.New(t) + is.Equal(slices.Join([]int{}, ""), "") + is.Equal(slices.Join([]int{}, "|"), "") + + is.Equal(slices.Join([]int{1}, ""), "1") + is.Equal(slices.Join([]int{1}, "|"), "1") + + is.Equal(slices.Join([]int{1, 2, 3}, ""), "123") + is.Equal(slices.Join([]int{1, 2, 3}, "|"), "1|2|3") + is.Equal(slices.Join([]int{1, 2, 3}, ""), "123") + is.Equal(slices.Join([]string{"hello", "world"}, " "), "hello world") +} + func TestLast(t *testing.T) { is := is.New(t) f := func(given []int, expectedEl int, expectedErr error) { @@ -428,19 +416,6 @@ func TestProduct(t *testing.T) { }) } -func TestReverse(t *testing.T) { - is := is.New(t) - f := func(given []int, expected []int) { - actual := slices.Reverse(given) - is.Equal(expected, actual) - } - f([]int{}, []int{}) - f([]int{1}, []int{1}) - f([]int{1, 2}, []int{2, 1}) - f([]int{1, 2, 3}, []int{3, 2, 1}) - f([]int{1, 2, 2, 3, 3}, []int{3, 3, 2, 2, 1}) -} - func TestRepeat(t *testing.T) { is := is.New(t) is.Equal(slices.Repeat([]int{}, 0), []int{}) @@ -481,6 +456,19 @@ func TestReplace(t *testing.T) { f([]int{4, 5, 6, 7}, 2, 3, 9, []int{4, 5, 9, 7}) } +func TestReverse(t *testing.T) { + is := is.New(t) + f := func(given []int, expected []int) { + actual := slices.Reverse(given) + is.Equal(expected, actual) + } + f([]int{}, []int{}) + f([]int{1}, []int{1}) + f([]int{1, 2}, []int{2, 1}) + f([]int{1, 2, 3}, []int{3, 2, 1}) + f([]int{1, 2, 2, 3, 3}, []int{3, 3, 2, 2, 1}) +} + func TestSame(t *testing.T) { is := is.New(t) f := func(given []int, expected bool) { @@ -497,6 +485,17 @@ func TestSame(t *testing.T) { f([]int{1, 1, 2}, false) } +func TestShrink(t *testing.T) { + is := is.New(t) + arr := make([]int, 3, 5) + is.Equal(len(arr), 3) + is.Equal(cap(arr), 5) + res := slices.Shrink(arr) + is.Equal(len(arr), 3) + is.Equal(cap(arr), 5) + is.Equal(len(res), 3) + is.Equal(cap(res), 3) +} func TestShuffle(t *testing.T) { is := is.New(t) f := func(given []int, seed int64, expected []int) { @@ -640,6 +639,24 @@ func TestTakeEvery(t *testing.T) { is.Equal(err, slices.ErrNegativeValue) } +func TestTakeRandom(t *testing.T) { + is := is.New(t) + f := func(given []int, count int, seed int64, expected []int) { + actual, err := slices.TakeRandom(given, count, seed) + is.NoErr(err) + is.Equal(expected, actual) + } + f([]int{1}, 1, 0, []int{1}) + f([]int{1, 2, 3, 4, 5}, 3, 1, []int{3, 1, 2}) + f([]int{1, 2, 3, 4, 5}, 5, 1, []int{3, 1, 2, 5, 4}) + + _, err := slices.TakeRandom([]int{1, 2}, 5, 0) + is.Equal(err, slices.ErrOutOfRange) + + _, err = slices.TakeRandom([]int{1, 2}, -1, 0) + is.Equal(err, slices.ErrNonPositiveValue) +} + func TestToChannel(t *testing.T) { is := is.New(t) f := func(given, expected []int) { @@ -657,18 +674,6 @@ func TestToChannel(t *testing.T) { f([]int{4, 7, 9, 9, 4, 0}, []int{4, 7, 9, 9, 4, 0}) } -func TestToMap(t *testing.T) { - is := is.New(t) - f := func(given []int32, expected map[int]int32) { - actual := slices.ToMap(given) - is.Equal(expected, actual) - } - f([]int32{}, map[int]int32{}) - f([]int32{5}, map[int]int32{0: 5}) - f([]int32{5, 4, 4}, map[int]int32{0: 5, 1: 4, 2: 4}) - f(nil, nil) -} - func TestToKeys(t *testing.T) { is := is.New(t) f := func(given []int32, expected map[int32]int) { @@ -681,22 +686,16 @@ func TestToKeys(t *testing.T) { f(nil, nil) } -func TestTakeRandom(t *testing.T) { +func TestToMap(t *testing.T) { is := is.New(t) - f := func(given []int, count int, seed int64, expected []int) { - actual, err := slices.TakeRandom(given, count, seed) - is.NoErr(err) + f := func(given []int32, expected map[int]int32) { + actual := slices.ToMap(given) is.Equal(expected, actual) } - f([]int{1}, 1, 0, []int{1}) - f([]int{1, 2, 3, 4, 5}, 3, 1, []int{3, 1, 2}) - f([]int{1, 2, 3, 4, 5}, 5, 1, []int{3, 1, 2, 5, 4}) - - _, err := slices.TakeRandom([]int{1, 2}, 5, 0) - is.Equal(err, slices.ErrOutOfRange) - - _, err = slices.TakeRandom([]int{1, 2}, -1, 0) - is.Equal(err, slices.ErrNonPositiveValue) + f([]int32{}, map[int]int32{}) + f([]int32{5}, map[int]int32{0: 5}) + f([]int32{5, 4, 4}, map[int]int32{0: 5, 1: 4, 2: 4}) + f(nil, nil) } func TestUniq(t *testing.T) { @@ -714,6 +713,21 @@ func TestUniq(t *testing.T) { f([]int{1, 2, 1, 2, 3, 2, 1, 1}, []int{1, 2, 3}) } +func TestUnique(t *testing.T) { + is := is.New(t) + f := func(given []int, expected bool) { + actual := slices.Unique(given) + is.Equal(expected, actual) + } + f([]int{}, true) + f([]int{1}, true) + f([]int{1, 1}, false) + f([]int{1, 2, 2}, false) + f([]int{1, 2, 3}, true) + f([]int{2, 1}, true) + f([]int{1, 2, 1}, false) +} + func TestWindow(t *testing.T) { is := is.New(t) f := func(given []int, size int, expected [][]int) { @@ -733,21 +747,6 @@ func TestWindow(t *testing.T) { is.Equal(err, slices.ErrNonPositiveValue) } -func TestUnique(t *testing.T) { - is := is.New(t) - f := func(given []int, expected bool) { - actual := slices.Unique(given) - is.Equal(expected, actual) - } - f([]int{}, true) - f([]int{1}, true) - f([]int{1, 1}, false) - f([]int{1, 2, 2}, false) - f([]int{1, 2, 3}, true) - f([]int{2, 1}, true) - f([]int{1, 2, 1}, false) -} - func TestWithout(t *testing.T) { is := is.New(t) f := func(given []int, items []int, expected []int) { diff --git a/slices/slices_test.go b/slices/slices_test.go index 397ae90..ca1c341 100644 --- a/slices/slices_test.go +++ b/slices/slices_test.go @@ -38,6 +38,23 @@ func TestDifference(t *testing.T) { f([]int{4, 5, 3, 1, 2, 1}, []int{1, 5, 5}, []int{4, 3, 2}) } +func TestIntersect(t *testing.T) { + is := is.New(t) + f := func(left, right []int, exp []int) { + act := slices.Intersect(left, right) + is.Equal(act, exp) + } + f([]int{}, []int{}, []int{}) + f([]int{}, []int{4}, []int{}) + f([]int{4}, []int{}, []int{}) + f([]int{4}, []int{5}, []int{}) + f([]int{4}, []int{4}, []int{4}) + f([]int{4}, []int{4, 4}, []int{4}) + f([]int{4, 4}, []int{4}, []int{4}) + f([]int{4, 4}, []int{4, 4}, []int{4}) + f([]int{1, 2, 2, 3, 1}, []int{2, 3, 4, 2, 5}, []int{2, 3}) +} + func TestProduct2(t *testing.T) { is := is.New(t) f := func(given [][]int, expected [][]int) { @@ -56,23 +73,6 @@ func TestProduct2(t *testing.T) { f([][]int{{1, 2}, {3}, {4, 5}}, [][]int{{1, 3, 4}, {1, 3, 5}, {2, 3, 4}, {2, 3, 5}}) } -func TestIntersect(t *testing.T) { - is := is.New(t) - f := func(left, right []int, exp []int) { - act := slices.Intersect(left, right) - is.Equal(act, exp) - } - f([]int{}, []int{}, []int{}) - f([]int{}, []int{4}, []int{}) - f([]int{4}, []int{}, []int{}) - f([]int{4}, []int{5}, []int{}) - f([]int{4}, []int{4}, []int{4}) - f([]int{4}, []int{4, 4}, []int{4}) - f([]int{4, 4}, []int{4}, []int{4}) - f([]int{4, 4}, []int{4, 4}, []int{4}) - f([]int{1, 2, 2, 3, 1}, []int{2, 3, 4, 2, 5}, []int{2, 3}) -} - func TestUnion(t *testing.T) { is := is.New(t) f := func(left, right []int, exp []int) { From 713a0cb52d314611f7a2ccaacd8e7d86f9cb3f4d Mon Sep 17 00:00:00 2001 From: gram Date: Tue, 19 Sep 2023 15:05:17 +0200 Subject: [PATCH 4/5] channels: sort all functions --- channels/channel.go | 10 ++-- channels/channel_ctx.go | 24 ++++---- channels/channel_test.go | 124 +++++++++++++++++++-------------------- scripts/test_docs.py | 2 + 4 files changed, 81 insertions(+), 79 deletions(-) diff --git a/channels/channel.go b/channels/channel.go index 8a03aeb..3268fb9 100644 --- a/channels/channel.go +++ b/channels/channel.go @@ -6,16 +6,16 @@ import ( "github.com/life4/genesis/constraints" ) -// Any returns true if f returns true for any element in channel. -func Any[T any](c <-chan T, f func(el T) bool) bool { - return AnyC(context.Background(), c, f) -} - // All is an alias for [AllC] without a context. func All[T any](c <-chan T, f func(el T) bool) bool { return AllC(context.Background(), c, f) } +// Any returns true if f returns true for any element in channel. +func Any[T any](c <-chan T, f func(el T) bool) bool { + return AnyC(context.Background(), c, f) +} + // BufferSize returns how many messages a channel can hold before being blocked. // // When you push that many messages in the channel, the next attempt diff --git a/channels/channel_ctx.go b/channels/channel_ctx.go index 60107c1..a2ae27d 100644 --- a/channels/channel_ctx.go +++ b/channels/channel_ctx.go @@ -8,38 +8,38 @@ import ( "github.com/life4/genesis/constraints" ) -// Any returns true if f returns true for any element in channel. -func AnyC[T any](ctx context.Context, c <-chan T, f func(el T) bool) bool { +// All returns true if f returns true for all elements in channel. +func AllC[T any](ctx context.Context, c <-chan T, f func(el T) bool) bool { for { select { case el, ok := <-c: if !ok { - return false - } - if f(el) { return true + } + if !f(el) { + return false } case <-ctx.Done(): - return false + return true } } } -// All returns true if f returns true for all elements in channel. -func AllC[T any](ctx context.Context, c <-chan T, f func(el T) bool) bool { +// Any returns true if f returns true for any element in channel. +func AnyC[T any](ctx context.Context, c <-chan T, f func(el T) bool) bool { for { select { case el, ok := <-c: if !ok { - return true - } - if !f(el) { return false + } + if f(el) { + return true } case <-ctx.Done(): - return true + return false } } } diff --git a/channels/channel_test.go b/channels/channel_test.go index 4762229..f806da2 100644 --- a/channels/channel_test.go +++ b/channels/channel_test.go @@ -8,9 +8,10 @@ import ( "github.com/matryer/is" ) -func TestToSlice(t *testing.T) { +func TestAll(t *testing.T) { is := is.New(t) - f := func(given []int) { + f := func(given []int, expected bool) { + even := func(t int) bool { return t%2 == 0 } c := make(chan int, 1) go func() { for _, el := range given { @@ -18,12 +19,16 @@ func TestToSlice(t *testing.T) { } close(c) }() - actual := channels.ToSlice(c) - is.Equal(given, actual) + actual := channels.All(c, even) + is.Equal(expected, actual) } - f([]int{}) - f([]int{1}) - f([]int{1, 2, 3, 1, 2}) + f([]int{}, true) + f([]int{1}, false) + f([]int{2}, true) + f([]int{1, 2}, false) + f([]int{2, 4}, true) + f([]int{2, 4, 6, 8, 10, 12}, true) + f([]int{2, 4, 6, 8, 11, 12}, false) } func TestAny(t *testing.T) { @@ -50,29 +55,6 @@ func TestAny(t *testing.T) { f([]int{1, 3, 5, 7, 10, 11}, true) } -func TestAll(t *testing.T) { - is := is.New(t) - f := func(given []int, expected bool) { - even := func(t int) bool { return t%2 == 0 } - c := make(chan int, 1) - go func() { - for _, el := range given { - c <- el - } - close(c) - }() - actual := channels.All(c, even) - is.Equal(expected, actual) - } - f([]int{}, true) - f([]int{1}, false) - f([]int{2}, true) - f([]int{1, 2}, false) - f([]int{2, 4}, true) - f([]int{2, 4, 6, 8, 10, 12}, true) - f([]int{2, 4, 6, 8, 11, 12}, false) -} - func TestBufferSize(t *testing.T) { is := is.New(t) is.Equal(channels.BufferSize(make(chan int, 3)), 3) @@ -80,38 +62,6 @@ func TestBufferSize(t *testing.T) { is.Equal(channels.BufferSize[int](nil), 0) } -func TestClose(t *testing.T) { - is := is.New(t) - is.True(!channels.Close[int](nil)) - c := make(chan int) - is.True(channels.Close(c)) - is.True(!channels.Close(c)) -} - -func TestEach(t *testing.T) { - is := is.New(t) - f := func(given []int) { - c := make(chan int, 1) - go func() { - for _, el := range given { - c <- el - } - close(c) - }() - result := make(chan int, len(given)) - mapper := func(t int) { result <- t } - channels.Each(c, mapper) - close(result) - actual := channels.ToSlice(result) - is.Equal(given, actual) - } - - f([]int{}) - f([]int{1}) - f([]int{1, 2, 3}) - f([]int{1, 2, 3, 4, 5, 6, 7}) -} - func TestChunkEvery(t *testing.T) { is := is.New(t) f := func(size int, given []int, expected [][]int) { @@ -136,6 +86,14 @@ func TestChunkEvery(t *testing.T) { f(2, []int{1, 2, 3, 4, 5}, [][]int{{1, 2}, {3, 4}, {5}}) } +func TestClose(t *testing.T) { + is := is.New(t) + is.True(!channels.Close[int](nil)) + c := make(chan int) + is.True(channels.Close(c)) + is.True(!channels.Close(c)) +} + func TestCount(t *testing.T) { is := is.New(t) f := func(element int, given []int, expected int) { @@ -181,6 +139,30 @@ func TestDrop(t *testing.T) { f(1, []int{1, 2, 3, 4, 5, 6}, []int{2, 3, 4, 5, 6}) } +func TestEach(t *testing.T) { + is := is.New(t) + f := func(given []int) { + c := make(chan int, 1) + go func() { + for _, el := range given { + c <- el + } + close(c) + }() + result := make(chan int, len(given)) + mapper := func(t int) { result <- t } + channels.Each(c, mapper) + close(result) + actual := channels.ToSlice(result) + is.Equal(given, actual) + } + + f([]int{}) + f([]int{1}) + f([]int{1, 2, 3}) + f([]int{1, 2, 3, 4, 5, 6, 7}) +} + func TestFilter(t *testing.T) { is := is.New(t) f := func(given []int, expected []int) { @@ -460,6 +442,24 @@ func TestTee(t *testing.T) { f(10, []int{1, 2, 3, 1, 2}) } +func TestToSlice(t *testing.T) { + is := is.New(t) + f := func(given []int) { + c := make(chan int, 1) + go func() { + for _, el := range given { + c <- el + } + close(c) + }() + actual := channels.ToSlice(c) + is.Equal(given, actual) + } + f([]int{}) + f([]int{1}) + f([]int{1, 2, 3, 1, 2}) +} + func TestWithBuffer(t *testing.T) { is := is.New(t) c1 := make(chan int) diff --git a/scripts/test_docs.py b/scripts/test_docs.py index 67d5f07..65bb13f 100644 --- a/scripts/test_docs.py +++ b/scripts/test_docs.py @@ -90,10 +90,12 @@ def test_all_have_tests(pkg: str) -> None: 'sets', 'lambdas', 'slices', + 'channels', ]) def test_all_funcs_sorted(pkg: str) -> None: for fpath in Path(pkg).iterdir(): funcs = list(get_funcs_for_file(fpath)) + funcs = [func.split('_')[0] for func in funcs] assert funcs == sorted(funcs) From 7f29aeba12f505c6e4bf0e734b406968fa0343da Mon Sep 17 00:00:00 2001 From: gram Date: Tue, 19 Sep 2023 15:13:03 +0200 Subject: [PATCH 5/5] enforce empty line between funcs --- lambdas/checks_test.go | 1 + scripts/test_docs.py | 35 ++++++++++++++++++++--------------- slices/slice_test.go | 1 + 3 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lambdas/checks_test.go b/lambdas/checks_test.go index 030832c..f53f11a 100644 --- a/lambdas/checks_test.go +++ b/lambdas/checks_test.go @@ -103,6 +103,7 @@ func TestIsNotZero(t *testing.T) { is.True(lambdas.IsNotZero(float64(13))) is.True(lambdas.IsNotZero(-1)) } + func TestIsZero(t *testing.T) { is := is.New(t) is.True(lambdas.IsZero(int(0))) diff --git a/scripts/test_docs.py b/scripts/test_docs.py index 65bb13f..2554046 100644 --- a/scripts/test_docs.py +++ b/scripts/test_docs.py @@ -3,6 +3,14 @@ from typing import Iterator import pytest +ALL_PACKAGES = ( + 'channels', + 'lambdas', + 'maps', + 'sets', + 'slices', +) + def get_funcs_for_pkg(pkg: str) -> Iterator[str]: for fpath in Path(pkg).iterdir(): @@ -67,14 +75,8 @@ def test_all_have_examples(pkg: str) -> None: assert funcs == examples -@pytest.mark.parametrize('pkg', [ - 'slices', - 'maps', - 'lambdas', - 'sets', - # channels need tests for context-aware versions - # 'channels', -]) +# channels need tests for context-aware versions +@pytest.mark.parametrize('pkg', sorted(set(ALL_PACKAGES) - {'channels'})) def test_all_have_tests(pkg: str) -> None: """Every function must have unit tests. """ @@ -85,13 +87,7 @@ def test_all_have_tests(pkg: str) -> None: assert not diff -@pytest.mark.parametrize('pkg', [ - 'maps', - 'sets', - 'lambdas', - 'slices', - 'channels', -]) +@pytest.mark.parametrize('pkg', ALL_PACKAGES) def test_all_funcs_sorted(pkg: str) -> None: for fpath in Path(pkg).iterdir(): funcs = list(get_funcs_for_file(fpath)) @@ -99,6 +95,15 @@ def test_all_funcs_sorted(pkg: str) -> None: assert funcs == sorted(funcs) +@pytest.mark.parametrize('pkg', ALL_PACKAGES) +def test_all_funcs_separated_by_newline(pkg: str) -> None: + for fpath in Path(pkg).iterdir(): + content = fpath.read_text() + funcs = content.split('}\n') + for func in funcs: + assert not func.startswith('func'), 'must have an empty line' + + @pytest.mark.parametrize('func', get_funcs_for_pkg('slices')) def test_slices_func_linked_in_docs(func: str) -> None: """Every func in the slices package must be listed in the package docs. diff --git a/slices/slice_test.go b/slices/slice_test.go index 11d47fc..62df543 100644 --- a/slices/slice_test.go +++ b/slices/slice_test.go @@ -496,6 +496,7 @@ func TestShrink(t *testing.T) { is.Equal(len(res), 3) is.Equal(cap(res), 3) } + func TestShuffle(t *testing.T) { is := is.New(t) f := func(given []int, seed int64, expected []int) {