Skip to content

Commit

Permalink
Iterator: general refactoring and reset method (#193)
Browse files Browse the repository at this point in the history
Feature

Reset allows for the iteration process over a sequence to be restarted from the beginning.
It enables reusing the iterator for multiple traversals without needing to recreate it.

Refactoring

It is a idiomatic practice to design functions and methods to return concrete struct types.
This approach promotes flexibility and decoupling, allowing the calling code to work with any implementation that satisfies the interface
  • Loading branch information
donutloop committed Feb 29, 2024
1 parent 0e1593c commit 473f9c9
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 35 deletions.
66 changes: 41 additions & 25 deletions iterator/iterator.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,15 @@ type Iterator[T any] interface {
Next() (item T, ok bool)
}

// StopIterator is an interface for stopping Iterator.
// ResettableIterator supports to reset the iterator
type ResettableIterator[T any] interface {
Iterator[T]
// Reset allows for the iteration process over a sequence to be restarted from the beginning.
// It enables reusing the iterator for multiple traversals without needing to recreate it.
Reset()
}

// StopIterator is an interface for stopping Iterator.
type StopIterator[T any] interface {
Iterator[T]

Expand Down Expand Up @@ -81,8 +89,8 @@ type PrevIterator[T any] interface {
////////////////////////////////////////////////////////////////////////////////////////////////////

// FromSlice returns an iterator over a slice of data.
func FromSlice[T any](slice []T) Iterator[T] {
return &sliceIterator[T]{slice: slice, index: -1}
func FromSlice[T any](slice []T) *SliceIterator[T] {
return &SliceIterator[T]{slice: slice, index: -1}
}

func ToSlice[T any](iter Iterator[T]) []T {
Expand All @@ -93,16 +101,16 @@ func ToSlice[T any](iter Iterator[T]) []T {
return result
}

type sliceIterator[T any] struct {
type SliceIterator[T any] struct {
slice []T
index int
}

func (iter *sliceIterator[T]) HasNext() bool {
func (iter *SliceIterator[T]) HasNext() bool {
return iter.index < len(iter.slice)-1
}

func (iter *sliceIterator[T]) Next() (T, bool) {
func (iter *SliceIterator[T]) Next() (T, bool) {
iter.index++

ok := iter.index >= 0 && iter.index < len(iter.slice)
Expand All @@ -116,7 +124,7 @@ func (iter *sliceIterator[T]) Next() (T, bool) {
}

// Prev implements PrevIterator.
func (iter *sliceIterator[T]) Prev() {
func (iter *SliceIterator[T]) Prev() {
if iter.index == -1 {
panic("Next function should be called Prev")
}
Expand All @@ -128,7 +136,7 @@ func (iter *sliceIterator[T]) Prev() {
}

// Set implements SetIterator.
func (iter *sliceIterator[T]) Set(value T) {
func (iter *SliceIterator[T]) Set(value T) {
if iter.index == -1 {
panic("Next function should be called Set")
}
Expand All @@ -138,52 +146,60 @@ func (iter *sliceIterator[T]) Set(value T) {
iter.slice[iter.index] = value
}

func (iter *SliceIterator[T]) Reset() {
iter.index = -1
}

// FromRange creates a iterator which returns the numeric range between start inclusive and end
// exclusive by the step size. start should be less than end, step shoud be positive.
func FromRange[T constraints.Integer | constraints.Float](start, end, step T) Iterator[T] {
func FromRange[T constraints.Integer | constraints.Float](start, end, step T) *RangeIterator[T] {
if end < start {
panic("RangeIterator: start should be before end")
} else if step <= 0 {
panic("RangeIterator: step should be positive")
}

return &rangeIterator[T]{start: start, end: end, step: step}
return &RangeIterator[T]{start: start, end: end, step: step, current: start}
}

type rangeIterator[T constraints.Integer | constraints.Float] struct {
start, end, step T
type RangeIterator[T constraints.Integer | constraints.Float] struct {
start, end, step, current T
}

func (iter *rangeIterator[T]) HasNext() bool {
return iter.start < iter.end
func (iter *RangeIterator[T]) HasNext() bool {
return iter.current < iter.end
}

func (iter *rangeIterator[T]) Next() (T, bool) {
if iter.start >= iter.end {
func (iter *RangeIterator[T]) Next() (T, bool) {
if iter.current >= iter.end {
var zero T
return zero, false
}
num := iter.start
iter.start += iter.step
num := iter.current
iter.current += iter.step
return num, true
}

// FromRange creates a iterator which returns the numeric range between start inclusive and end
// exclusive by the step size. start should be less than end, step shoud be positive.
func FromChannel[T any](channel <-chan T) Iterator[T] {
return &channelIterator[T]{channel: channel}
func (iter *RangeIterator[T]) Reset() {
iter.current = iter.start
}

// FromChannel creates an iterator which returns items received from the provided channel.
// The iteration continues until the channel is closed.
func FromChannel[T any](channel <-chan T) *ChannelIterator[T] {
return &ChannelIterator[T]{channel: channel}
}

type channelIterator[T any] struct {
type ChannelIterator[T any] struct {
channel <-chan T
}

func (iter *channelIterator[T]) Next() (T, bool) {
func (iter *ChannelIterator[T]) Next() (T, bool) {
item, ok := <-iter.channel
return item, ok
}

func (iter *channelIterator[T]) HasNext() bool {
func (iter *ChannelIterator[T]) HasNext() bool {
return len(iter.channel) == 0
}

Expand Down
75 changes: 72 additions & 3 deletions iterator/iterator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,15 +50,36 @@ func TestSliceIterator(t *testing.T) {
assert.Equal(false, ok)
})

// Reset
t.Run("slice iterator Reset: ", func(t *testing.T) {
iter1 := FromSlice([]int{1, 2, 3, 4})
for i := 0; i < 4; i++ {
item, ok := iter1.Next()
if !ok {
break
}
assert.Equal(i+1, item)
}

iter1.Reset()

for i := 0; i < 4; i++ {
item, ok := iter1.Next()
if !ok {
break
}
assert.Equal(i+1, item)
}
})

t.Run("slice iterator ToSlice: ", func(t *testing.T) {
iter := FromSlice([]int{1, 2, 3, 4})
item, _ := iter.Next()
assert.Equal(1, item)

data := ToSlice(iter)
data := ToSlice[int](iter)
assert.Equal([]int{2, 3, 4}, data)
})

}

func TestRangeIterator(t *testing.T) {
Expand All @@ -84,6 +105,54 @@ func TestRangeIterator(t *testing.T) {
_, ok = iter.Next()
assert.Equal(false, ok)
assert.Equal(false, iter.HasNext())

iter.Reset()

item, ok = iter.Next()
assert.Equal(1, item)
assert.Equal(true, ok)

item, ok = iter.Next()
assert.Equal(2, item)
assert.Equal(true, ok)

item, ok = iter.Next()
assert.Equal(3, item)
assert.Equal(true, ok)

_, ok = iter.Next()
assert.Equal(false, ok)
assert.Equal(false, iter.HasNext())
})

t.Run("range iterator reset: ", func(t *testing.T) {
iter := FromRange(1, 4, 1)

item, ok := iter.Next()
assert.Equal(1, item)
assert.Equal(true, ok)

item, ok = iter.Next()
assert.Equal(2, item)
assert.Equal(true, ok)

iter.Reset()

item, ok = iter.Next()
assert.Equal(1, item)
assert.Equal(true, ok)

item, ok = iter.Next()
assert.Equal(2, item)
assert.Equal(true, ok)

item, ok = iter.Next()
assert.Equal(3, item)
assert.Equal(true, ok)

_, ok = iter.Next()
assert.Equal(false, ok)
assert.Equal(false, iter.HasNext())
})

}
Expand All @@ -93,7 +162,7 @@ func TestChannelIterator(t *testing.T) {

assert := internal.NewAssert(t, "TestRangeIterator")

iter := FromSlice([]int{1, 2, 3, 4})
var iter Iterator[int] = FromSlice([]int{1, 2, 3, 4})

ctx, cancel := context.WithCancel(context.Background())
iter = FromChannel(ToChannel(ctx, iter, 0))
Expand Down
14 changes: 7 additions & 7 deletions iterator/operation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestMapIterator(t *testing.T) {

assert := internal.NewAssert(t, "TestMapIterator")

iter := FromSlice([]int{1, 2, 3, 4})
var iter Iterator[int] = FromSlice([]int{1, 2, 3, 4})

iter = Map(iter, func(n int) int { return n / 2 })

Expand All @@ -34,7 +34,7 @@ func TestFilterIterator(t *testing.T) {

assert := internal.NewAssert(t, "TestFilterIterator")

iter := FromSlice([]int{1, 2, 3, 4})
var iter Iterator[int] = FromSlice([]int{1, 2, 3, 4})

iter = Filter(iter, func(n int) bool { return n < 3 })

Expand All @@ -47,10 +47,10 @@ func TestJoinIterator(t *testing.T) {

assert := internal.NewAssert(t, "TestJoinIterator")

iter1 := FromSlice([]int{1, 2})
iter2 := FromSlice([]int{3, 4})
var iter1 Iterator[int] = FromSlice([]int{1, 2})
var iter2 Iterator[int] = FromSlice([]int{3, 4})

iter := Join(iter1, iter2)
var iter Iterator[int] = Join(iter1, iter2)

item, ok := iter.Next()
assert.Equal(1, item)
Expand All @@ -64,7 +64,7 @@ func TestReduce(t *testing.T) {

assert := internal.NewAssert(t, "TestReduce")

iter := FromSlice([]int{1, 2, 3, 4})
var iter Iterator[int] = FromSlice([]int{1, 2, 3, 4})
sum := Reduce(iter, 0, func(a, b int) int { return a + b })
assert.Equal(10, sum)
}
Expand All @@ -74,7 +74,7 @@ func TestTakeIterator(t *testing.T) {

assert := internal.NewAssert(t, "TestTakeIterator")

iter := FromSlice([]int{1, 2, 3, 4, 5})
var iter Iterator[int] = FromSlice([]int{1, 2, 3, 4, 5})

iter = Take(iter, 3)

Expand Down

0 comments on commit 473f9c9

Please sign in to comment.