Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure immutability of sets, maps & lists #39

Merged
merged 4 commits into from
Jan 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,8 +286,8 @@ Please see the internal `defaultComparer` for an example, bearing in mind that i

## Set

The `Set` represents a collection of unique values. It uses a `map[T]struct{}`, so it carries over some characteristics from the built-in Go `map` type.
Values neeed to be `comparable`.
The `Set` represents a collection of unique values, and it is implemented as a
wrapper around a `Map[T, struct{}]`.
BarrensZeppelin marked this conversation as resolved.
Show resolved Hide resolved

Like Maps, Sets require a `Hasher` to hash keys and check for equality. There are built-in
hasher implementations for most primitive types such as `int`, `uint`, and
Expand Down
37 changes: 4 additions & 33 deletions immutable.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,8 @@ func (l *List[T]) set(index int, value T, mutable bool) *List[T] {
}

// Append returns a new list with value added to the end of the list.
func (l *List[T]) Append(values ...T) *List[T] {
other := l.clone()
for _, value := range values {
other.append(value, true)
}
return other
func (l *List[T]) Append(value T) *List[T] {
return l.append(value, false)
}

func (l *List[T]) append(value T, mutable bool) *List[T] {
Expand All @@ -145,12 +141,8 @@ func (l *List[T]) append(value T, mutable bool) *List[T] {
}

// Prepend returns a new list with value(s) added to the beginning of the list.
func (l *List[T]) Prepend(values ...T) *List[T] {
other := l.clone()
for i := len(values) - 1; i >= 0; i-- {
other.prepend(values[i], true)
}
return other
func (l *List[T]) Prepend(value T) *List[T] {
return l.prepend(value, false)
}

func (l *List[T]) prepend(value T, mutable bool) *List[T] {
Expand Down Expand Up @@ -752,18 +744,6 @@ func (m *Map[K, V]) Set(key K, value V) *Map[K, V] {
return m.set(key, value, false)
}

// SetMany returns a map with the keys set to the new values. nil values are allowed.
//
// This function will return a new map even if the updated value is the same as
// the existing value because Map does not track value equality.
func (m *Map[K, V]) SetMany(entries map[K]V) *Map[K, V] {
n := m.clone()
for k, v := range entries {
n.set(k, v, true)
}
return n
}

func (m *Map[K, V]) set(key K, value V, mutable bool) *Map[K, V] {
// Set a hasher on the first value if one does not already exist.
hasher := m.hasher
Expand Down Expand Up @@ -1642,15 +1622,6 @@ func (m *SortedMap[K, V]) Set(key K, value V) *SortedMap[K, V] {
return m.set(key, value, false)
}

// SetMany returns a map with the keys set to the new values.
func (m *SortedMap[K, V]) SetMany(entries map[K]V) *SortedMap[K, V] {
n := m.clone()
for k, v := range entries {
n.set(k, v, true)
}
return n
}

func (m *SortedMap[K, V]) set(key K, value V, mutable bool) *SortedMap[K, V] {
// Set a comparer on the first value if one does not already exist.
comparer := m.comparer
Expand Down
14 changes: 14 additions & 0 deletions immutable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,19 @@ func TestList(t *testing.T) {
}
})

t.Run("AppendImmutable", func(t *testing.T) {
outer_l := NewList[int]()
for N := 0; N < 1_000; N++ {
l1 := outer_l.Append(0)
outer_l.Append(1)
if actual := l1.Get(N); actual != 0 {
t.Fatalf("Append mutates list with %d elements. Got %d instead of 0", N, actual)
}

outer_l = outer_l.Append(0)
}
})

RunRandom(t, "Random", func(t *testing.T, rand *rand.Rand) {
l := NewTList()
for i := 0; i < 100000; i++ {
Expand Down Expand Up @@ -2482,6 +2495,7 @@ func RunRandom(t *testing.T, name string, fn func(t *testing.T, rand *rand.Rand)
}
t.Run(name, func(t *testing.T) {
for i := 0; i < *randomN; i++ {
i := i
t.Run(fmt.Sprintf("%08d", i), func(t *testing.T) {
t.Parallel()
fn(t, rand.New(rand.NewSource(int64(i))))
Expand Down
60 changes: 16 additions & 44 deletions sets.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,37 +14,23 @@ type Set[T comparable] struct {
// Default hasher implementations only exist for int, string, and byte slice types.
// NewSet can also take some initial values as varargs.
func NewSet[T comparable](hasher Hasher[T], values ...T) Set[T] {
s := Set[T]{
m: NewMap[T, struct{}](hasher),
}
m := NewMap[T, struct{}](hasher)
for _, value := range values {
s.m.set(value, struct{}{}, true)
m = m.set(value, struct{}{}, true)
}
return s
return Set[T]{m}
}

// Set returns a set containing the new value.
// Add returns a set containing the new value.
//
// This function will return a new set even if the set already contains the value.
func (s Set[T]) Set(values ...T) Set[T] {
n := Set[T]{
m: s.m.clone(),
}
BarrensZeppelin marked this conversation as resolved.
Show resolved Hide resolved
for _, value := range values {
n.m.set(value, struct{}{}, true)
}
return n
func (s Set[T]) Add(value T) Set[T] {
return Set[T]{s.m.Set(value, struct{}{})}
}

// Delete returns a set with the given key removed.
func (s Set[T]) Delete(values ...T) Set[T] {
n := Set[T]{
m: s.m.clone(),
}
for _, value := range values {
n.m.delete(value, true)
}
return n
func (s Set[T]) Delete(value T) Set[T] {
return Set[T]{s.m.Delete(value)}
}

// Has returns true when the set contains the given value
Expand Down Expand Up @@ -133,37 +119,23 @@ type SortedSet[T comparable] struct {
// exist for int, string, and byte slice keys.
// NewSortedSet can also take some initial values as varargs.
func NewSortedSet[T comparable](comparer Comparer[T], values ...T) SortedSet[T] {
s := SortedSet[T]{
m: NewSortedMap[T, struct{}](comparer),
}
m := NewSortedMap[T, struct{}](comparer)
for _, value := range values {
s.m.set(value, struct{}{}, true)
m = m.set(value, struct{}{}, true)
}
return s
return SortedSet[T]{m}
}

// Set returns a set containing the new value.
// Add returns a set containing the new value.
//
// This function will return a new set even if the set already contains the value.
func (s SortedSet[T]) Set(values ...T) SortedSet[T] {
n := SortedSet[T]{
m: s.m.clone(),
}
for _, value := range values {
n.m.set(value, struct{}{}, true)
}
return n
func (s SortedSet[T]) Add(value T) SortedSet[T] {
return SortedSet[T]{s.m.Set(value, struct{}{})}
}

// Delete returns a set with the given key removed.
func (s SortedSet[T]) Delete(values ...T) SortedSet[T] {
n := SortedSet[T]{
m: s.m.clone(),
}
for _, value := range values {
n.m.delete(value, true)
}
return n
func (s SortedSet[T]) Delete(value T) SortedSet[T] {
return SortedSet[T]{s.m.Delete(value)}
}

// Has returns true when the set contains the given value
Expand Down
9 changes: 5 additions & 4 deletions sets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import (

func TestSetsPut(t *testing.T) {
s := NewSet[string](nil)
s2 := s.Set("1").Set("1")
s2 := s.Add("1").Add("1")
s2.Add("2")
if s.Len() != 0 {
t.Fatalf("Unexpected mutation of set")
}
Expand All @@ -33,7 +34,7 @@ func TestSetsPut(t *testing.T) {

func TestSetsDelete(t *testing.T) {
s := NewSet[string](nil)
s2 := s.Set("1")
s2 := s.Add("1")
s3 := s.Delete("1")
if s2.Len() != 1 {
t.Fatalf("Unexpected non-mutation of set")
Expand All @@ -51,7 +52,7 @@ func TestSetsDelete(t *testing.T) {

func TestSortedSetsPut(t *testing.T) {
s := NewSortedSet[string](nil)
s2 := s.Set("1").Set("1").Set("0")
s2 := s.Add("1").Add("1").Add("0")
if s.Len() != 0 {
t.Fatalf("Unexpected mutation of set")
}
Expand Down Expand Up @@ -85,7 +86,7 @@ func TestSortedSetsPut(t *testing.T) {

func TestSortedSetsDelete(t *testing.T) {
s := NewSortedSet[string](nil)
s2 := s.Set("1")
s2 := s.Add("1")
s3 := s.Delete("1")
if s2.Len() != 1 {
t.Fatalf("Unexpected non-mutation of set")
Expand Down