From c9971c0fa073c28fc8c4d18746f6f4972ef616d1 Mon Sep 17 00:00:00 2001 From: Seth Hoenig Date: Mon, 15 May 2023 15:01:55 +0000 Subject: [PATCH] common: some tweaks for common helper functions This PR renames TransformUnion to InsertSetFunc, and TransformSlice to SliceFunc which should be a bit more obvious to understand. Also, the "Union" methods on the set types are ones that return a newly created set, and so we are also now more consistent with how those are named. Also adds a boolean return value to InsertSetFunc, which like the insertion methods on the set types returns true if the set was modified as a result of the operation. And adds test cases around being modified or not modified. Cleans up a few doc strings. --- common.go | 47 ++++++++++++++++++++--------- common_test.go | 81 +++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 96 insertions(+), 32 deletions(-) diff --git a/common.go b/common.go index 2547cea..644995b 100644 --- a/common.go +++ b/common.go @@ -3,21 +3,32 @@ package set -// Common is the interface that all sets implement +// Common is a minimal interface that all sets implement. type Common[T any] interface { - // Slice returns a slice of all elements in the set + + // Slice returns a slice of all elements in the set. + // + // Note: order of elements depends on the underlying implementation. Slice() []T - // Insert inserts an element into the set - // if the element already exists, it will return false + + // Insert an element into the set. + // + // Returns true if the set is modified as a result. Insert(T) bool - // InsertSlice inserts all elements from the slice into the set + + // InsertSlice inserts all elements from the slice into the set. + // + // Returns true if the set was modified as a result. InsertSlice([]T) bool - // Size returns the number of elements in the set + + // Size returns the number of elements in the set. Size() int - // ForEach will call the callback function for each element in the set. + + // ForEach will call the callback function for each element in the set. // If the callback returns false, the iteration will stop. - // Note: iteration order depends on the underlying implementation; - ForEach(call func(T) bool) + // + // Note: iteration order depends on the underlying implementation. + ForEach(func(T) bool) } // InsertSliceFunc inserts all elements from the slice into the set @@ -27,16 +38,24 @@ func InsertSliceFunc[T, E any](s Common[T], items []E, f func(element E) T) { } } -// TransformUnion transforms the set A into another set B -func TransformUnion[T, E any](a Common[T], b Common[E], transform func(T) E) { +// InsertSetFunc inserts the elements of a into b, applying the transform function +// to each element before insertion. +// +// Returns true if b was modified as a result. +func InsertSetFunc[T, E any](a Common[T], b Common[E], transform func(T) E) bool { + modified := false a.ForEach(func(item T) bool { - _ = b.Insert(transform(item)) + if b.Insert(transform(item)) { + modified = true + } return true }) + return modified } -// TransformSlice transforms the set into a slice -func TransformSlice[T, E any](s Common[T], transform func(T) E) []E { +// SliceFunc produces a slice of the elements in s, applying the transform +// function to each element first. +func SliceFunc[T, E any](s Common[T], transform func(T) E) []E { slice := make([]E, 0, s.Size()) s.ForEach(func(item T) bool { slice = append(slice, transform(item)) diff --git a/common_test.go b/common_test.go index 9bd7a5c..840f825 100644 --- a/common_test.go +++ b/common_test.go @@ -13,17 +13,19 @@ import ( func TestInsertSliceFunc(t *testing.T) { numbers := ints(3) + t.Run("set", func(t *testing.T) { s := New[string](10) transform := func(element int) string { return strconv.Itoa(element) } - InsertSliceFunc[string, int](s, numbers, transform) + InsertSliceFunc[string](s, numbers, transform) slices := s.Slice() sort.Strings(slices) must.SliceEqFunc(t, slices, []string{"1", "2", "3"}, func(a, b string) bool { return a == b }) }) + t.Run("hashset", func(t *testing.T) { s := NewHashSet[*company, string](10) - InsertSliceFunc[*company, int](s, numbers, func(element int) *company { + InsertSliceFunc[*company](s, numbers, func(element int) *company { return &company{ address: "InsertSliceFunc", floor: element, @@ -33,9 +35,10 @@ func TestInsertSliceFunc(t *testing.T) { "InsertSliceFunc:1", "InsertSliceFunc:2", "InsertSliceFunc:3", }) }) + t.Run("treeSet", func(t *testing.T) { s := NewTreeSet[string, Compare[string]](Cmp[string]) - InsertSliceFunc[string, int](s, numbers, func(element int) string { + InsertSliceFunc[string](s, numbers, func(element int) string { return strconv.Itoa(element) }) invariants(t, s, Cmp[string]) @@ -43,11 +46,10 @@ func TestInsertSliceFunc(t *testing.T) { }) } -func TestTransformSlice(t *testing.T) { +func TestSliceFunc(t *testing.T) { t.Run("set", func(t *testing.T) { s := From(ints(3)) - - slice := TransformSlice[int, string](s, func(element int) string { + slice := SliceFunc[int](s, func(element int) string { return strconv.Itoa(element) }) sort.Strings(slice) @@ -57,15 +59,16 @@ func TestTransformSlice(t *testing.T) { t.Run("hashset", func(t *testing.T) { s := NewHashSet[*company, string](10) s.InsertSlice([]*company{c1, c2, c3}) - slice := TransformSlice[*company, string](s, func(element *company) string { + slice := SliceFunc[*company](s, func(element *company) string { return element.Hash() }) sort.Strings(slice) must.SliceEqFunc(t, slice, []string{"street:1", "street:2", "street:3"}, func(a, b string) bool { return a == b }) }) + t.Run("treeSet", func(t *testing.T) { s := TreeSetFrom[int, Compare[int]]([]int{1, 2, 3}, Cmp[int]) - slice := TransformSlice[int, string](s, func(element int) string { + slice := SliceFunc[int](s, func(element int) string { return strconv.Itoa(element) }) sort.Strings(slice) @@ -73,14 +76,15 @@ func TestTransformSlice(t *testing.T) { }) } -func TestTransform(t *testing.T) { +func TestInsertSetFunc(t *testing.T) { t.Run("set", func(t *testing.T) { a := From(ints(3)) t.Run("set -> set", func(t *testing.T) { b := New[string](3) - TransformUnion[int, string](a, b, func(element int) string { + modified := InsertSetFunc[int, string](a, b, func(element int) string { return strconv.Itoa(element) }) + must.True(t, modified) slice := b.Slice() sort.Strings(slice) must.SliceEqFunc(t, slice, []string{"1", "2", "3"}, func(a, b string) bool { return a == b }) @@ -88,91 +92,132 @@ func TestTransform(t *testing.T) { t.Run("set -> hashset", func(t *testing.T) { b := NewHashSet[*company, string](10) - TransformUnion[int, *company](a, b, func(element int) *company { + modified := InsertSetFunc[int, *company](a, b, func(element int) *company { return &company{ address: "street", floor: element, } }) + must.True(t, modified) must.MapContainsKeys(t, b.items, []string{ "street:1", "street:2", "street:3", }) }) + t.Run("set -> treeSet", func(t *testing.T) { b := NewTreeSet[string, Compare[string]](Cmp[string]) - TransformUnion[int, string](a, b, func(element int) string { + modified := InsertSetFunc[int, string](a, b, func(element int) string { return strconv.Itoa(element) }) + must.True(t, modified) slice := b.Slice() sort.Strings(slice) must.SliceEqFunc(t, slice, []string{"1", "2", "3"}, func(a, b string) bool { return a == b }) }) + + t.Run("not modified", func(t *testing.T) { + b := a.Copy() + modified := InsertSetFunc[int, int](a, b, func(element int) int { + return element + }) + must.False(t, modified) + }) }) + t.Run("hashSet", func(t *testing.T) { a := NewHashSet[*company, string](10) a.InsertSlice([]*company{c1, c2, c3}) + t.Run("hashSet -> set", func(t *testing.T) { b := New[int](3) - TransformUnion[*company, int](a, b, func(element *company) int { + modified := InsertSetFunc[*company, int](a, b, func(element *company) int { return element.floor }) + must.True(t, modified) slice := b.Slice() sort.Ints(slice) must.SliceEqFunc(t, slice, []int{1, 2, 3}, func(a, b int) bool { return a == b }) }) + t.Run("hashSet -> hashSet", func(t *testing.T) { b := NewHashSet[*company, string](10) - TransformUnion[*company, *company](a, b, func(element *company) *company { + modified := InsertSetFunc[*company, *company](a, b, func(element *company) *company { return &company{ address: element.address, floor: element.floor * 5, } }) + must.True(t, modified) must.MapContainsKeys(t, b.items, []string{ "street:5", "street:10", "street:15", }) }) + t.Run("hashSet -> treeSet", func(t *testing.T) { b := NewTreeSet[int, Compare[int]](Cmp[int]) - TransformUnion[*company, int](a, b, func(element *company) int { + modified := InsertSetFunc[*company, int](a, b, func(element *company) int { return element.floor }) + must.True(t, modified) slice := b.Slice() sort.Ints(slice) must.SliceEqFunc(t, slice, []int{1, 2, 3}, func(a, b int) bool { return a == b }) }) + + t.Run("not modified", func(t *testing.T) { + b := a.Copy() + modified := InsertSetFunc[*company, *company](a, b, func(element *company) *company { + return element + }) + must.False(t, modified) + }) }) + t.Run("treeSet", func(t *testing.T) { a := TreeSetFrom[int, Compare[int]]([]int{1, 2, 3}, Cmp[int]) + t.Run("treeSet -> set", func(t *testing.T) { b := New[string](3) - TransformUnion[int, string](a, b, func(element int) string { + modified := InsertSetFunc[int, string](a, b, func(element int) string { return strconv.Itoa(element) }) + must.True(t, modified) slice := b.Slice() sort.Strings(slice) must.SliceEqFunc(t, slice, []string{"1", "2", "3"}, func(a, b string) bool { return a == b }) }) + t.Run("treeSet -> hashSet", func(t *testing.T) { b := NewHashSet[*company, string](10) - TransformUnion[int, *company](a, b, func(element int) *company { + modified := InsertSetFunc[int, *company](a, b, func(element int) *company { return &company{ address: "street", floor: element, } }) + must.True(t, modified) must.MapContainsKeys(t, b.items, []string{ "street:1", "street:2", "street:3", }) }) + t.Run("treeSet -> treeSet", func(t *testing.T) { b := NewTreeSet[string, Compare[string]](Cmp[string]) - TransformUnion[int, string](a, b, func(element int) string { + modified := InsertSetFunc[int, string](a, b, func(element int) string { return strconv.Itoa(element) }) + must.True(t, modified) slice := b.Slice() sort.Strings(slice) must.SliceEqFunc(t, slice, []string{"1", "2", "3"}, func(a, b string) bool { return a == b }) }) + + t.Run("not modified", func(t *testing.T) { + b := a.Copy() + modified := InsertSetFunc[int, int](a, b, func(element int) int { + return element + }) + must.False(t, modified) + }) }) }