Skip to content
Draft
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
71 changes: 71 additions & 0 deletions immutable.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import (
"sort"
"strings"

"github.com/benbjohnson/immutable/immutableiter"
"golang.org/x/exp/constraints"
)

Expand Down Expand Up @@ -229,6 +230,23 @@ func (l *List[T]) Iterator() *ListIterator[T] {
return itr
}

// All returns an [iter.Seq] function that can be used to perform range-like operations on the [iter.Seq] and inter-operate with other libraries using the Go 1.23 iterable function API.
//
// This is equivalent to calling the [List.Iterator] method and wrapping the iterator with [immutableiter.IndexedSeq].
func (l *List[T]) All() immutableiter.Seq[T] {
return immutableiter.IndexedSeq[T](l.Iterator())
}

// AllWithIndex returns an [iter.Seq2] function that can be used to perform range-like operations on the [iter.Seq2] and inter-operate with other libraries using the Go 1.23 iterable function
// API.
//
// This is equivalent to calling the [List.Iterator] method and wrapping the iterator with [immutableiter.IndexedSeqWithIndex].
//
// TODO(Izzette) chose either All or AllWithIndex?
func (l *List[T]) AllWithIndex() immutableiter.Seq2[int, T] {
return immutableiter.IndexedSeqWithIndex[T](l.Iterator())
}

// ListBuilder represents an efficient builder for creating new Lists.
type ListBuilder[T any] struct {
list *List[T] // current state
Expand Down Expand Up @@ -294,6 +312,25 @@ func (b *ListBuilder[T]) Iterator() *ListIterator[T] {
return b.list.Iterator()
}

// All returns an [iter.Seq] function that can be used to perform range-like operations on the [iter.Seq] and inter-operate with other libraries using the Go 1.23 iterable function API.
//
// This is equivalent to calling the [ListBuilder.Iterator] method and wrapping the iterator with [immutableiter.IndexedSeq].
//
// TODO(Izzette): should the builders really have All or is this just noise?
func (l *ListBuilder[T]) All() immutableiter.Seq[T] {
return immutableiter.IndexedSeq[T](l.Iterator())
}

// AllWithIndex returns an [iter.Seq2] function that can be used to perform range-like operations on the [iter.Seq2] and inter-operate with other libraries using the Go 1.23 iterable function
// API.
//
// This is equivalent to calling the [ListBuilder.Iterator] method and wrapping the iterator with [immutableiter.IndexedSeqWithIndex].
//
// TODO(Izzette): should the builders really have All or is this just noise?
func (l *ListBuilder[T]) AllWithIndex() immutableiter.Seq2[int, T] {
return immutableiter.IndexedSeqWithIndex[T](l.Iterator())
}

// Constants for bit shifts used for levels in the List trie.
const (
listNodeBits = 5
Expand Down Expand Up @@ -539,6 +576,8 @@ func (n *listLeafNode[T]) deleteAfter(index int, mutable bool) listNode[T] {
}

// ListIterator represents an ordered iterator over a list.
//
// ListIterator implements [immutableiter.IndexedIterator] for T.
type ListIterator[T any] struct {
list *List[T] // source list
index int // current index position
Expand Down Expand Up @@ -813,6 +852,13 @@ func (m *Map[K, V]) Iterator() *MapIterator[K, V] {
return itr
}

// All returns an [iter.Seq2] function that can be used to perform range-like operations on the [iter.Seq2] and inter-operate with other libraries using the Go 1.23 iterable function API.
//
// This is equivalent to calling the [Map.Iterator] method and wrapping the iterator with [immutableiter.KeyedSeq].
func (m *Map[K, V]) All() immutableiter.Seq2[K, V] {
return immutableiter.KeyedSeq[K, V](m.Iterator())
}

// MapBuilder represents an efficient builder for creating Maps.
type MapBuilder[K, V any] struct {
m *Map[K, V] // current state
Expand Down Expand Up @@ -862,6 +908,13 @@ func (b *MapBuilder[K, V]) Iterator() *MapIterator[K, V] {
return b.m.Iterator()
}

// All returns an [iter.Seq2] function that can be used to perform range-like operations on the [iter.Seq2] and inter-operate with other libraries using the Go 1.23 iterable function API.
//
// This is equivalent to calling the [MapBuilder.Iterator] method and wrapping the iterator with [immutableiter.KeyedSeq].
func (m *MapBuilder[K, V]) All() immutableiter.Seq2[K, V] {
return immutableiter.KeyedSeq[K, V](m.Iterator())
}

// mapNode represents any node in the map tree.
type mapNode[K, V any] interface {
get(key K, shift uint, keyHash uint32, h Hasher[K]) (value V, ok bool)
Expand Down Expand Up @@ -1435,6 +1488,8 @@ type mapEntry[K, V any] struct {

// MapIterator represents an iterator over a map's key/value pairs. Although
// map keys are not sorted, the iterator's order is deterministic.
//
// MapIterator implements [immutableiter.KeyedIterator] for K and V.
type MapIterator[K, V any] struct {
m *Map[K, V] // source map

Expand Down Expand Up @@ -1704,6 +1759,13 @@ func (m *SortedMap[K, V]) Iterator() *SortedMapIterator[K, V] {
return itr
}

// All returns an [iter.Seq2] function that can be used to perform range-like operations on the [iter.Seq2] and inter-operate with other libraries using the Go 1.23 iterable function API.
//
// This is equivalent to calling the [SortedMap.Iterator] method and wrapping the iterator with [immutableiter.KeyedSeq].
func (m *SortedMap[K, V]) All() immutableiter.Seq2[K, V] {
return immutableiter.KeyedSeq[K, V](m.Iterator())
}

// SortedMapBuilder represents an efficient builder for creating sorted maps.
type SortedMapBuilder[K, V any] struct {
m *SortedMap[K, V] // current state
Expand Down Expand Up @@ -1753,6 +1815,13 @@ func (b *SortedMapBuilder[K, V]) Iterator() *SortedMapIterator[K, V] {
return b.m.Iterator()
}

// All returns an [iter.Seq2] function that can be used to perform range-like operations on the [iter.Seq2] and inter-operate with other libraries using the Go 1.23 iterable function API.
//
// This is equivalent to calling the [SortedMapBuilder.Iterator] method and wrapping the iterator with [immutableiter.KeyedSeq].
func (m *SortedMapBuilder[K, V]) All() immutableiter.Seq2[K, V] {
return immutableiter.KeyedSeq[K, V](m.Iterator())
}

// sortedMapNode represents a branch or leaf node in the sorted map.
type sortedMapNode[K, V any] interface {
minKey() K
Expand Down Expand Up @@ -2035,6 +2104,8 @@ func (n *sortedMapLeafNode[K, V]) delete(key K, c Comparer[K], mutable bool, res

// SortedMapIterator represents an iterator over a sorted map.
// Iteration can occur in natural or reverse order based on use of Next() or Prev().
//
// SortedMapIterator implements the [immutableiter.KeyedIterator] interface for K and V.
type SortedMapIterator[K, V any] struct {
m *SortedMap[K, V] // source map

Expand Down
249 changes: 249 additions & 0 deletions immutable_seq_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
package immutable

import (
"reflect"
"sort"
"testing"

"github.com/benbjohnson/immutable/immutableiter"
)

// TODO(Izzette): add tests for the All function of the Map, SortedMap, Set, and SortedSet types and their corresponding builders.

func TestList_All(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
seqTestEmpty(t, func() immutableiter.Seq[int] {
return NewList[int]().All()
})
})
t.Run("One", func(t *testing.T) {
seqTestOne(t, func() immutableiter.Seq[int] {
return NewList[int](1).All()
}, 1)
})
t.Run("OneBreak", func(t *testing.T) {
seqTestOneBreak(t, func() immutableiter.Seq[int] {
return NewList[int](1).All()
}, 1)
})
t.Run("AllOrdered", func(t *testing.T) {
seqTestAllOrdered(t, func() immutableiter.Seq[int] {
return NewList[int](1, 2, 3).All()
}, []int{1, 2, 3})
})
t.Run("AllOrderedBreak", func(t *testing.T) {
seqTestAllOrderedBreak(t, func() immutableiter.Seq[int] {
return NewList[int](1, 2, 3, 4, 5).All()
}, []int{1, 2, 3})
})
}

func TestListBuilder_All(t *testing.T) {
t.Run("Empty", func(t *testing.T) {
seqTestEmpty(t, func() immutableiter.Seq[int] {
return NewListBuilder[int]().All()
})
})
t.Run("One", func(t *testing.T) {
seqTestOne(t, func() immutableiter.Seq[int] {
lb := NewListBuilder[int]()
lb.Append(1)
return lb.All()
}, 1)
})
t.Run("OneBreak", func(t *testing.T) {
seqTestOneBreak(t, func() immutableiter.Seq[int] {
lb := NewListBuilder[int]()
lb.Append(1)
return lb.All()
}, 1)
})
t.Run("AllOrdered", func(t *testing.T) {
seqTestAllOrdered(t, func() immutableiter.Seq[int] {
lb := NewListBuilder[int]()
lb.Append(1)
lb.Append(2)
lb.Append(3)
return lb.All()
}, []int{1, 2, 3})
})
t.Run("AllOrderedBreak", func(t *testing.T) {
seqTestAllOrderedBreak(t, func() immutableiter.Seq[int] {
lb := NewListBuilder[int]()
for i := 1; i <= 5; i++ {
lb.Append(i)
}
return lb.All()
}, []int{1, 2, 3})
})
}

// seqTestEmpty checks if the sequence returned by allFunc is empty.
func seqTestEmpty[T any](t *testing.T, allFunc func() immutableiter.Seq[T]) {
seq := allFunc()

actual := make([]T, 0)
yield := func(v T) bool {
actual = append(actual, v)
return true
}
seq(yield)

if len(actual) != 0 {
t.Errorf("Expected empty sequence, got %v", actual)
}
}

// seqTestOne checks if the sequence returned by allFunc contains exactly one
// element and that it matches the expected value.
func seqTestOne[T any](t *testing.T, allFunc func() immutableiter.Seq[T], expected T) {
seq := allFunc()

actual := make([]T, 0)
yield := func(v T) bool {
actual = append(actual, v)
return true
}
seq(yield)

if len(actual) != 1 {
t.Errorf("Expected 1 element, got %d", len(actual))
}
if !reflect.DeepEqual(actual, []T{expected}) {
t.Errorf("Expected %v, got %v", expected, actual[0])
}
}

// seqTestOneBreak checks if the sequence returned by allFunc contains exactly
// one element and that it matches the expected value, but stops yielding after
// the first element.
func seqTestOneBreak[T any](t *testing.T, allFunc func() immutableiter.Seq[T], expected T) {
seq := allFunc()

actual := make([]T, 0)
yield := func(v T) bool {
actual = append(actual, v)
// Do not continue yielding, but this is the last element so it should
// not impact the result.
return false
}
seq(yield)

if len(actual) != 1 {
t.Errorf("Expected 1 element, got %d", len(actual))
}
if !reflect.DeepEqual(actual, []T{expected}) {
t.Errorf("Expected %v, got %v", expected, actual[0])
}
}

// seqTestAllOrdered checks if the sequence returned by allFunc contains all
// elements in the expected order.
// It compares the actual sequence with the expected sequence.
func seqTestAllOrdered[T any](
t *testing.T,
allFunc func() immutableiter.Seq[T],
expected []T,
) {
seq := allFunc()

actual := make([]T, 0, len(expected))
yield := func(v T) bool {
actual = append(actual, v)
return true
}
seq(yield)

if !reflect.DeepEqual(actual, expected) {
t.Errorf("Expected %v, got %v", expected, actual)
}
}

// seqTestAllUnordered checks if the sequence returned by allFunc contains all
// elements in any order.
// It compares the actual sequence with the expected sequence after sorting
// both slices.
func seqTestAllUnordered[T immutableiter.Ordered](
t *testing.T,
allFunc func() immutableiter.Seq[T],
expected []T,
) {
seq := allFunc()

actual := make([]T, 0, len(expected))
yield := func(v T) bool {
actual = append(actual, v)
return true
}
seq(yield)

// Sort both slices for comparison
sort.Slice(actual, func(i, j int) bool {
return actual[i] < actual[j]
})
sort.Slice(expected, func(i, j int) bool {
return expected[i] < expected[j]
})

if !reflect.DeepEqual(actual, expected) {
t.Errorf("Expected %v, got %v", expected, actual)
}
}

// seqTestAllOrderedBreak checks if the sequence returned by allFunc contains
// all elements in the expected order, but stops yielding after the nth element
// where n is the length of the expected slice.
func seqTestAllOrderedBreak[T any](
t *testing.T,
allFunc func() immutableiter.Seq[T],
expected []T,
) {
seq := allFunc()

actual := make([]T, 0, len(expected))
yield := func(v T) bool {
actual = append(actual, v)
return len(actual) < len(expected)
}
seq(yield)

if !reflect.DeepEqual(actual, expected) {
t.Errorf("Expected %v, got %v", expected, actual)
}
}

// seqTestAllSet checks if the sequence returned by allFunc contains all
// elements in any order, but ensures that no element is returned more than
// once.
// It stops yielding after the nth element where n is the sample parameter.
func seqTestAllSetBreak[T comparable](
t *testing.T,
allFunc func() immutableiter.Seq[T],
sample int,
expected []T,
) {
seq := allFunc()

actual := make([]T, 0, len(expected))
yield := func(v T) bool {
actual = append(actual, v)
return len(actual) < sample
}
seq(yield)

// Convert expected slice to a map for comparison
expectedMap := make(map[T]int, len(expected))
for _, v := range expected {
expectedMap[v] = 0
}
// Check if all yielded values are in the expected map no more than once
for _, v := range actual {
if count, ok := expectedMap[v]; !ok {
t.Errorf("Unexpected value %v in yielded values", v)
} else if count > 0 {
t.Errorf("Duplicate value %v in yielded values", v)
} else {
expectedMap[v]++
}
}
}
Loading