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

Sets & maps append-multi. Also docs and a fix for SortedSets #35

Merged
merged 4 commits into from
Jan 2, 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
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Immutable ![release](https://img.shields.io/github/release/benbjohnson/immutable
=========

This repository contains *generic* immutable collection types for Go. It includes
`List`, `Map`, and `SortedMap` implementations. Immutable collections can
`List`, `Map`, `SortedMap`, `Set` and `SortedSet` implementations. Immutable collections can
provide efficient, lock free sharing of data by requiring that edits to the
collections return new collections.

Expand Down Expand Up @@ -284,6 +284,29 @@ type Comparer[K comparable] interface {

Please see the internal `defaultComparer` for an example, bearing in mind that it works for several types.

## 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`.

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
`string` keys. You may pass in a `nil` hasher to `NewMap()` if you are using
one of these key types.


## Sorted Set

The `SortedSet` represents a sorted collection of unique values.
Unlike the `Set`, however, keys can be iterated over in-order. It is implemented
as a B+tree.

Sorted sets require a `Comparer` to sort values and check for equality. There are
built-in comparer implementations for `int`, `uint`, and `string` keys. You may
pass a `nil` comparer to `NewSortedSet()` if you are using one of these key
types.

The API is identical to the `Set` implementation.


## Contributing
Expand Down
49 changes: 49 additions & 0 deletions immutable.go
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,20 @@ func NewMap[K comparable, V any](hasher Hasher[K]) *Map[K, V] {
}
}

// NewMapOf returns a new instance of Map, containing a map of provided entries.
//
// If hasher is nil, a default hasher implementation will automatically be chosen based on the first key added.
// Default hasher implementations only exist for int, string, and byte slice types.
func NewMapOf[K comparable, V any](hasher Hasher[K], entries map[K]V) *Map[K, V] {
m := &Map[K, V]{
hasher: hasher,
}
for k, v := range entries {
m.set(k, v, true)
}
return m
}

// Len returns the number of elements in the map.
func (m *Map[K, V]) Len() int {
return m.size
Expand Down Expand Up @@ -738,6 +752,18 @@ 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 @@ -1582,6 +1608,20 @@ func NewSortedMap[K comparable, V any](comparer Comparer[K]) *SortedMap[K, V] {
}
}

// NewSortedMapOf returns a new instance of SortedMap, containing a map of provided entries.
//
// If comparer is nil then a default comparer is set after the first key is inserted. Default comparers
// exist for int, string, and byte slice keys.
func NewSortedMapOf[K comparable, V any](comparer Comparer[K], entries map[K]V) *SortedMap[K, V] {
m := &SortedMap[K, V]{
comparer: comparer,
}
for k, v := range entries {
m.set(k, v, true)
}
return m
}

// Len returns the number of elements in the sorted map.
func (m *SortedMap[K, V]) Len() int {
return m.size
Expand All @@ -1602,6 +1642,15 @@ 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
32 changes: 30 additions & 2 deletions immutable_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,10 @@ func TestList(t *testing.T) {
t.Fatal("Failed to find leaf node due to nil child")
}
return findLeaf(n.children[0])
case *listLeafNode[*int]: return n
default: panic("Unexpected case")
case *listLeafNode[*int]:
return n
default:
panic("Unexpected case")
}
}

Expand Down Expand Up @@ -959,6 +961,22 @@ func TestMap_Set(t *testing.T) {
}
})

t.Run("Multi", func(t *testing.T) {
m := NewMapOf(nil, map[int]string{1: "foo"})
itr := m.Iterator()
if itr.Done() {
t.Fatal("MapIterator.Done()=false, expected true")
}
if k, v, ok := itr.Next(); !ok {
t.Fatalf("MapIterator.Next()!=ok, expected ok")
} else if k != 1 || v != "foo" {
t.Fatalf("MapIterator.Next()=<%v,%v>, expected <1, \"foo\">", k, v)
}
if k, v, ok := itr.Next(); ok {
t.Fatalf("MapIterator.Next()=<%v,%v>, expected nil", k, v)
}
})

t.Run("VerySmall", func(t *testing.T) {
const n = 6
m := NewMap[int, int](nil)
Expand Down Expand Up @@ -1070,6 +1088,16 @@ func TestMap_Overwrite(t *testing.T) {
t.Fatalf("Get(%d)=<%v,%v>", i, v, ok)
}
}

t.Run("Simple", func(t *testing.T) {
m := NewMap[int, string](nil)
itr := m.Iterator()
if !itr.Done() {
t.Fatal("MapIterator.Done()=true, expected false")
} else if k, v, ok := itr.Next(); ok {
t.Fatalf("MapIterator.Next()=<%v,%v>, expected nil", k, v)
}
})
}

func TestMap_Delete(t *testing.T) {
Expand Down
123 changes: 107 additions & 16 deletions sets.go
Original file line number Diff line number Diff line change
@@ -1,54 +1,98 @@
package immutable

// Set represents a collection of unique values. The set uses a Hasher
// to generate hashes and check for equality of key values.
//
// Internally, the Set stores values as keys of a Map[T,struct{}]
type Set[T comparable] struct {
m *Map[T, struct{}]
}

func NewSet[T comparable](hasher Hasher[T]) Set[T] {
return Set[T]{
// NewSet returns a new instance of Set.
//
// If hasher is nil, a default hasher implementation will automatically be chosen based on the first key added.
// 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),
}
for _, value := range values {
s.m.set(value, struct{}{}, true)
}
return s
}

func (s Set[T]) Set(val T) Set[T] {
return Set[T]{
m: s.m.Set(val, struct{}{}),
// Set 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(),
}
for _, value := range values {
n.m.set(value, struct{}{}, true)
}
return n
}

func (s Set[T]) Delete(val T) Set[T] {
return Set[T]{
m: s.m.Delete(val),
// 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
}

// Has returns true when the set contains the given value
func (s Set[T]) Has(val T) bool {
_, ok := s.m.Get(val)
return ok
}

// Len returns the number of elements in the underlying map.
func (s Set[K]) Len() int {
return s.m.Len()
}

// Items returns a slice of the items inside the set
func (s Set[T]) Items() []T {
r := make([]T, 0, s.Len())
itr := s.Iterator()
for !itr.Done() {
v, _ := itr.Next()
r = append(r, v)
}
return r
}

// Iterator returns a new iterator for this set positioned at the first value.
func (s Set[T]) Iterator() *SetIterator[T] {
itr := &SetIterator[T]{mi: s.m.Iterator()}
itr.mi.First()
return itr
}

// SetIterator represents an iterator over a set.
// Iteration can occur in natural or reverse order based on use of Next() or Prev().
type SetIterator[T comparable] struct {
mi *MapIterator[T, struct{}]
}

// Done returns true if no more values remain in the iterator.
func (itr *SetIterator[T]) Done() bool {
return itr.mi.Done()
}

// First moves the iterator to the first value.
func (itr *SetIterator[T]) First() {
itr.mi.First()
}

// Next moves the iterator to the next value.
func (itr *SetIterator[T]) Next() (val T, ok bool) {
val, _, ok = itr.mi.Next()
return
Expand Down Expand Up @@ -82,65 +126,112 @@ type SortedSet[T comparable] struct {
m *SortedMap[T, struct{}]
}

func NewSortedSet[T comparable](comparer Comparer[T]) SortedSet[T] {
return SortedSet[T]{
// NewSortedSet returns a new instance of SortedSet.
//
// If comparer is nil then
// a default comparer is set after the first key is inserted. Default comparers
// 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),
}
for _, value := range values {
s.m.set(value, struct{}{}, true)
}
return s
}

func (s SortedSet[T]) Put(val T) SortedSet[T] {
return SortedSet[T]{
m: s.m.Set(val, struct{}{}),
// Set 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]) Delete(val T) SortedSet[T] {
return SortedSet[T]{
m: s.m.Delete(val),
// 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
}

// Has returns true when the set contains the given value
func (s SortedSet[T]) Has(val T) bool {
_, ok := s.m.Get(val)
return ok
}

// Len returns the number of elements in the underlying map.
func (s SortedSet[K]) Len() int {
return s.m.Len()
}

// Items returns a slice of the items inside the set
func (s SortedSet[T]) Items() []T {
r := make([]T, 0, s.Len())
itr := s.Iterator()
for !itr.Done() {
v, _ := itr.Next()
r = append(r, v)
}
return r
}

// Iterator returns a new iterator for this set positioned at the first value.
func (s SortedSet[T]) Iterator() *SortedSetIterator[T] {
itr := &SortedSetIterator[T]{mi: s.m.Iterator()}
itr.mi.First()
return itr
}

// SortedSetIterator represents an iterator over a sorted set.
// Iteration can occur in natural or reverse order based on use of Next() or Prev().
type SortedSetIterator[T comparable] struct {
mi *SortedMapIterator[T, struct{}]
}

// Done returns true if no more values remain in the iterator.
func (itr *SortedSetIterator[T]) Done() bool {
return itr.mi.Done()
}

// First moves the iterator to the first value.
func (itr *SortedSetIterator[T]) First() {
itr.mi.First()
}

// Last moves the iterator to the last value.
func (itr *SortedSetIterator[T]) Last() {
itr.mi.Last()
}

// Next moves the iterator to the next value.
func (itr *SortedSetIterator[T]) Next() (val T, ok bool) {
val, _, ok = itr.mi.Next()
return
}

// Next moves the iterator to the previous value.
func (itr *SortedSetIterator[T]) Prev() (val T, ok bool) {
val, _, ok = itr.mi.Prev()
return
}

// Next moves the iterator to the given value.
//
// If the value does not exist then the next value is used. If no more keys exist
// then the iterator is marked as done.
func (itr *SortedSetIterator[T]) Seek(val T) {
itr.mi.Seek(val)
}
Expand Down
Loading