From e235ba1361108af8a79bcf847f409f7632d21469 Mon Sep 17 00:00:00 2001 From: gram Date: Sun, 10 Sep 2023 12:08:48 +0200 Subject: [PATCH 01/15] sets: implement first functions --- sets/set.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++ sets/sets.go | 27 ++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 sets/set.go create mode 100644 sets/sets.go diff --git a/sets/set.go b/sets/set.go new file mode 100644 index 0000000..83940a3 --- /dev/null +++ b/sets/set.go @@ -0,0 +1,63 @@ +package sets + +import ( + c "github.com/life4/genesis/constraints" +) + +// Z is a zero type used as the map value for sets. +type Z = struct{} + +func Contains[S ~map[K]Z, K comparable](set S, item K) bool { + var found bool + _, found = set[item] + return found +} + +// Copy returns a shallow copy of the given set. +func Copy[S ~map[K]Z, K comparable](set S) S { + result := make(S) + for k := range set { + result[k] = Z{} + } + return result +} + +// Equal returns true if both given sets have the same elements. +func Equal[S1, S2 ~map[K]Z, K comparable](first S1, second S2) bool { + if len(first) != len(second) { + return false + } + for k := range second { + _, found := first[k] + if !found { + return false + } + } + return true +} + +// FromSlice converts a slice into a set. +func FromSlice[S ~[]K, K comparable](items S) map[K]Z { + set := make(map[K]Z) + for _, item := range items { + set[item] = Z{} + } + return set +} + +// Map applies the given function to each element of the set and returns a set of results. +func Map[S ~map[K]Z, K, R comparable](set S, f func(K) R) map[R]Z { + result := make(map[R]Z) + for item := range set { + result[f(item)] = Z{} + } + return result +} + +func Sum[S ~map[K]Z, K c.Integer | c.Float](set S) K { + var result K + for item := range set { + result += item + } + return result +} diff --git a/sets/sets.go b/sets/sets.go new file mode 100644 index 0000000..ea7ae78 --- /dev/null +++ b/sets/sets.go @@ -0,0 +1,27 @@ +package sets + +// DisjointMany returns true if the given sets don't have any common elements. +func DisjointMany[S ~map[K]Z, K comparable](sets ...S) bool { + union := make(map[K]Z) + for _, set := range sets { + for k := range set { + _, seen := union[k] + if seen { + return false + } + union[k] = Z{} + } + } + return true +} + +// Union returns a set containing all elements from all the given sets. +func UnionMany[S ~map[K]Z, K comparable](sets ...S) map[K]Z { + result := make(map[K]Z) + for _, set := range sets { + for k := range set { + result[k] = Z{} + } + } + return result +} From 243c0074f0e7b6b6958aaf500d1fef00522e8b5b Mon Sep 17 00:00:00 2001 From: gram Date: Mon, 11 Sep 2023 10:27:07 +0200 Subject: [PATCH 02/15] more funcs, regroup elements --- sets/bool.go | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++ sets/errors.go | 8 +++++ sets/rest.go | 70 ++++++++++++++++++++++++++++++++++++ sets/set.go | 52 ++++++++++++--------------- sets/sets.go | 27 -------------- 5 files changed, 198 insertions(+), 57 deletions(-) create mode 100644 sets/bool.go create mode 100644 sets/errors.go create mode 100644 sets/rest.go delete mode 100644 sets/sets.go diff --git a/sets/bool.go b/sets/bool.go new file mode 100644 index 0000000..8f66f36 --- /dev/null +++ b/sets/bool.go @@ -0,0 +1,98 @@ +package sets + +// This file contains functions returning bool. + +// Contains returns true if the given set contains the given element. +func Contains[S ~map[K]Z, K comparable](set S, item K) bool { + var found bool + _, found = set[item] + return found +} + +// Disjoint returns true if the two given sets don't have any common elements. +func Disjoint[S1, S2 ~map[K]Z, K comparable](first S1, second S2) bool { + for k := range second { + _, common := first[k] + if common { + return false + } + } + return true +} + +// DisjointMany returns true if the given sets don't have any common elements. +func DisjointMany[S ~map[K]Z, K comparable](sets ...S) bool { + union := make(map[K]Z) + for _, set := range sets { + for k := range set { + _, seen := union[k] + if seen { + return false + } + union[k] = Z{} + } + } + return true +} + +// Empty returns true if the set has no elements +func Empty[S ~map[K]Z, K comparable](set S) bool { + return len(set) == 0 +} + +// Equal returns true if both given sets have the same elements. +func Equal[S1, S2 ~map[K]Z, K comparable](first S1, second S2) bool { + if len(first) != len(second) { + return false + } + for k := range second { + _, found := first[k] + if !found { + return false + } + } + return true +} + +// Intersect returns true if the two given sets have at least one common element. +func Intersect[S1, S2 ~map[K]Z, K comparable](first S1, second S2) bool { + for k := range second { + _, common := first[k] + if common { + return true + } + } + return false +} + +// Subset returns true if the first set is a subset of the second one. +// +// One set is called a subset of another if all its elements are included in that set. +// +// This function is the same as [Superset] but with inversed argument order. +// Which one to use is a matter of readability. +func Subset[S1, S2 ~map[K]Z, K comparable](small S1, big S2) bool { + for k := range small { + _, found := big[k] + if !found { + return false + } + } + return true +} + +// Superset returns true if the first set is a superset of the second one. +// +// One set is called a superset of another if it includes all elements of that set. +// +// This function is the same as [Subset] but with inversed argument order. +// Which one to use is a matter of readability. +func Superset[S1, S2 ~map[K]Z, K comparable](big S1, small S2) bool { + for k := range small { + _, found := big[k] + if !found { + return false + } + } + return true +} diff --git a/sets/errors.go b/sets/errors.go new file mode 100644 index 0000000..e0d8455 --- /dev/null +++ b/sets/errors.go @@ -0,0 +1,8 @@ +package sets + +import ( + "errors" +) + +// ErrEmpty is an error for empty set when it's expected to have elements +var ErrEmpty = errors.New("container is empty") diff --git a/sets/rest.go b/sets/rest.go new file mode 100644 index 0000000..1b3f52c --- /dev/null +++ b/sets/rest.go @@ -0,0 +1,70 @@ +package sets + +// This file contains functions returning not a bool or a set. Usually, a set element. + +import ( + c "github.com/life4/genesis/constraints" +) + +// Z is a zero type used as the map value for sets. +type Z = struct{} + +func Max[S ~map[K]Z, K c.Ordered](set S) (K, error) { + var max K + first := true + for k := range set { + if first { + max = k + first = false + continue + } + if k > max { + max = k + } + } + if first { + return max, ErrEmpty + } + return max, nil +} + +func Min[S ~map[K]Z, K c.Ordered](set S) (K, error) { + var min K + first := true + for k := range set { + if first { + min = k + first = false + continue + } + if k < min { + min = k + } + } + if first { + return min, ErrEmpty + } + return min, nil +} + +// Sum returns the sum of all elements of the set. +// +// If the set is empty, 0 is returned. +func Sum[S ~map[K]Z, K c.Integer | c.Float](set S) K { + var result K + for item := range set { + result += item + } + return result +} + +// ToSlice converts the set to a slice. +// +// The order of elements in the resulting set is semi-random. +func ToSlice[S ~map[K]Z, K comparable](set S) []K { + result := make([]K, 0, len(set)) + for k := range set { + result = append(result, k) + } + return result +} diff --git a/sets/set.go b/sets/set.go index 83940a3..5d8dfbc 100644 --- a/sets/set.go +++ b/sets/set.go @@ -1,17 +1,6 @@ package sets -import ( - c "github.com/life4/genesis/constraints" -) - -// Z is a zero type used as the map value for sets. -type Z = struct{} - -func Contains[S ~map[K]Z, K comparable](set S, item K) bool { - var found bool - _, found = set[item] - return found -} +// This file contains functions returning a set. // Copy returns a shallow copy of the given set. func Copy[S ~map[K]Z, K comparable](set S) S { @@ -22,20 +11,6 @@ func Copy[S ~map[K]Z, K comparable](set S) S { return result } -// Equal returns true if both given sets have the same elements. -func Equal[S1, S2 ~map[K]Z, K comparable](first S1, second S2) bool { - if len(first) != len(second) { - return false - } - for k := range second { - _, found := first[k] - if !found { - return false - } - } - return true -} - // FromSlice converts a slice into a set. func FromSlice[S ~[]K, K comparable](items S) map[K]Z { set := make(map[K]Z) @@ -54,10 +29,27 @@ func Map[S ~map[K]Z, K, R comparable](set S, f func(K) R) map[R]Z { return result } -func Sum[S ~map[K]Z, K c.Integer | c.Float](set S) K { - var result K - for item := range set { - result += item +// Union returns a set containing all elements from all the given sets. +func UnionMany[S ~map[K]Z, K comparable](sets ...S) map[K]Z { + result := make(map[K]Z) + for _, set := range sets { + for k := range set { + result[k] = Z{} + } } return result } + +// Union +// Intersection +// Difference +// SymmetricDifference +// EqualMany +// Filter +// Reduce + +// Add +// Discard +// Pop +// Update +// Clear diff --git a/sets/sets.go b/sets/sets.go deleted file mode 100644 index ea7ae78..0000000 --- a/sets/sets.go +++ /dev/null @@ -1,27 +0,0 @@ -package sets - -// DisjointMany returns true if the given sets don't have any common elements. -func DisjointMany[S ~map[K]Z, K comparable](sets ...S) bool { - union := make(map[K]Z) - for _, set := range sets { - for k := range set { - _, seen := union[k] - if seen { - return false - } - union[k] = Z{} - } - } - return true -} - -// Union returns a set containing all elements from all the given sets. -func UnionMany[S ~map[K]Z, K comparable](sets ...S) map[K]Z { - result := make(map[K]Z) - for _, set := range sets { - for k := range set { - result[k] = Z{} - } - } - return result -} From 5a9491938bf9052d79a2c5fc3c4956d868505816 Mon Sep 17 00:00:00 2001 From: gram Date: Mon, 11 Sep 2023 12:54:31 +0200 Subject: [PATCH 03/15] more funcs --- sets/bool.go | 23 ++++++++++++++++ sets/rest.go | 8 ++++++ sets/set.go | 75 +++++++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 97 insertions(+), 9 deletions(-) diff --git a/sets/bool.go b/sets/bool.go index 8f66f36..f3a1ac8 100644 --- a/sets/bool.go +++ b/sets/bool.go @@ -54,6 +54,29 @@ func Equal[S1, S2 ~map[K]Z, K comparable](first S1, second S2) bool { return true } +// EqualMany returns true if all the given sets have the same elements. +func EqualMany[S ~map[K]Z, K comparable](sets ...S) bool { + if len(sets) < 2 { + return true + } + first := sets[0] + lFirst := len(first) + for _, set := range sets { + if len(set) != lFirst { + return false + } + } + for _, set := range sets[1:] { + for k := range set { + _, found := first[k] + if !found { + return false + } + } + } + return true +} + // Intersect returns true if the two given sets have at least one common element. func Intersect[S1, S2 ~map[K]Z, K comparable](first S1, second S2) bool { for k := range second { diff --git a/sets/rest.go b/sets/rest.go index 1b3f52c..9e33064 100644 --- a/sets/rest.go +++ b/sets/rest.go @@ -47,6 +47,14 @@ func Min[S ~map[K]Z, K c.Ordered](set S) (K, error) { return min, nil } +// Reduce applies the function to acc and every set element and returns the acc. +func Reduce[S ~map[K]Z, K comparable, R any](set S, acc R, f func(K, R) R) R { + for k := range set { + acc = f(k, acc) + } + return acc +} + // Sum returns the sum of all elements of the set. // // If the set is empty, 0 is returned. diff --git a/sets/set.go b/sets/set.go index 5d8dfbc..24a2919 100644 --- a/sets/set.go +++ b/sets/set.go @@ -11,6 +11,29 @@ func Copy[S ~map[K]Z, K comparable](set S) S { return result } +// Difference returns a set containing elements that appear in the first set but not in the second. +func Difference[S1, S2 ~map[K]Z, K comparable](first S1, second S2) map[K]Z { + result := make(map[K]Z) + for k := range first { + _, found := second[k] + if !found { + result[k] = Z{} + } + } + return result +} + +// Filter returns elements of the set for which the given function returns true. +func Filter[S ~map[K]Z, K comparable](set S, f func(K) bool) S { + result := make(S) + for k := range set { + if f(k) { + result[k] = Z{} + } + } + return result +} + // FromSlice converts a slice into a set. func FromSlice[S ~[]K, K comparable](items S) map[K]Z { set := make(map[K]Z) @@ -20,6 +43,18 @@ func FromSlice[S ~[]K, K comparable](items S) map[K]Z { return set } +// Intersection returns a set containing elements that appear in both of the given sets. +func Intersection[S1, S2 ~map[K]Z, K comparable](first S1, second S2) map[K]Z { + result := make(map[K]Z) + for k := range second { + _, found := first[k] + if found { + result[k] = Z{} + } + } + return result +} + // Map applies the given function to each element of the set and returns a set of results. func Map[S ~map[K]Z, K, R comparable](set S, f func(K) R) map[R]Z { result := make(map[R]Z) @@ -29,7 +64,37 @@ func Map[S ~map[K]Z, K, R comparable](set S, f func(K) R) map[R]Z { return result } -// Union returns a set containing all elements from all the given sets. +// SymmetricDifference returns a set containing elements that appear only in one set but not both. +func SymmetricDifference[S1, S2 ~map[K]Z, K comparable](first S1, second S2) map[K]Z { + result := make(map[K]Z) + for k := range first { + _, found := second[k] + if !found { + result[k] = Z{} + } + } + for k := range second { + _, found := first[k] + if !found { + result[k] = Z{} + } + } + return result +} + +// Union returns a set containing all elements from both of the given sets. +func Union[S1, S2 ~map[K]Z, K comparable](first S1, second S2) map[K]Z { + result := make(map[K]Z) + for k := range first { + result[k] = Z{} + } + for k := range second { + result[k] = Z{} + } + return result +} + +// UnionMany returns a set containing all elements from all the given sets. func UnionMany[S ~map[K]Z, K comparable](sets ...S) map[K]Z { result := make(map[K]Z) for _, set := range sets { @@ -40,14 +105,6 @@ func UnionMany[S ~map[K]Z, K comparable](sets ...S) map[K]Z { return result } -// Union -// Intersection -// Difference -// SymmetricDifference -// EqualMany -// Filter -// Reduce - // Add // Discard // Pop From 6c7a79bfe751ac3ac07bf83bae330bdb0d337f0a Mon Sep 17 00:00:00 2001 From: gram Date: Mon, 11 Sep 2023 13:10:45 +0200 Subject: [PATCH 04/15] add in place functions --- sets/set.go | 6 ------ sets/void.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 6 deletions(-) create mode 100644 sets/void.go diff --git a/sets/set.go b/sets/set.go index 24a2919..43ea2ec 100644 --- a/sets/set.go +++ b/sets/set.go @@ -104,9 +104,3 @@ func UnionMany[S ~map[K]Z, K comparable](sets ...S) map[K]Z { } return result } - -// Add -// Discard -// Pop -// Update -// Clear diff --git a/sets/void.go b/sets/void.go new file mode 100644 index 0000000..af995f3 --- /dev/null +++ b/sets/void.go @@ -0,0 +1,53 @@ +package sets + +// The file contains functions that don't return anything (or only return an error). + +// Add adds the given element in the set. +// +// If the element already in the set, nothing happens. +// +// The set is modified in place. +func Add[S ~map[K]Z, K comparable](set S, value K) { + set[value] = Z{} +} + +// Clear removes all elements from the set. +// +// The set is modified in place. +func Clear[S ~map[K]Z, K comparable](set S) { + for k := range set { + delete(set, k) + } +} + +// Discard removes the given element from the set. +// +// If the element already not in the set, nothing happens. +// +// The set is modified in place. +func Discard[S ~map[K]Z, K comparable](set S, value K) { + delete(set, value) +} + +// Pop removes an element from the set and returns that element. +// +// If the set is empty, [ErrEmpty] is returned. +// +// The set is modified in place. +func Pop[S ~map[K]Z, K comparable](set S) (K, error) { + for k := range set { + delete(set, k) + return k, nil + } + var k K + return k, ErrEmpty +} + +// Update adds elements from the values set into the target set. +// +// The set is modified in place. +func Update[S1, S2 ~map[K]Z, K comparable](target S1, values S2) { + for k := range values { + target[k] = Z{} + } +} From 29baeade430468221723099af284894d4a8012ad Mon Sep 17 00:00:00 2001 From: gram Date: Mon, 11 Sep 2023 13:21:57 +0200 Subject: [PATCH 05/15] package docs --- sets/doc.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 sets/doc.go diff --git a/sets/doc.go b/sets/doc.go new file mode 100644 index 0000000..b7e5d89 --- /dev/null +++ b/sets/doc.go @@ -0,0 +1,13 @@ +// Package sets provides generic functions for sets. +// +// By convention, a set in go is represented as map[T]struct{} where T +// is the set element type. It allows to ensure that each element +// of the set appears only once, and using struct{} allows to not spend any memory +// on the map values. +// +// Using maps instead of a specifically deisigned for sets data structures +// means that some operations, like [Union], aren't as fast as they potentially +// could be. However, we decided to stick to maps instead of introducing +// a new data type to avoid vendor lock on genesis +// and simplify iteration over elements. +package sets From 5a216d815a06f3d1760348c7d5cdc606548de7b8 Mon Sep 17 00:00:00 2001 From: gram Date: Mon, 11 Sep 2023 14:51:06 +0200 Subject: [PATCH 06/15] test bool set --- Taskfile.yaml | 8 +++ sets/bool_test.go | 149 ++++++++++++++++++++++++++++++++++++++++++++++ sets/set.go | 5 ++ 3 files changed, 162 insertions(+) create mode 100644 sets/bool_test.go diff --git a/Taskfile.yaml b/Taskfile.yaml index f7a6e49..b94cd06 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -9,8 +9,16 @@ tasks: test: desc: Run go tests with coverage and timeout and without cache + cmds: + - task: test:go + - task: test:py + + test:go: cmds: - go test -count 1 -cover -timeout 1s ./... + + test:py: + cmds: - python3 -m pytest ./scripts docs: diff --git a/sets/bool_test.go b/sets/bool_test.go new file mode 100644 index 0000000..13670c1 --- /dev/null +++ b/sets/bool_test.go @@ -0,0 +1,149 @@ +package sets_test + +import ( + "testing" + + "github.com/life4/genesis/sets" + "github.com/matryer/is" +) + +var ( + nilSet map[int]struct{} = nil + emptySet map[int]struct{} = map[int]struct{}{} +) + +func TestContains(t *testing.T) { + is := is.NewRelaxed(t) + is.True(sets.Contains(sets.Set(3, 4, 5), 3)) + is.True(sets.Contains(sets.Set(3, 4, 5), 4)) + is.True(sets.Contains(sets.Set(3, 4, 5), 5)) + is.True(!sets.Contains(sets.Set(3, 4, 5), 6)) + is.True(!sets.Contains(emptySet, 3)) + is.True(!sets.Contains(nilSet, 3)) +} + +func TestDisjoint(t *testing.T) { + is := is.NewRelaxed(t) + is.True(!sets.Disjoint(sets.Set(3, 4, 5), sets.Set(3, 4, 5))) + is.True(!sets.Disjoint(sets.Set(3, 4, 5), sets.Set(5, 6, 7))) + is.True(sets.Disjoint(sets.Set(3, 4, 5), sets.Set(6, 7))) + is.True(sets.Disjoint(emptySet, emptySet)) + is.True(sets.Disjoint(emptySet, sets.Set(6))) + is.True(sets.Disjoint(sets.Set(6), emptySet)) + is.True(sets.Disjoint(sets.Set(6), nilSet)) + is.True(sets.Disjoint(nilSet, sets.Set(6))) + is.True(sets.Disjoint(nilSet, nilSet)) +} + +func TestDisjointMany(t *testing.T) { + is := is.NewRelaxed(t) + + // two sets + is.True(!sets.DisjointMany(sets.Set(3, 4, 5), sets.Set(3, 4, 5))) + is.True(!sets.DisjointMany(sets.Set(3, 4, 5), sets.Set(5, 6, 7))) + is.True(sets.DisjointMany(sets.Set(3, 4, 5), sets.Set(6, 7))) + is.True(sets.DisjointMany(emptySet, emptySet)) + is.True(sets.DisjointMany(emptySet, sets.Set(6))) + is.True(sets.DisjointMany(sets.Set(6), emptySet)) + is.True(sets.DisjointMany(sets.Set(6), nilSet)) + is.True(sets.DisjointMany(nilSet, sets.Set(6))) + is.True(sets.DisjointMany(nilSet, nilSet)) + + // three and more sets + is.True(sets.DisjointMany(sets.Set(3), sets.Set(4), sets.Set(5))) + is.True(!sets.DisjointMany(sets.Set(3), sets.Set(4), sets.Set(4, 5))) + is.True(!sets.DisjointMany(sets.Set(3), sets.Set(4), sets.Set(3, 5))) + + // one and zero sets + is.True(sets.DisjointMany(nilSet)) + is.True(sets.DisjointMany(emptySet)) + is.True(sets.DisjointMany[map[int]struct{}]()) +} + +func TestEmpty(t *testing.T) { + is := is.NewRelaxed(t) + is.True(sets.Empty(nilSet)) + is.True(sets.Empty(emptySet)) + is.True(!sets.Empty(sets.Set(3))) + is.True(!sets.Empty(sets.Set(3, 4))) +} + +func TestEqual(t *testing.T) { + is := is.NewRelaxed(t) + is.True(sets.Equal(nilSet, nilSet)) + is.True(sets.Equal(emptySet, emptySet)) + is.True(sets.Equal(nilSet, emptySet)) + is.True(sets.Equal(sets.Set(3, 4, 5), sets.Set(3, 4, 5))) + is.True(!sets.Equal(sets.Set(3, 4, 5), sets.Set(3, 4))) + is.True(!sets.Equal(sets.Set(3, 4), sets.Set(3, 4, 5))) + is.True(!sets.Equal(sets.Set(3, 4, 6), sets.Set(3, 4, 5))) + is.True(!sets.Equal(sets.Set(3), emptySet)) + is.True(!sets.Equal(emptySet, sets.Set(3))) +} + +func TestEqualMany(t *testing.T) { + is := is.NewRelaxed(t) + + // two sets + is.True(sets.EqualMany(nilSet, nilSet)) + is.True(sets.EqualMany(emptySet, emptySet)) + is.True(sets.EqualMany(nilSet, emptySet)) + is.True(sets.EqualMany(sets.Set(3, 4, 5), sets.Set(3, 4, 5))) + is.True(!sets.EqualMany(sets.Set(3, 4, 5), sets.Set(3, 4))) + is.True(!sets.EqualMany(sets.Set(3, 4), sets.Set(3, 4, 5))) + is.True(!sets.EqualMany(sets.Set(3, 4, 6), sets.Set(3, 4, 5))) + is.True(!sets.EqualMany(sets.Set(3), emptySet)) + is.True(!sets.EqualMany(emptySet, sets.Set(3))) + + // one or zero sets + is.True(sets.EqualMany(emptySet)) + is.True(sets.EqualMany(nilSet)) + is.True(sets.EqualMany[map[int]struct{}]()) +} + +func TestIntersect(t *testing.T) { + is := is.NewRelaxed(t) + is.True(sets.Intersect(sets.Set(3, 4, 5), sets.Set(3, 4, 5))) + is.True(sets.Intersect(sets.Set(3, 4, 5), sets.Set(5, 6, 7))) + is.True(!sets.Intersect(sets.Set(3, 4, 5), sets.Set(6, 7))) + is.True(!sets.Intersect(emptySet, emptySet)) + is.True(!sets.Intersect(emptySet, sets.Set(6))) + is.True(!sets.Intersect(sets.Set(6), emptySet)) + is.True(!sets.Intersect(sets.Set(6), nilSet)) + is.True(!sets.Intersect(nilSet, sets.Set(6))) + is.True(!sets.Intersect(nilSet, nilSet)) +} + +func TestSubset(t *testing.T) { + is := is.NewRelaxed(t) + is.True(sets.Subset(sets.Set(3, 4, 5), sets.Set(3, 4, 5))) + is.True(sets.Subset(sets.Set(3, 4), sets.Set(3, 4, 5))) + is.True(sets.Subset(sets.Set(4), sets.Set(3, 4, 5))) + is.True(sets.Subset(emptySet, sets.Set(3, 4, 5))) + is.True(sets.Subset(emptySet, emptySet)) + is.True(sets.Subset(emptySet, nilSet)) + is.True(sets.Subset(nilSet, emptySet)) + + is.True(!sets.Subset(sets.Set(4), emptySet)) + is.True(!sets.Subset(sets.Set(4), nilSet)) + is.True(!sets.Subset(sets.Set(4), sets.Set(5, 6))) + is.True(!sets.Subset(sets.Set(4, 5), sets.Set(5, 6))) + is.True(!sets.Subset(sets.Set(4, 5, 6), sets.Set(5, 6))) +} + +func TestSuperset(t *testing.T) { + is := is.NewRelaxed(t) + is.True(sets.Superset(sets.Set(3, 4, 5), sets.Set(3, 4, 5))) + is.True(sets.Superset(sets.Set(3, 4, 5), sets.Set(3, 4))) + is.True(sets.Superset(sets.Set(3, 4, 5), sets.Set(4))) + is.True(sets.Superset(sets.Set(3, 4, 5), emptySet)) + is.True(sets.Superset(emptySet, emptySet)) + is.True(sets.Superset(nilSet, emptySet)) + is.True(sets.Superset(emptySet, nilSet)) + + is.True(!sets.Superset(emptySet, sets.Set(4))) + is.True(!sets.Superset(nilSet, sets.Set(4))) + is.True(!sets.Superset(sets.Set(5, 6), sets.Set(4))) + is.True(!sets.Superset(sets.Set(5, 6), sets.Set(4, 5))) + is.True(!sets.Superset(sets.Set(5, 6), sets.Set(4, 5, 6))) +} diff --git a/sets/set.go b/sets/set.go index 43ea2ec..126964e 100644 --- a/sets/set.go +++ b/sets/set.go @@ -64,6 +64,11 @@ func Map[S ~map[K]Z, K, R comparable](set S, f func(K) R) map[R]Z { return result } +// Set is a convenience function for constructing a set from a list of values. +func Set[K comparable](values ...K) map[K]Z { + return FromSlice(values) +} + // SymmetricDifference returns a set containing elements that appear only in one set but not both. func SymmetricDifference[S1, S2 ~map[K]Z, K comparable](first S1, second S2) map[K]Z { result := make(map[K]Z) From af7b934c37e8693281fc5d0adfeaeba7dd927d25 Mon Sep 17 00:00:00 2001 From: gram Date: Tue, 12 Sep 2023 13:09:12 +0200 Subject: [PATCH 07/15] test inplace funcs --- scripts/count_funcs.py | 2 +- scripts/test_docs.py | 10 +++- sets/bool_test.go | 120 ++++++++++++++++++++--------------------- sets/set.go | 4 +- sets/void.go | 4 ++ sets/void_test.go | 84 +++++++++++++++++++++++++++++ 6 files changed, 160 insertions(+), 64 deletions(-) create mode 100644 sets/void_test.go diff --git a/scripts/count_funcs.py b/scripts/count_funcs.py index cc4c735..26e9d44 100644 --- a/scripts/count_funcs.py +++ b/scripts/count_funcs.py @@ -26,7 +26,7 @@ def count_funcs(pkg: str) -> int: total = 0 -pkgs = ('channels', 'lambdas', 'maps', 'slices') +pkgs = ('channels', 'lambdas', 'maps', 'sets', 'slices') for pkg in pkgs: count = count_funcs(pkg) print(f'{pkg}: {count}') diff --git a/scripts/test_docs.py b/scripts/test_docs.py index ea05635..7b3849a 100644 --- a/scripts/test_docs.py +++ b/scripts/test_docs.py @@ -78,7 +78,15 @@ def test_all_have_tests(pkg: str) -> None: @pytest.mark.parametrize('func', get_funcs('slices')) def test_slices_func_linked_in_docs(func: str) -> None: - """Every function in the slices package must be listed in the package docs. + """Every func in the slices package must be listed in the package docs. """ docs = Path('slices', 'doc.go').read_text() assert f'// - [{func}](' in docs + + +@pytest.mark.parametrize('func', get_funcs('channels')) +def test_channels_func_linked_in_docs(func: str) -> None: + """Every func in the channels package must be listed in the package docs. + """ + docs = Path('channels', 'doc.go').read_text() + assert f' [{func}]' in docs diff --git a/sets/bool_test.go b/sets/bool_test.go index 13670c1..3eb6e82 100644 --- a/sets/bool_test.go +++ b/sets/bool_test.go @@ -14,24 +14,24 @@ var ( func TestContains(t *testing.T) { is := is.NewRelaxed(t) - is.True(sets.Contains(sets.Set(3, 4, 5), 3)) - is.True(sets.Contains(sets.Set(3, 4, 5), 4)) - is.True(sets.Contains(sets.Set(3, 4, 5), 5)) - is.True(!sets.Contains(sets.Set(3, 4, 5), 6)) + is.True(sets.Contains(sets.New(3, 4, 5), 3)) + is.True(sets.Contains(sets.New(3, 4, 5), 4)) + is.True(sets.Contains(sets.New(3, 4, 5), 5)) + is.True(!sets.Contains(sets.New(3, 4, 5), 6)) is.True(!sets.Contains(emptySet, 3)) is.True(!sets.Contains(nilSet, 3)) } func TestDisjoint(t *testing.T) { is := is.NewRelaxed(t) - is.True(!sets.Disjoint(sets.Set(3, 4, 5), sets.Set(3, 4, 5))) - is.True(!sets.Disjoint(sets.Set(3, 4, 5), sets.Set(5, 6, 7))) - is.True(sets.Disjoint(sets.Set(3, 4, 5), sets.Set(6, 7))) + is.True(!sets.Disjoint(sets.New(3, 4, 5), sets.New(3, 4, 5))) + is.True(!sets.Disjoint(sets.New(3, 4, 5), sets.New(5, 6, 7))) + is.True(sets.Disjoint(sets.New(3, 4, 5), sets.New(6, 7))) is.True(sets.Disjoint(emptySet, emptySet)) - is.True(sets.Disjoint(emptySet, sets.Set(6))) - is.True(sets.Disjoint(sets.Set(6), emptySet)) - is.True(sets.Disjoint(sets.Set(6), nilSet)) - is.True(sets.Disjoint(nilSet, sets.Set(6))) + is.True(sets.Disjoint(emptySet, sets.New(6))) + is.True(sets.Disjoint(sets.New(6), emptySet)) + is.True(sets.Disjoint(sets.New(6), nilSet)) + is.True(sets.Disjoint(nilSet, sets.New(6))) is.True(sets.Disjoint(nilSet, nilSet)) } @@ -39,20 +39,20 @@ func TestDisjointMany(t *testing.T) { is := is.NewRelaxed(t) // two sets - is.True(!sets.DisjointMany(sets.Set(3, 4, 5), sets.Set(3, 4, 5))) - is.True(!sets.DisjointMany(sets.Set(3, 4, 5), sets.Set(5, 6, 7))) - is.True(sets.DisjointMany(sets.Set(3, 4, 5), sets.Set(6, 7))) + is.True(!sets.DisjointMany(sets.New(3, 4, 5), sets.New(3, 4, 5))) + is.True(!sets.DisjointMany(sets.New(3, 4, 5), sets.New(5, 6, 7))) + is.True(sets.DisjointMany(sets.New(3, 4, 5), sets.New(6, 7))) is.True(sets.DisjointMany(emptySet, emptySet)) - is.True(sets.DisjointMany(emptySet, sets.Set(6))) - is.True(sets.DisjointMany(sets.Set(6), emptySet)) - is.True(sets.DisjointMany(sets.Set(6), nilSet)) - is.True(sets.DisjointMany(nilSet, sets.Set(6))) + is.True(sets.DisjointMany(emptySet, sets.New(6))) + is.True(sets.DisjointMany(sets.New(6), emptySet)) + is.True(sets.DisjointMany(sets.New(6), nilSet)) + is.True(sets.DisjointMany(nilSet, sets.New(6))) is.True(sets.DisjointMany(nilSet, nilSet)) // three and more sets - is.True(sets.DisjointMany(sets.Set(3), sets.Set(4), sets.Set(5))) - is.True(!sets.DisjointMany(sets.Set(3), sets.Set(4), sets.Set(4, 5))) - is.True(!sets.DisjointMany(sets.Set(3), sets.Set(4), sets.Set(3, 5))) + is.True(sets.DisjointMany(sets.New(3), sets.New(4), sets.New(5))) + is.True(!sets.DisjointMany(sets.New(3), sets.New(4), sets.New(4, 5))) + is.True(!sets.DisjointMany(sets.New(3), sets.New(4), sets.New(3, 5))) // one and zero sets is.True(sets.DisjointMany(nilSet)) @@ -64,8 +64,8 @@ func TestEmpty(t *testing.T) { is := is.NewRelaxed(t) is.True(sets.Empty(nilSet)) is.True(sets.Empty(emptySet)) - is.True(!sets.Empty(sets.Set(3))) - is.True(!sets.Empty(sets.Set(3, 4))) + is.True(!sets.Empty(sets.New(3))) + is.True(!sets.Empty(sets.New(3, 4))) } func TestEqual(t *testing.T) { @@ -73,12 +73,12 @@ func TestEqual(t *testing.T) { is.True(sets.Equal(nilSet, nilSet)) is.True(sets.Equal(emptySet, emptySet)) is.True(sets.Equal(nilSet, emptySet)) - is.True(sets.Equal(sets.Set(3, 4, 5), sets.Set(3, 4, 5))) - is.True(!sets.Equal(sets.Set(3, 4, 5), sets.Set(3, 4))) - is.True(!sets.Equal(sets.Set(3, 4), sets.Set(3, 4, 5))) - is.True(!sets.Equal(sets.Set(3, 4, 6), sets.Set(3, 4, 5))) - is.True(!sets.Equal(sets.Set(3), emptySet)) - is.True(!sets.Equal(emptySet, sets.Set(3))) + is.True(sets.Equal(sets.New(3, 4, 5), sets.New(3, 4, 5))) + is.True(!sets.Equal(sets.New(3, 4, 5), sets.New(3, 4))) + is.True(!sets.Equal(sets.New(3, 4), sets.New(3, 4, 5))) + is.True(!sets.Equal(sets.New(3, 4, 6), sets.New(3, 4, 5))) + is.True(!sets.Equal(sets.New(3), emptySet)) + is.True(!sets.Equal(emptySet, sets.New(3))) } func TestEqualMany(t *testing.T) { @@ -88,12 +88,12 @@ func TestEqualMany(t *testing.T) { is.True(sets.EqualMany(nilSet, nilSet)) is.True(sets.EqualMany(emptySet, emptySet)) is.True(sets.EqualMany(nilSet, emptySet)) - is.True(sets.EqualMany(sets.Set(3, 4, 5), sets.Set(3, 4, 5))) - is.True(!sets.EqualMany(sets.Set(3, 4, 5), sets.Set(3, 4))) - is.True(!sets.EqualMany(sets.Set(3, 4), sets.Set(3, 4, 5))) - is.True(!sets.EqualMany(sets.Set(3, 4, 6), sets.Set(3, 4, 5))) - is.True(!sets.EqualMany(sets.Set(3), emptySet)) - is.True(!sets.EqualMany(emptySet, sets.Set(3))) + is.True(sets.EqualMany(sets.New(3, 4, 5), sets.New(3, 4, 5))) + is.True(!sets.EqualMany(sets.New(3, 4, 5), sets.New(3, 4))) + is.True(!sets.EqualMany(sets.New(3, 4), sets.New(3, 4, 5))) + is.True(!sets.EqualMany(sets.New(3, 4, 6), sets.New(3, 4, 5))) + is.True(!sets.EqualMany(sets.New(3), emptySet)) + is.True(!sets.EqualMany(emptySet, sets.New(3))) // one or zero sets is.True(sets.EqualMany(emptySet)) @@ -103,47 +103,47 @@ func TestEqualMany(t *testing.T) { func TestIntersect(t *testing.T) { is := is.NewRelaxed(t) - is.True(sets.Intersect(sets.Set(3, 4, 5), sets.Set(3, 4, 5))) - is.True(sets.Intersect(sets.Set(3, 4, 5), sets.Set(5, 6, 7))) - is.True(!sets.Intersect(sets.Set(3, 4, 5), sets.Set(6, 7))) + is.True(sets.Intersect(sets.New(3, 4, 5), sets.New(3, 4, 5))) + is.True(sets.Intersect(sets.New(3, 4, 5), sets.New(5, 6, 7))) + is.True(!sets.Intersect(sets.New(3, 4, 5), sets.New(6, 7))) is.True(!sets.Intersect(emptySet, emptySet)) - is.True(!sets.Intersect(emptySet, sets.Set(6))) - is.True(!sets.Intersect(sets.Set(6), emptySet)) - is.True(!sets.Intersect(sets.Set(6), nilSet)) - is.True(!sets.Intersect(nilSet, sets.Set(6))) + is.True(!sets.Intersect(emptySet, sets.New(6))) + is.True(!sets.Intersect(sets.New(6), emptySet)) + is.True(!sets.Intersect(sets.New(6), nilSet)) + is.True(!sets.Intersect(nilSet, sets.New(6))) is.True(!sets.Intersect(nilSet, nilSet)) } func TestSubset(t *testing.T) { is := is.NewRelaxed(t) - is.True(sets.Subset(sets.Set(3, 4, 5), sets.Set(3, 4, 5))) - is.True(sets.Subset(sets.Set(3, 4), sets.Set(3, 4, 5))) - is.True(sets.Subset(sets.Set(4), sets.Set(3, 4, 5))) - is.True(sets.Subset(emptySet, sets.Set(3, 4, 5))) + is.True(sets.Subset(sets.New(3, 4, 5), sets.New(3, 4, 5))) + is.True(sets.Subset(sets.New(3, 4), sets.New(3, 4, 5))) + is.True(sets.Subset(sets.New(4), sets.New(3, 4, 5))) + is.True(sets.Subset(emptySet, sets.New(3, 4, 5))) is.True(sets.Subset(emptySet, emptySet)) is.True(sets.Subset(emptySet, nilSet)) is.True(sets.Subset(nilSet, emptySet)) - is.True(!sets.Subset(sets.Set(4), emptySet)) - is.True(!sets.Subset(sets.Set(4), nilSet)) - is.True(!sets.Subset(sets.Set(4), sets.Set(5, 6))) - is.True(!sets.Subset(sets.Set(4, 5), sets.Set(5, 6))) - is.True(!sets.Subset(sets.Set(4, 5, 6), sets.Set(5, 6))) + is.True(!sets.Subset(sets.New(4), emptySet)) + is.True(!sets.Subset(sets.New(4), nilSet)) + is.True(!sets.Subset(sets.New(4), sets.New(5, 6))) + is.True(!sets.Subset(sets.New(4, 5), sets.New(5, 6))) + is.True(!sets.Subset(sets.New(4, 5, 6), sets.New(5, 6))) } func TestSuperset(t *testing.T) { is := is.NewRelaxed(t) - is.True(sets.Superset(sets.Set(3, 4, 5), sets.Set(3, 4, 5))) - is.True(sets.Superset(sets.Set(3, 4, 5), sets.Set(3, 4))) - is.True(sets.Superset(sets.Set(3, 4, 5), sets.Set(4))) - is.True(sets.Superset(sets.Set(3, 4, 5), emptySet)) + is.True(sets.Superset(sets.New(3, 4, 5), sets.New(3, 4, 5))) + is.True(sets.Superset(sets.New(3, 4, 5), sets.New(3, 4))) + is.True(sets.Superset(sets.New(3, 4, 5), sets.New(4))) + is.True(sets.Superset(sets.New(3, 4, 5), emptySet)) is.True(sets.Superset(emptySet, emptySet)) is.True(sets.Superset(nilSet, emptySet)) is.True(sets.Superset(emptySet, nilSet)) - is.True(!sets.Superset(emptySet, sets.Set(4))) - is.True(!sets.Superset(nilSet, sets.Set(4))) - is.True(!sets.Superset(sets.Set(5, 6), sets.Set(4))) - is.True(!sets.Superset(sets.Set(5, 6), sets.Set(4, 5))) - is.True(!sets.Superset(sets.Set(5, 6), sets.Set(4, 5, 6))) + is.True(!sets.Superset(emptySet, sets.New(4))) + is.True(!sets.Superset(nilSet, sets.New(4))) + is.True(!sets.Superset(sets.New(5, 6), sets.New(4))) + is.True(!sets.Superset(sets.New(5, 6), sets.New(4, 5))) + is.True(!sets.Superset(sets.New(5, 6), sets.New(4, 5, 6))) } diff --git a/sets/set.go b/sets/set.go index 126964e..ef6f226 100644 --- a/sets/set.go +++ b/sets/set.go @@ -64,8 +64,8 @@ func Map[S ~map[K]Z, K, R comparable](set S, f func(K) R) map[R]Z { return result } -// Set is a convenience function for constructing a set from a list of values. -func Set[K comparable](values ...K) map[K]Z { +// New is a convenience function for constructing a set from a list of values. +func New[K comparable](values ...K) map[K]Z { return FromSlice(values) } diff --git a/sets/void.go b/sets/void.go index af995f3..9004274 100644 --- a/sets/void.go +++ b/sets/void.go @@ -6,6 +6,8 @@ package sets // // If the element already in the set, nothing happens. // +// If the set is nil, the function will panic. +// // The set is modified in place. func Add[S ~map[K]Z, K comparable](set S, value K) { set[value] = Z{} @@ -45,6 +47,8 @@ func Pop[S ~map[K]Z, K comparable](set S) (K, error) { // Update adds elements from the values set into the target set. // +// If the target set is nil and the values set is not empty, the function will panic. +// // The set is modified in place. func Update[S1, S2 ~map[K]Z, K comparable](target S1, values S2) { for k := range values { diff --git a/sets/void_test.go b/sets/void_test.go new file mode 100644 index 0000000..b0817fc --- /dev/null +++ b/sets/void_test.go @@ -0,0 +1,84 @@ +package sets_test + +import ( + "testing" + + "github.com/life4/genesis/sets" + "github.com/matryer/is" +) + +func TestAdd(t *testing.T) { + is := is.NewRelaxed(t) + + f := func(given map[int]sets.Z, val int, exp map[int]sets.Z) { + sets.Add(given, val) + is.Equal(given, exp) + } + f(sets.New(3, 4, 5), 3, sets.New(3, 4, 5)) + f(sets.New(3, 4, 5), 6, sets.New(3, 4, 5, 6)) + f(sets.New[int](), 6, sets.New(6)) + // f(nilSet, 6, sets.Set(6)) +} + +func TestClear(t *testing.T) { + is := is.NewRelaxed(t) + s := sets.New(3, 4, 5) + sets.Clear(s) + is.Equal(s, sets.New[int]()) + + s = sets.New[int]() + sets.Clear(s) + is.Equal(s, sets.New[int]()) + + s = nil + sets.Clear(s) + is.Equal(s, nil) +} + +func TestDiscard(t *testing.T) { + is := is.NewRelaxed(t) + + f := func(given map[int]sets.Z, val int, exp map[int]sets.Z) { + sets.Discard(given, val) + is.Equal(given, exp) + } + f(sets.New(3, 4, 5), 3, sets.New(4, 5)) + f(sets.New(3, 4, 5), 6, sets.New(3, 4, 5)) + f(sets.New[int](), 6, sets.New[int]()) + f(nilSet, 6, nilSet) +} + +func TestPop(t *testing.T) { + is := is.NewRelaxed(t) + s := sets.New(3) + val, err := sets.Pop(s) + is.NoErr(err) + is.Equal(val, 3) + + val, err = sets.Pop(s) + is.Equal(err, sets.ErrEmpty) + is.Equal(val, 0) + + s = nil + val, err = sets.Pop(s) + is.Equal(err, sets.ErrEmpty) + is.Equal(val, 0) +} + +func TestUpdate(t *testing.T) { + is := is.NewRelaxed(t) + + f := func(target, values, exp map[int]sets.Z) { + sets.Update(target, values) + is.Equal(target, exp) + } + f(sets.New(3, 4, 5), sets.New(4, 5, 6), sets.New(3, 4, 5, 6)) + f(sets.New(3, 4, 5), sets.New(4), sets.New(3, 4, 5)) + f(sets.New[int](), sets.New(4), sets.New(4)) + f(sets.New[int](), sets.New[int](), sets.New[int]()) + f(sets.New[int](4), sets.New[int](), sets.New(4)) + f(nil, sets.New[int](), nil) + f(nil, nil, nil) + f(sets.New[int](4), nil, sets.New[int](4)) + // f(nil, sets.New[int](4), nil) +} From 2626c159426c20b8a84d834e715e4079de6a51f1 Mon Sep 17 00:00:00 2001 From: gram Date: Sun, 17 Sep 2023 14:24:49 +0200 Subject: [PATCH 08/15] test set functions returning set --- sets/set_test.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++ sets/void_test.go | 7 ++-- 2 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 sets/set_test.go diff --git a/sets/set_test.go b/sets/set_test.go new file mode 100644 index 0000000..21021c3 --- /dev/null +++ b/sets/set_test.go @@ -0,0 +1,85 @@ +package sets_test + +import ( + "testing" + + "github.com/life4/genesis/sets" + "github.com/matryer/is" +) + +func TestCopy(t *testing.T) { + is := is.New(t) + a := sets.New(4, 5) + b := sets.Copy(a) + a[6] = struct{}{} + b[7] = struct{}{} + is.Equal(a, sets.New(4, 5, 6)) + is.Equal(b, sets.New(4, 5, 7)) +} + +func TestDifference(t *testing.T) { + is := is.NewRelaxed(t) + is.Equal(sets.Difference(sets.New(3, 4, 5), sets.New(3, 4, 5)), sets.New[int]()) + is.Equal(sets.Difference(sets.New(3, 4, 5), sets.New(3)), sets.New[int](4, 5)) + is.Equal(sets.Difference(sets.New(3, 4, 5), sets.New(3, 6, 7)), sets.New[int](4, 5)) + is.Equal(sets.Difference(sets.New(3), sets.New(3, 6, 7)), sets.New[int]()) + is.Equal(sets.Difference(sets.New[int](), sets.New(3, 6, 7)), sets.New[int]()) + is.Equal(sets.Difference(nilSet, sets.New(3, 6, 7)), sets.New[int]()) +} + +func TestFilter(t *testing.T) { + is := is.NewRelaxed(t) + even := func(x int) bool { return x%2 == 0 } + is.Equal(sets.Filter(sets.New(3, 5, 7), even), sets.New[int]()) + is.Equal(sets.Filter(sets.New(3, 4, 5), even), sets.New(4)) + is.Equal(sets.Filter(sets.New(3, 4, 5, 6), even), sets.New(4, 6)) + is.Equal(sets.Filter(sets.New(3, 4, 5, 6, 4, 4), even), sets.New(4, 6, 4, 4)) +} + +func TestFromSlice(t *testing.T) { + is := is.NewRelaxed(t) + var nilSlice []int = nil + is.Equal(sets.FromSlice(nilSlice), sets.New[int]()) + is.Equal(sets.FromSlice([]int{}), sets.New[int]()) + is.Equal(sets.FromSlice([]int{4, 5, 6}), sets.New(4, 5, 6)) + is.Equal(sets.FromSlice([]int{6, 4, 5, 6, 6}), sets.New(4, 5, 6)) +} + +func TestIntersection(t *testing.T) { + is := is.NewRelaxed(t) + is.Equal(sets.Intersection(sets.New(3, 4, 5), sets.New(6, 7, 8)), sets.New[int]()) + is.Equal(sets.Intersection(sets.New(3, 4, 5), sets.New(3, 4, 5)), sets.New[int](3, 4, 5)) + is.Equal(sets.Intersection(sets.New(2, 3, 4), sets.New(3, 4, 5)), sets.New[int](3, 4)) + is.Equal(sets.Intersection(sets.New[int](), sets.New(3, 6, 7)), sets.New[int]()) + is.Equal(sets.Intersection(nilSet, sets.New(3, 6, 7)), sets.New[int]()) +} + +func TestMap(t *testing.T) { + is := is.NewRelaxed(t) + double := func(x int) int { return x * 2 } + is.Equal(sets.Map(sets.New(3, 4, 5), double), sets.New(6, 8, 10)) + is.Equal(sets.Map(sets.New[int](), double), sets.New[int]()) + is.Equal(sets.Map(nilSet, double), sets.New[int]()) +} + +func TestSymmetricDifference(t *testing.T) { + is := is.NewRelaxed(t) + is.Equal(sets.SymmetricDifference(sets.New(3, 4, 5), sets.New(3, 4, 5)), sets.New[int]()) + is.Equal(sets.SymmetricDifference(sets.New(3, 4, 5), sets.New(3)), sets.New[int](4, 5)) + is.Equal(sets.SymmetricDifference(sets.New(3, 4, 5), sets.New(3, 6, 7)), sets.New[int](4, 5, 6, 7)) + is.Equal(sets.SymmetricDifference(sets.New(3), sets.New(3, 6, 7)), sets.New[int](6, 7)) + is.Equal(sets.SymmetricDifference(sets.New[int](), sets.New(3, 6, 7)), sets.New(3, 6, 7)) + is.Equal(sets.SymmetricDifference(nilSet, sets.New(3, 6, 7)), sets.New(3, 6, 7)) +} + +func TestUnion(t *testing.T) { + is := is.NewRelaxed(t) + is.Equal(sets.Union(sets.New(3, 4, 5), sets.New(3, 4, 5)), sets.New(3, 4, 5)) + is.Equal(sets.Union(sets.New(3, 4, 5), sets.New(3)), sets.New[int](3, 4, 5)) + is.Equal(sets.Union(sets.New(3, 4, 5), sets.New(3, 6, 7)), sets.New[int](3, 4, 5, 6, 7)) + is.Equal(sets.Union(sets.New(3), sets.New(3, 6, 7)), sets.New[int](3, 6, 7)) + is.Equal(sets.Union(sets.New(3, 4), sets.New(6, 7)), sets.New[int](3, 4, 6, 7)) + is.Equal(sets.Union(sets.New[int](), sets.New(3, 6, 7)), sets.New(3, 6, 7)) + is.Equal(sets.Union(sets.New[int](), sets.New[int]()), sets.New[int]()) + is.Equal(sets.Union(nilSet, sets.New(3, 6, 7)), sets.New[int](3, 6, 7)) +} diff --git a/sets/void_test.go b/sets/void_test.go index b0817fc..9b8d047 100644 --- a/sets/void_test.go +++ b/sets/void_test.go @@ -9,7 +9,6 @@ import ( func TestAdd(t *testing.T) { is := is.NewRelaxed(t) - f := func(given map[int]sets.Z, val int, exp map[int]sets.Z) { sets.Add(given, val) is.Equal(given, exp) @@ -21,7 +20,7 @@ func TestAdd(t *testing.T) { } func TestClear(t *testing.T) { - is := is.NewRelaxed(t) + is := is.New(t) s := sets.New(3, 4, 5) sets.Clear(s) is.Equal(s, sets.New[int]()) @@ -37,7 +36,6 @@ func TestClear(t *testing.T) { func TestDiscard(t *testing.T) { is := is.NewRelaxed(t) - f := func(given map[int]sets.Z, val int, exp map[int]sets.Z) { sets.Discard(given, val) is.Equal(given, exp) @@ -49,7 +47,7 @@ func TestDiscard(t *testing.T) { } func TestPop(t *testing.T) { - is := is.NewRelaxed(t) + is := is.New(t) s := sets.New(3) val, err := sets.Pop(s) is.NoErr(err) @@ -67,7 +65,6 @@ func TestPop(t *testing.T) { func TestUpdate(t *testing.T) { is := is.NewRelaxed(t) - f := func(target, values, exp map[int]sets.Z) { sets.Update(target, values) is.Equal(target, exp) From 1e81bfe31273f9cb68561d04b5c09e70a82ef1c1 Mon Sep 17 00:00:00 2001 From: gram Date: Sun, 17 Sep 2023 14:41:43 +0200 Subject: [PATCH 09/15] test rest --- sets/rest.go | 3 ++ sets/rest_test.go | 74 +++++++++++++++++++++++++++++++++++++++++++++++ sets/set.go | 9 ++++++ sets/set_test.go | 19 ++++++++++++ 4 files changed, 105 insertions(+) create mode 100644 sets/rest_test.go diff --git a/sets/rest.go b/sets/rest.go index 9e33064..1d8c0c2 100644 --- a/sets/rest.go +++ b/sets/rest.go @@ -70,6 +70,9 @@ func Sum[S ~map[K]Z, K c.Integer | c.Float](set S) K { // // The order of elements in the resulting set is semi-random. func ToSlice[S ~map[K]Z, K comparable](set S) []K { + if set == nil { + return nil + } result := make([]K, 0, len(set)) for k := range set { result = append(result, k) diff --git a/sets/rest_test.go b/sets/rest_test.go new file mode 100644 index 0000000..02a39d8 --- /dev/null +++ b/sets/rest_test.go @@ -0,0 +1,74 @@ +package sets_test + +import ( + "testing" + + "github.com/life4/genesis/sets" + "github.com/matryer/is" +) + +func TestMax(t *testing.T) { + is := is.NewRelaxed(t) + + act, err := sets.Max(sets.New(3, 4, 5)) + is.NoErr(err) + is.Equal(act, 5) + + act, err = sets.Max(sets.New(-3, -4, -5)) + is.NoErr(err) + is.Equal(act, -3) + + act, err = sets.Max(sets.New[int]()) + is.Equal(err, sets.ErrEmpty) + is.Equal(act, 0) + + act, err = sets.Max(nilSet) + is.Equal(err, sets.ErrEmpty) + is.Equal(act, 0) +} + +func TestMin(t *testing.T) { + is := is.NewRelaxed(t) + + act, err := sets.Min(sets.New(3, 4, 5)) + is.NoErr(err) + is.Equal(act, 3) + + act, err = sets.Min(sets.New(-3, -4, -5)) + is.NoErr(err) + is.Equal(act, -5) + + act, err = sets.Min(sets.New[int]()) + is.Equal(err, sets.ErrEmpty) + is.Equal(act, 0) + + act, err = sets.Min(nilSet) + is.Equal(err, sets.ErrEmpty) + is.Equal(act, 0) +} + +func TestReduce(t *testing.T) { + is := is.NewRelaxed(t) + add := func(x, y int) int { return x + y } + is.Equal(sets.Reduce(sets.New(3, 4, 5), 0, add), 12) + is.Equal(sets.Reduce(sets.New(3, 4, 5), -10, add), 2) + is.Equal(sets.Reduce(sets.New(-3, -4, -5), 0, add), -12) + is.Equal(sets.Reduce(sets.New[int](), 0, add), 0) + is.Equal(sets.Reduce(nilSet, 0, add), 0) +} + +func TestSum(t *testing.T) { + is := is.NewRelaxed(t) + is.Equal(sets.Sum(sets.New(3, 4, 5)), 12) + is.Equal(sets.Sum(sets.New(-3, -4, -5)), -12) + is.Equal(sets.Sum(sets.New[int]()), 0) + is.Equal(sets.Sum(nilSet), 0) +} + +func TestToSlice(t *testing.T) { + is := is.NewRelaxed(t) + s := sets.ToSlice(sets.New(3, 4, 5)) + is.Equal(sets.FromSlice(s), sets.New(3, 4, 5)) + is.Equal(sets.ToSlice(sets.New[int]()), []int{}) + is.Equal(sets.ToSlice(nilSet), nil) +} diff --git a/sets/set.go b/sets/set.go index ef6f226..e628d36 100644 --- a/sets/set.go +++ b/sets/set.go @@ -4,6 +4,9 @@ package sets // Copy returns a shallow copy of the given set. func Copy[S ~map[K]Z, K comparable](set S) S { + if set == nil { + return nil + } result := make(S) for k := range set { result[k] = Z{} @@ -25,6 +28,9 @@ func Difference[S1, S2 ~map[K]Z, K comparable](first S1, second S2) map[K]Z { // Filter returns elements of the set for which the given function returns true. func Filter[S ~map[K]Z, K comparable](set S, f func(K) bool) S { + if set == nil { + return nil + } result := make(S) for k := range set { if f(k) { @@ -36,6 +42,9 @@ func Filter[S ~map[K]Z, K comparable](set S, f func(K) bool) S { // FromSlice converts a slice into a set. func FromSlice[S ~[]K, K comparable](items S) map[K]Z { + // if items == nil { + // return nil + // } set := make(map[K]Z) for _, item := range items { set[item] = Z{} diff --git a/sets/set_test.go b/sets/set_test.go index 21021c3..6cc9404 100644 --- a/sets/set_test.go +++ b/sets/set_test.go @@ -15,6 +15,8 @@ func TestCopy(t *testing.T) { b[7] = struct{}{} is.Equal(a, sets.New(4, 5, 6)) is.Equal(b, sets.New(4, 5, 7)) + + is.Equal(sets.Copy(nilSet), nil) } func TestDifference(t *testing.T) { @@ -34,6 +36,7 @@ func TestFilter(t *testing.T) { is.Equal(sets.Filter(sets.New(3, 4, 5), even), sets.New(4)) is.Equal(sets.Filter(sets.New(3, 4, 5, 6), even), sets.New(4, 6)) is.Equal(sets.Filter(sets.New(3, 4, 5, 6, 4, 4), even), sets.New(4, 6, 4, 4)) + is.Equal(sets.Filter(nilSet, even), nilSet) } func TestFromSlice(t *testing.T) { @@ -83,3 +86,19 @@ func TestUnion(t *testing.T) { is.Equal(sets.Union(sets.New[int](), sets.New[int]()), sets.New[int]()) is.Equal(sets.Union(nilSet, sets.New(3, 6, 7)), sets.New[int](3, 6, 7)) } + +func TestUnionMany(t *testing.T) { + is := is.NewRelaxed(t) + + is.Equal(sets.UnionMany[map[int]struct{}](), sets.New[int]()) + is.Equal(sets.UnionMany(sets.New(3, 4, 5)), sets.New(3, 4, 5)) + + is.Equal(sets.UnionMany(sets.New(3, 4, 5), sets.New(3, 4, 5)), sets.New(3, 4, 5)) + is.Equal(sets.UnionMany(sets.New(3, 4, 5), sets.New(3)), sets.New[int](3, 4, 5)) + is.Equal(sets.UnionMany(sets.New(3, 4, 5), sets.New(3, 6, 7)), sets.New[int](3, 4, 5, 6, 7)) + is.Equal(sets.UnionMany(sets.New(3), sets.New(3, 6, 7)), sets.New[int](3, 6, 7)) + is.Equal(sets.UnionMany(sets.New(3, 4), sets.New(6, 7)), sets.New[int](3, 4, 6, 7)) + is.Equal(sets.UnionMany(sets.New[int](), sets.New(3, 6, 7)), sets.New(3, 6, 7)) + is.Equal(sets.UnionMany(sets.New[int](), sets.New[int]()), sets.New[int]()) + is.Equal(sets.UnionMany(nilSet, sets.New(3, 6, 7)), sets.New[int](3, 6, 7)) +} From d5ffd47ab38c69855653a17842a726d84679c2a7 Mon Sep 17 00:00:00 2001 From: gram Date: Sun, 17 Sep 2023 14:55:31 +0200 Subject: [PATCH 10/15] examples for bool returning sets --- sets/example_test.go | 79 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 sets/example_test.go diff --git a/sets/example_test.go b/sets/example_test.go new file mode 100644 index 0000000..2c01399 --- /dev/null +++ b/sets/example_test.go @@ -0,0 +1,79 @@ +package sets_test + +import ( + "fmt" + + "github.com/life4/genesis/sets" +) + +func ExampleContains() { + s := sets.New(3, 4, 5) + result := sets.Contains(s, 4) + fmt.Println(result) + // Output: true +} + +func ExampleDisjoint() { + a := sets.New(3, 4, 5) + b := sets.New(5, 6, 7) + result := sets.Disjoint(a, b) + fmt.Println(result) // a and b both share 5 + // Output: false +} + +func ExampleDisjointMany() { + a := sets.New(3, 4) + b := sets.New(5, 6) + c := sets.New(6, 7) + result := sets.DisjointMany(a, b, c) + fmt.Println(result) // b and c both share 6 + // Output: false +} + +func ExampleEmpty() { + s := sets.New[int]() + result := sets.Empty(s) + fmt.Println(result) + // Output: true +} + +func ExampleEqual() { + a := sets.New(3, 4, 5) + b := sets.New(3, 4, 6) + result := sets.Equal(a, b) + fmt.Println(result) + // Output: false +} + +func ExampleEqualMany() { + a := sets.New(3, 4, 5) + b := sets.New(3, 4, 5) + c := sets.New(3, 4, 6) + result := sets.EqualMany(a, b, c) + fmt.Println(result) + // Output: false +} + +func ExampleIntersect() { + a := sets.New(3, 4, 5) + b := sets.New(5, 6, 7) + result := sets.Intersect(a, b) + fmt.Println(result) + // Output: true +} + +func ExampleSubset() { + a := sets.New(4, 5) + b := sets.New(3, 4, 5, 6) + result := sets.Subset(a, b) + fmt.Println(result) + // Output: true +} + +func ExampleSuperset() { + a := sets.New(3, 4, 5, 6) + b := sets.New(4, 5) + result := sets.Superset(a, b) + fmt.Println(result) + // Output: true +} From df4f8e32e62014b82f34e557dc33307c67b9cf4a Mon Sep 17 00:00:00 2001 From: gram Date: Tue, 19 Sep 2023 09:19:21 +0200 Subject: [PATCH 11/15] add examples for rest --- .gitignore | 1 + README.md | 7 ++++--- doc.go | 1 + sets/example_test.go | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index cf4c27e..c73efb3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ __pycache__/ .vscode .swp *~ +.task diff --git a/README.md b/README.md index 23f4bae..75168cd 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,10 @@ codes := slices.MapAsync( Genesis contains the following packages: -+ [🍞 slices](https://pkg.go.dev/github.com/life4/genesis/slices): generic functions for slices. -+ [🗺 maps](https://pkg.go.dev/github.com/life4/genesis/maps): generic functions for maps. -+ [📺 channels](https://pkg.go.dev/github.com/life4/genesis/channels): generic function for channels. ++ [🍞 slices](https://pkg.go.dev/github.com/life4/genesis/slices): generic functions for slices (`[]T`). ++ [🗺 maps](https://pkg.go.dev/github.com/life4/genesis/maps): generic functions for maps (`map[K]V`). ++ [📺 channels](https://pkg.go.dev/github.com/life4/genesis/channels): generic function for channels (`chan T`). ++ [⚙️ sets](https://pkg.go.dev/github.com/life4/genesis/sets): generic function for sets (`map[T]struct{}`). + [🛟 lambdas](https://pkg.go.dev/github.com/life4/genesis/lambdas): helper generic functions to work with `slices.Map` and similar. See [📄 DOCUMENTATION](https://pkg.go.dev/github.com/life4/genesis) for more info. diff --git a/doc.go b/doc.go index 862576b..b60f0a0 100644 --- a/doc.go +++ b/doc.go @@ -6,5 +6,6 @@ import ( _ "github.com/life4/genesis/channels" _ "github.com/life4/genesis/lambdas" _ "github.com/life4/genesis/maps" + _ "github.com/life4/genesis/sets" _ "github.com/life4/genesis/slices" ) diff --git a/sets/example_test.go b/sets/example_test.go index 2c01399..31722ea 100644 --- a/sets/example_test.go +++ b/sets/example_test.go @@ -62,6 +62,28 @@ func ExampleIntersect() { // Output: true } +func ExampleMax() { + s := sets.New(3, 6, 4, 5) + result, _ := sets.Max(s) + fmt.Println(result) + // Output: 6 +} + +func ExampleMin() { + s := sets.New(4, 5, 3, 6) + result, _ := sets.Min(s) + fmt.Println(result) + // Output: 3 +} + +func ExampleReduce() { + s := sets.New(3, 4, 5) + add := func(x, acc int) int { return x + acc } + result := sets.Reduce(s, 0, add) + fmt.Println(result) + // Output: 12 +} + func ExampleSubset() { a := sets.New(4, 5) b := sets.New(3, 4, 5, 6) @@ -70,6 +92,20 @@ func ExampleSubset() { // Output: true } +func ExampleSum() { + s := sets.New(3, 4, 5) + result := sets.Sum(s) + fmt.Println(result) + // Output: 12 +} + +func ExampleToSlice() { + s := sets.New(3) + result := sets.ToSlice(s) + fmt.Println(result) + // Output: [3] +} + func ExampleSuperset() { a := sets.New(3, 4, 5, 6) b := sets.New(4, 5) From 4430217b63e8751b5e78056bd25d9a8e881b4192 Mon Sep 17 00:00:00 2001 From: gram Date: Tue, 19 Sep 2023 09:22:53 +0200 Subject: [PATCH 12/15] emojis for packages --- channels/doc.go | 2 +- constraints/constraints.go | 2 +- lambdas/doc.go | 2 +- maps/doc.go | 2 +- sets/README.md | 3 +++ sets/doc.go | 2 +- slices/doc.go | 2 +- 7 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 sets/README.md diff --git a/channels/doc.go b/channels/doc.go index 4dcf720..64d840e 100644 --- a/channels/doc.go +++ b/channels/doc.go @@ -1,4 +1,4 @@ -// Package channels provides generic functions for channels. +// 📺 Package channels provides generic functions for channels. // // # ☎️ Naming // diff --git a/constraints/constraints.go b/constraints/constraints.go index 8521604..4abd65f 100644 --- a/constraints/constraints.go +++ b/constraints/constraints.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package constraints defines a set of useful constraints to be used +// 🙅 Package constraints defines a set of useful constraints to be used // with type parameters. package constraints diff --git a/lambdas/doc.go b/lambdas/doc.go index 710df3d..aca9e81 100644 --- a/lambdas/doc.go +++ b/lambdas/doc.go @@ -1,4 +1,4 @@ -// Package lambdas provides helper generic functions. +// 🛟 Package lambdas provides helper generic functions. // // These functions are especially helpful in combination with other `genesis` packages. package lambdas diff --git a/maps/doc.go b/maps/doc.go index f33f42b..4b1e8ea 100644 --- a/maps/doc.go +++ b/maps/doc.go @@ -1,4 +1,4 @@ -// Package maps provides generic functions for maps. +// 🗺 Package maps provides generic functions for maps. // // The package is inspired by the `Map` Elixir module. package maps diff --git a/sets/README.md b/sets/README.md new file mode 100644 index 0000000..f1ade5a --- /dev/null +++ b/sets/README.md @@ -0,0 +1,3 @@ +# genesis/sets + +Package sets provides generic functions for sets. diff --git a/sets/doc.go b/sets/doc.go index b7e5d89..7e7ac4a 100644 --- a/sets/doc.go +++ b/sets/doc.go @@ -1,4 +1,4 @@ -// Package sets provides generic functions for sets. +// ⚙️ Package sets provides generic functions for sets. // // By convention, a set in go is represented as map[T]struct{} where T // is the set element type. It allows to ensure that each element diff --git a/slices/doc.go b/slices/doc.go index a01b503..8802605 100644 --- a/slices/doc.go +++ b/slices/doc.go @@ -1,4 +1,4 @@ -// Package slices provides generic functions for slices. +// 🍞 Package slices provides generic functions for slices. // // The package is inspired by [Enum] and [List] Elixir modules. // From 3be5438ae5e3846e9f0ad6258a4d8b1d74aebd6f Mon Sep 17 00:00:00 2001 From: gram Date: Tue, 19 Sep 2023 09:41:59 +0200 Subject: [PATCH 13/15] add examples for all funcs in sets --- scripts/test_docs.py | 3 +- sets/example_test.go | 115 --------------------- sets/examples_test.go | 235 ++++++++++++++++++++++++++++++++++++++++++ sets/void.go | 2 +- 4 files changed, 238 insertions(+), 117 deletions(-) delete mode 100644 sets/example_test.go create mode 100644 sets/examples_test.go diff --git a/scripts/test_docs.py b/scripts/test_docs.py index 7b3849a..53ad9a3 100644 --- a/scripts/test_docs.py +++ b/scripts/test_docs.py @@ -51,7 +51,7 @@ def get_examples(pkg: str) -> Iterator[str]: yield fname -@pytest.mark.parametrize('pkg', ['slices']) +@pytest.mark.parametrize('pkg', ['slices', 'sets']) def test_all_have_examples(pkg: str) -> None: """Every function must have an example. """ @@ -64,6 +64,7 @@ def test_all_have_examples(pkg: str) -> None: 'slices', 'maps', 'lambdas', + # 'sets', # channels need tests for context-aware versions # 'channels', ]) diff --git a/sets/example_test.go b/sets/example_test.go deleted file mode 100644 index 31722ea..0000000 --- a/sets/example_test.go +++ /dev/null @@ -1,115 +0,0 @@ -package sets_test - -import ( - "fmt" - - "github.com/life4/genesis/sets" -) - -func ExampleContains() { - s := sets.New(3, 4, 5) - result := sets.Contains(s, 4) - fmt.Println(result) - // Output: true -} - -func ExampleDisjoint() { - a := sets.New(3, 4, 5) - b := sets.New(5, 6, 7) - result := sets.Disjoint(a, b) - fmt.Println(result) // a and b both share 5 - // Output: false -} - -func ExampleDisjointMany() { - a := sets.New(3, 4) - b := sets.New(5, 6) - c := sets.New(6, 7) - result := sets.DisjointMany(a, b, c) - fmt.Println(result) // b and c both share 6 - // Output: false -} - -func ExampleEmpty() { - s := sets.New[int]() - result := sets.Empty(s) - fmt.Println(result) - // Output: true -} - -func ExampleEqual() { - a := sets.New(3, 4, 5) - b := sets.New(3, 4, 6) - result := sets.Equal(a, b) - fmt.Println(result) - // Output: false -} - -func ExampleEqualMany() { - a := sets.New(3, 4, 5) - b := sets.New(3, 4, 5) - c := sets.New(3, 4, 6) - result := sets.EqualMany(a, b, c) - fmt.Println(result) - // Output: false -} - -func ExampleIntersect() { - a := sets.New(3, 4, 5) - b := sets.New(5, 6, 7) - result := sets.Intersect(a, b) - fmt.Println(result) - // Output: true -} - -func ExampleMax() { - s := sets.New(3, 6, 4, 5) - result, _ := sets.Max(s) - fmt.Println(result) - // Output: 6 -} - -func ExampleMin() { - s := sets.New(4, 5, 3, 6) - result, _ := sets.Min(s) - fmt.Println(result) - // Output: 3 -} - -func ExampleReduce() { - s := sets.New(3, 4, 5) - add := func(x, acc int) int { return x + acc } - result := sets.Reduce(s, 0, add) - fmt.Println(result) - // Output: 12 -} - -func ExampleSubset() { - a := sets.New(4, 5) - b := sets.New(3, 4, 5, 6) - result := sets.Subset(a, b) - fmt.Println(result) - // Output: true -} - -func ExampleSum() { - s := sets.New(3, 4, 5) - result := sets.Sum(s) - fmt.Println(result) - // Output: 12 -} - -func ExampleToSlice() { - s := sets.New(3) - result := sets.ToSlice(s) - fmt.Println(result) - // 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 -} diff --git a/sets/examples_test.go b/sets/examples_test.go new file mode 100644 index 0000000..e434a94 --- /dev/null +++ b/sets/examples_test.go @@ -0,0 +1,235 @@ +package sets_test + +import ( + "fmt" + + "github.com/life4/genesis/sets" +) + +func ExampleAdd() { + s := sets.New(3, 4) + sets.Add(s, 4) + sets.Add(s, 5) + fmt.Println(s) + // Output: map[3:{} 4:{} 5:{}] +} + +func ExampleClear() { + s := sets.New(3, 4) + sets.Clear(s) + fmt.Println(s) + // Output: map[] +} + +func ExampleContains() { + s := sets.New(3, 4, 5) + result := sets.Contains(s, 4) + fmt.Println(result) + // Output: true +} + +func ExampleCopy() { + a := sets.New(3, 4) + b := sets.Copy(a) + sets.Add(a, 5) + sets.Add(b, 6) + fmt.Println(a) + fmt.Println(b) + // Output: + // map[3:{} 4:{} 5:{}] + // 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) + result := sets.Difference(a, b) + fmt.Println(result) + // Output: map[3:{} 4:{}] +} + +func ExampleDisjoint() { + a := sets.New(3, 4, 5) + b := sets.New(5, 6, 7) + result := sets.Disjoint(a, b) + fmt.Println(result) // a and b both share 5 + // Output: false +} + +func ExampleDisjointMany() { + a := sets.New(3, 4) + b := sets.New(5, 6) + c := sets.New(6, 7) + result := sets.DisjointMany(a, b, c) + fmt.Println(result) // b and c both share 6 + // Output: false +} + +func ExampleEmpty() { + s := sets.New[int]() + result := sets.Empty(s) + fmt.Println(result) + // Output: true +} + +func ExampleEqual() { + a := sets.New(3, 4, 5) + b := sets.New(3, 4, 6) + result := sets.Equal(a, b) + fmt.Println(result) + // Output: false +} + +func ExampleEqualMany() { + a := sets.New(3, 4, 5) + b := sets.New(3, 4, 5) + c := sets.New(3, 4, 6) + result := sets.EqualMany(a, b, c) + fmt.Println(result) + // Output: false +} + +func ExampleFilter() { + s := sets.New(3, 4, 5, 6, 7, 8) + even := func(x int) bool { return x%2 == 0 } + result := sets.Filter(s, even) + fmt.Println(result) + // Output: map[4:{} 6:{} 8:{}] +} + +func ExampleFromSlice() { + s := []int{3, 4, 5} + result := sets.FromSlice(s) + fmt.Println(result) + // Output: map[3:{} 4:{} 5:{}] +} + +func ExampleIntersect() { + a := sets.New(3, 4, 5) + b := sets.New(5, 6, 7) + result := sets.Intersect(a, b) + fmt.Println(result) + // Output: true +} + +func ExampleIntersection() { + a := sets.New(3, 4, 5) + b := sets.New(4, 5, 6) + result := sets.Intersection(a, b) + fmt.Println(result) + // Output: map[4:{} 5:{}] +} + +func ExampleMap() { + s := sets.New(3, 4, 5) + double := func(x int) int { return x * 2 } + result := sets.Map(s, double) + fmt.Println(result) + // Output: map[6:{} 8:{} 10:{}] +} + +func ExampleMax() { + s := sets.New(3, 6, 4, 5) + result, _ := sets.Max(s) + fmt.Println(result) + // Output: 6 +} + +func ExampleMin() { + s := sets.New(4, 5, 3, 6) + result, _ := sets.Min(s) + fmt.Println(result) + // Output: 3 +} + +func ExampleNew() { + result := sets.New(3, 4, 4, 5, 3) + fmt.Println(result) + // Output: map[3:{} 4:{} 5:{}] +} + +func ExamplePop() { + s := sets.New(4) + val, _ := sets.Pop(s) + fmt.Println(val, s) + // Output: 4 map[] +} + +func ExampleReduce() { + s := sets.New(3, 4, 5) + add := func(x, acc int) int { return x + acc } + result := sets.Reduce(s, 0, add) + fmt.Println(result) + // Output: 12 +} + +func ExampleSubset() { + a := sets.New(4, 5) + b := sets.New(3, 4, 5, 6) + result := sets.Subset(a, b) + fmt.Println(result) + // Output: true +} + +func ExampleSum() { + s := sets.New(3, 4, 5) + result := sets.Sum(s) + fmt.Println(result) + // Output: 12 +} + +func ExampleSymmetricDifference() { + a := sets.New(3, 4, 5) + b := sets.New(5, 6, 7) + result := sets.SymmetricDifference(a, b) + fmt.Println(result) + // Output: map[3:{} 4:{} 6:{} 7:{}] +} + +func ExampleToSlice() { + s := sets.New(3) + result := sets.ToSlice(s) + fmt.Println(result) + // 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) + result := sets.Union(a, b) + fmt.Println(result) + // Output: map[3:{} 4:{} 5:{} 6:{} 7:{}] +} + +func ExampleUnionMany() { + a := sets.New(3, 4) + b := sets.New(5, 6) + c := sets.New(6, 7) + result := sets.UnionMany(a, b, c) + fmt.Println(result) + // Output: map[3:{} 4:{} 5:{} 6:{} 7:{}] +} + +func ExampleUpdate() { + a := sets.New(4, 5) + b := sets.New(5, 6) + sets.Update(a, b) + fmt.Println(a) + // Output: map[4:{} 5:{} 6:{}] +} diff --git a/sets/void.go b/sets/void.go index 9004274..e5e6bac 100644 --- a/sets/void.go +++ b/sets/void.go @@ -49,7 +49,7 @@ func Pop[S ~map[K]Z, K comparable](set S) (K, error) { // // If the target set is nil and the values set is not empty, the function will panic. // -// The set is modified in place. +// The set is modified in place. If you don't want to modify the set, use [Union] instead. func Update[S1, S2 ~map[K]Z, K comparable](target S1, values S2) { for k := range values { target[k] = Z{} From e7d85f2a399cb755d00682d458b3919f97d42b3b Mon Sep 17 00:00:00 2001 From: gram Date: Tue, 19 Sep 2023 09:44:49 +0200 Subject: [PATCH 14/15] test sets.New --- scripts/test_docs.py | 10 +++++++--- sets/set_test.go | 6 ++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/scripts/test_docs.py b/scripts/test_docs.py index 53ad9a3..3baf1f2 100644 --- a/scripts/test_docs.py +++ b/scripts/test_docs.py @@ -51,7 +51,10 @@ def get_examples(pkg: str) -> Iterator[str]: yield fname -@pytest.mark.parametrize('pkg', ['slices', 'sets']) +@pytest.mark.parametrize('pkg', [ + 'slices', + 'sets', +]) def test_all_have_examples(pkg: str) -> None: """Every function must have an example. """ @@ -64,7 +67,7 @@ def test_all_have_examples(pkg: str) -> None: 'slices', 'maps', 'lambdas', - # 'sets', + 'sets', # channels need tests for context-aware versions # 'channels', ]) @@ -74,7 +77,8 @@ def test_all_have_tests(pkg: str) -> None: funcs = set(get_funcs(pkg)) tests = set(get_tests(pkg)) assert funcs - assert not funcs - tests + diff = funcs - tests + assert not diff @pytest.mark.parametrize('func', get_funcs('slices')) diff --git a/sets/set_test.go b/sets/set_test.go index 6cc9404..8b8e66a 100644 --- a/sets/set_test.go +++ b/sets/set_test.go @@ -65,6 +65,12 @@ func TestMap(t *testing.T) { is.Equal(sets.Map(nilSet, double), sets.New[int]()) } +func TestNew(t *testing.T) { + is := is.New(t) + s := sets.New(3, 4, 5) + is.Equal(s, map[int]struct{}{3: {}, 4: {}, 5: {}}) +} + func TestSymmetricDifference(t *testing.T) { is := is.NewRelaxed(t) is.Equal(sets.SymmetricDifference(sets.New(3, 4, 5), sets.New(3, 4, 5)), sets.New[int]()) From 98e66ea1941b600b4a4e1db3850738160c290bc3 Mon Sep 17 00:00:00 2001 From: gram Date: Tue, 19 Sep 2023 09:46:41 +0200 Subject: [PATCH 15/15] test docs on CI --- .github/workflows/main.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2e72533..c55ff7d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,6 +29,16 @@ jobs: - uses: actions/checkout@v3 - run: go test -v ./... + pytest: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: "3.11" + - run: python3 -m pip install pytest + - run: python3 -m pytest ./scripts + golangci-lint: runs-on: ubuntu-latest steps: