mirror of
https://github.com/luxfi/container.git
synced 2026-09-24 00:24:34 +00:00
feat: add data structures from utils (iterator, buffer, sampler, etc)
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package buffer
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
_ Queue[struct{}] = (*boundedQueue[struct{}])(nil)
|
||||
|
||||
errInvalidMaxSize = errors.New("maxSize must be greater than 0")
|
||||
)
|
||||
|
||||
// A FIFO queue.
|
||||
type Queue[T any] interface {
|
||||
// Pushes [elt] onto the queue.
|
||||
// If the queue is full, the oldest element is evicted to make space.
|
||||
Push(T)
|
||||
// Pops the oldest element from the queue.
|
||||
// Returns false if the queue is empty.
|
||||
Pop() (T, bool)
|
||||
// Returns the oldest element without removing it.
|
||||
// Returns false if the queue is empty.
|
||||
Peek() (T, bool)
|
||||
// Returns the element at the given index without removing it.
|
||||
// Index(0) returns the oldest element.
|
||||
// Index(Len() - 1) returns the newest element.
|
||||
// Returns false if there is no element at that index.
|
||||
Index(int) (T, bool)
|
||||
// Returns the number of elements in the queue.
|
||||
Len() int
|
||||
// Returns the queue elements from oldest to newest.
|
||||
// This is an O(n) operation and should be used sparingly.
|
||||
List() []T
|
||||
}
|
||||
|
||||
// Keeps up to [maxSize] entries in an ordered buffer
|
||||
// and calls [onEvict] on any item that is evicted.
|
||||
// Not safe for concurrent use.
|
||||
type boundedQueue[T any] struct {
|
||||
deque Deque[T]
|
||||
maxSize int
|
||||
onEvict func(T)
|
||||
}
|
||||
|
||||
// Returns a new bounded, non-blocking queue that holds up to [maxSize] elements.
|
||||
// When an element is evicted, [onEvict] is called with the evicted element.
|
||||
// If [onEvict] is nil, this is a no-op.
|
||||
// [maxSize] must be >= 1.
|
||||
// Not safe for concurrent use.
|
||||
func NewBoundedQueue[T any](maxSize int, onEvict func(T)) (Queue[T], error) {
|
||||
if maxSize < 1 {
|
||||
return nil, errInvalidMaxSize
|
||||
}
|
||||
return &boundedQueue[T]{
|
||||
deque: NewUnboundedDeque[T](maxSize + 1), // +1 so we never resize
|
||||
maxSize: maxSize,
|
||||
onEvict: onEvict,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *boundedQueue[T]) Push(elt T) {
|
||||
if b.deque.Len() == b.maxSize {
|
||||
evicted, _ := b.deque.PopLeft()
|
||||
if b.onEvict != nil {
|
||||
b.onEvict(evicted)
|
||||
}
|
||||
}
|
||||
_ = b.deque.PushRight(elt)
|
||||
}
|
||||
|
||||
func (b *boundedQueue[T]) Pop() (T, bool) {
|
||||
return b.deque.PopLeft()
|
||||
}
|
||||
|
||||
func (b *boundedQueue[T]) Peek() (T, bool) {
|
||||
return b.deque.PeekLeft()
|
||||
}
|
||||
|
||||
func (b *boundedQueue[T]) Index(i int) (T, bool) {
|
||||
return b.deque.Index(i)
|
||||
}
|
||||
|
||||
func (b *boundedQueue[T]) Len() int {
|
||||
return b.deque.Len()
|
||||
}
|
||||
|
||||
func (b *boundedQueue[T]) List() []T {
|
||||
return b.deque.List()
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewBoundedQueue(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Case: maxSize < 1
|
||||
_, err := NewBoundedQueue[bool](0, nil)
|
||||
require.ErrorIs(err, errInvalidMaxSize)
|
||||
|
||||
// Case: maxSize == 1 and nil onEvict
|
||||
b, err := NewBoundedQueue[bool](1, nil)
|
||||
require.NoError(err)
|
||||
|
||||
// Put 2 elements to make sure we don't panic on evict
|
||||
b.Push(true)
|
||||
b.Push(true)
|
||||
}
|
||||
|
||||
func TestBoundedQueue(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
maxSize := 3
|
||||
evicted := []int{}
|
||||
onEvict := func(elt int) {
|
||||
evicted = append(evicted, elt)
|
||||
}
|
||||
b, err := NewBoundedQueue(maxSize, onEvict)
|
||||
require.NoError(err)
|
||||
|
||||
require.Zero(b.Len())
|
||||
|
||||
// Fill the queue
|
||||
for i := 0; i < maxSize; i++ {
|
||||
b.Push(i)
|
||||
require.Equal(i+1, b.Len())
|
||||
got, ok := b.Peek()
|
||||
require.True(ok)
|
||||
require.Zero(got)
|
||||
got, ok = b.Index(i)
|
||||
require.True(ok)
|
||||
require.Equal(i, got)
|
||||
require.Len(b.List(), i+1)
|
||||
}
|
||||
require.Equal([]int{}, evicted)
|
||||
require.Len(b.List(), maxSize)
|
||||
// Queue is [0, 1, 2]
|
||||
|
||||
// Empty the queue
|
||||
for i := 0; i < maxSize; i++ {
|
||||
got, ok := b.Pop()
|
||||
require.True(ok)
|
||||
require.Equal(i, got)
|
||||
require.Equal(maxSize-i-1, b.Len())
|
||||
require.Len(b.List(), maxSize-i-1)
|
||||
}
|
||||
|
||||
// Queue is empty
|
||||
|
||||
_, ok := b.Pop()
|
||||
require.False(ok)
|
||||
_, ok = b.Peek()
|
||||
require.False(ok)
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
require.Zero(b.Len())
|
||||
require.Empty(b.List())
|
||||
|
||||
// Fill the queue again
|
||||
for i := 0; i < maxSize; i++ {
|
||||
b.Push(i)
|
||||
require.Equal(i+1, b.Len())
|
||||
}
|
||||
|
||||
// Queue is [0, 1, 2]
|
||||
|
||||
// Putting another element should evict the oldest.
|
||||
b.Push(maxSize)
|
||||
|
||||
// Queue is [1, 2, 3]
|
||||
|
||||
require.Equal(maxSize, b.Len())
|
||||
require.Len(b.List(), maxSize)
|
||||
got, ok := b.Peek()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
got, ok = b.Index(maxSize - 1)
|
||||
require.True(ok)
|
||||
require.Equal(maxSize, got)
|
||||
require.Equal([]int{0}, evicted)
|
||||
|
||||
// Put 2 more elements
|
||||
b.Push(maxSize + 1)
|
||||
b.Push(maxSize + 2)
|
||||
|
||||
// Queue is [3, 4, 5]
|
||||
|
||||
require.Equal(maxSize, b.Len())
|
||||
require.Equal([]int{0, 1, 2}, evicted)
|
||||
got, ok = b.Peek()
|
||||
require.True(ok)
|
||||
require.Equal(3, got)
|
||||
require.Equal([]int{3, 4, 5}, b.List())
|
||||
|
||||
for i := maxSize; i < 2*maxSize; i++ {
|
||||
got, ok := b.Index(i - maxSize)
|
||||
require.True(ok)
|
||||
require.Equal(i, got)
|
||||
}
|
||||
|
||||
// Empty the queue
|
||||
for i := 0; i < maxSize; i++ {
|
||||
got, ok := b.Pop()
|
||||
require.True(ok)
|
||||
require.Equal(i+3, got)
|
||||
require.Equal(maxSize-i-1, b.Len())
|
||||
require.Len(b.List(), maxSize-i-1)
|
||||
}
|
||||
|
||||
// Queue is empty
|
||||
|
||||
require.Empty(b.List())
|
||||
require.Zero(b.Len())
|
||||
require.Equal([]int{0, 1, 2}, evicted)
|
||||
_, ok = b.Pop()
|
||||
require.False(ok)
|
||||
_, ok = b.Peek()
|
||||
require.False(ok)
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/luxfi/utils"
|
||||
)
|
||||
|
||||
var _ BlockingDeque[int] = (*unboundedBlockingDeque[int])(nil)
|
||||
|
||||
type BlockingDeque[T any] interface {
|
||||
Deque[T]
|
||||
|
||||
// Close and empty the deque.
|
||||
Close()
|
||||
}
|
||||
|
||||
// Returns a new unbounded deque with the given initial size.
|
||||
// Note that the returned deque is always empty -- [initSize] is just
|
||||
// a hint to prevent unnecessary resizing.
|
||||
func NewUnboundedBlockingDeque[T any](initSize int) BlockingDeque[T] {
|
||||
q := &unboundedBlockingDeque[T]{
|
||||
Deque: NewUnboundedDeque[T](initSize),
|
||||
}
|
||||
q.cond = sync.NewCond(&q.lock)
|
||||
return q
|
||||
}
|
||||
|
||||
type unboundedBlockingDeque[T any] struct {
|
||||
lock sync.RWMutex
|
||||
cond *sync.Cond
|
||||
closed bool
|
||||
|
||||
Deque[T]
|
||||
}
|
||||
|
||||
// If the deque is closed returns false.
|
||||
func (q *unboundedBlockingDeque[T]) PushRight(elt T) bool {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
|
||||
if q.closed {
|
||||
return false
|
||||
}
|
||||
|
||||
// Add the item to the queue
|
||||
q.Deque.PushRight(elt)
|
||||
|
||||
// Signal a waiting thread
|
||||
q.cond.Signal()
|
||||
return true
|
||||
}
|
||||
|
||||
// If the deque is closed returns false.
|
||||
func (q *unboundedBlockingDeque[T]) PopRight() (T, bool) {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
|
||||
for {
|
||||
if q.closed {
|
||||
return utils.Zero[T](), false
|
||||
}
|
||||
if q.Deque.Len() != 0 {
|
||||
return q.Deque.PopRight()
|
||||
}
|
||||
q.cond.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (q *unboundedBlockingDeque[T]) PeekRight() (T, bool) {
|
||||
q.lock.RLock()
|
||||
defer q.lock.RUnlock()
|
||||
|
||||
if q.closed {
|
||||
return utils.Zero[T](), false
|
||||
}
|
||||
return q.Deque.PeekRight()
|
||||
}
|
||||
|
||||
// If the deque is closed returns false.
|
||||
func (q *unboundedBlockingDeque[T]) PushLeft(elt T) bool {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
|
||||
if q.closed {
|
||||
return false
|
||||
}
|
||||
|
||||
// Add the item to the queue
|
||||
q.Deque.PushLeft(elt)
|
||||
|
||||
// Signal a waiting thread
|
||||
q.cond.Signal()
|
||||
return true
|
||||
}
|
||||
|
||||
// If the deque is closed returns false.
|
||||
func (q *unboundedBlockingDeque[T]) PopLeft() (T, bool) {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
|
||||
for {
|
||||
if q.closed {
|
||||
return utils.Zero[T](), false
|
||||
}
|
||||
if q.Deque.Len() != 0 {
|
||||
return q.Deque.PopLeft()
|
||||
}
|
||||
q.cond.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
func (q *unboundedBlockingDeque[T]) PeekLeft() (T, bool) {
|
||||
q.lock.RLock()
|
||||
defer q.lock.RUnlock()
|
||||
|
||||
if q.closed {
|
||||
return utils.Zero[T](), false
|
||||
}
|
||||
return q.Deque.PeekLeft()
|
||||
}
|
||||
|
||||
func (q *unboundedBlockingDeque[T]) Index(i int) (T, bool) {
|
||||
q.lock.RLock()
|
||||
defer q.lock.RUnlock()
|
||||
|
||||
if q.closed {
|
||||
return utils.Zero[T](), false
|
||||
}
|
||||
return q.Deque.Index(i)
|
||||
}
|
||||
|
||||
func (q *unboundedBlockingDeque[T]) Len() int {
|
||||
q.lock.RLock()
|
||||
defer q.lock.RUnlock()
|
||||
|
||||
if q.closed {
|
||||
return 0
|
||||
}
|
||||
return q.Deque.Len()
|
||||
}
|
||||
|
||||
func (q *unboundedBlockingDeque[T]) List() []T {
|
||||
q.lock.RLock()
|
||||
defer q.lock.RUnlock()
|
||||
|
||||
if q.closed {
|
||||
return nil
|
||||
}
|
||||
return q.Deque.List()
|
||||
}
|
||||
|
||||
func (q *unboundedBlockingDeque[T]) Close() {
|
||||
q.cond.L.Lock()
|
||||
defer q.cond.L.Unlock()
|
||||
|
||||
if q.closed {
|
||||
return
|
||||
}
|
||||
|
||||
q.Deque = nil
|
||||
|
||||
// Mark the queue as closed
|
||||
q.closed = true
|
||||
q.cond.Broadcast()
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUnboundedBlockingDequePush(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
deque := NewUnboundedBlockingDeque[int](2)
|
||||
require.Empty(deque.List())
|
||||
_, ok := deque.Index(0)
|
||||
require.False(ok)
|
||||
|
||||
ok = deque.PushRight(1)
|
||||
require.True(ok)
|
||||
require.Equal([]int{1}, deque.List())
|
||||
got, ok := deque.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
ok = deque.PushRight(2)
|
||||
require.True(ok)
|
||||
require.Equal([]int{1, 2}, deque.List())
|
||||
got, ok = deque.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
got, ok = deque.Index(1)
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
_, ok = deque.Index(2)
|
||||
require.False(ok)
|
||||
|
||||
ch, ok := deque.PopLeft()
|
||||
require.True(ok)
|
||||
require.Equal(1, ch)
|
||||
require.Equal([]int{2}, deque.List())
|
||||
got, ok = deque.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
}
|
||||
|
||||
func TestUnboundedBlockingDequePop(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
deque := NewUnboundedBlockingDeque[int](2)
|
||||
require.Empty(deque.List())
|
||||
|
||||
ok := deque.PushRight(1)
|
||||
require.True(ok)
|
||||
require.Equal([]int{1}, deque.List())
|
||||
got, ok := deque.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
ch, ok := deque.PopLeft()
|
||||
require.True(ok)
|
||||
require.Equal(1, ch)
|
||||
require.Empty(deque.List())
|
||||
|
||||
var (
|
||||
gotOk bool
|
||||
gotCh int
|
||||
)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
gotCh, gotOk = deque.PopLeft()
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
ok = deque.PushRight(2)
|
||||
require.True(ok)
|
||||
wg.Wait()
|
||||
|
||||
require.True(gotOk)
|
||||
require.Equal(2, gotCh)
|
||||
|
||||
require.Empty(deque.List())
|
||||
_, ok = deque.Index(0)
|
||||
require.False(ok)
|
||||
}
|
||||
|
||||
func TestUnboundedBlockingDequeClose(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
deque := NewUnboundedBlockingDeque[int](2)
|
||||
|
||||
ok := deque.PushLeft(1)
|
||||
require.True(ok)
|
||||
|
||||
deque.Close()
|
||||
|
||||
_, ok = deque.PopRight()
|
||||
require.False(ok)
|
||||
|
||||
ok = deque.PushLeft(1)
|
||||
require.False(ok)
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package buffer
|
||||
|
||||
import "github.com/luxfi/utils"
|
||||
|
||||
const defaultInitSize = 32
|
||||
|
||||
// An unbounded deque (double-ended queue).
|
||||
// See https://en.wikipedia.org/wiki/Double-ended_queue
|
||||
// Not safe for concurrent access.
|
||||
type Deque[T any] interface {
|
||||
// Place an element at the leftmost end of the deque.
|
||||
// Returns true if the element was placed in the deque.
|
||||
PushLeft(T) bool
|
||||
// Place an element at the rightmost end of the deque.
|
||||
// Returns true if the element was placed in the deque.
|
||||
PushRight(T) bool
|
||||
// Remove and return the leftmost element of the deque.
|
||||
// Returns false if the deque is empty.
|
||||
PopLeft() (T, bool)
|
||||
// Remove and return the rightmost element of the deque.
|
||||
// Returns false if the deque is empty.
|
||||
PopRight() (T, bool)
|
||||
// Return the leftmost element of the deque without removing it.
|
||||
// Returns false if the deque is empty.
|
||||
PeekLeft() (T, bool)
|
||||
// Return the rightmost element of the deque without removing it.
|
||||
// Returns false if the deque is empty.
|
||||
PeekRight() (T, bool)
|
||||
// Returns the element at the given index.
|
||||
// Returns false if the index is out of bounds.
|
||||
// The leftmost element is at index 0.
|
||||
Index(int) (T, bool)
|
||||
// Returns the number of elements in the deque.
|
||||
Len() int
|
||||
// Returns the elements in the deque from left to right.
|
||||
List() []T
|
||||
}
|
||||
|
||||
// Returns a new unbounded deque with the given initial slice size.
|
||||
// Note that the returned deque is always empty -- [initSize] is just
|
||||
// a hint to prevent unnecessary resizing.
|
||||
func NewUnboundedDeque[T any](initSize int) Deque[T] {
|
||||
if initSize < 2 {
|
||||
initSize = defaultInitSize
|
||||
}
|
||||
return &unboundedSliceDeque[T]{
|
||||
// Note that [initSize] must be >= 2 to satisfy invariants (1) and (2).
|
||||
data: make([]T, initSize),
|
||||
right: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// Invariants after each function call and before the first call:
|
||||
// (1) The next element pushed left will be placed at data[left]
|
||||
// (2) The next element pushed right will be placed at data[right]
|
||||
// (3) There are [size] elements in the deque.
|
||||
type unboundedSliceDeque[T any] struct {
|
||||
size, left, right int
|
||||
data []T
|
||||
}
|
||||
|
||||
func (b *unboundedSliceDeque[T]) PushRight(elt T) bool {
|
||||
// Invariant (2) says it's safe to place the element without resizing.
|
||||
b.data[b.right] = elt
|
||||
b.size++
|
||||
b.right++
|
||||
b.right %= len(b.data)
|
||||
|
||||
b.resize()
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *unboundedSliceDeque[T]) PushLeft(elt T) bool {
|
||||
// Invariant (1) says it's safe to place the element without resizing.
|
||||
b.data[b.left] = elt
|
||||
b.size++
|
||||
b.left--
|
||||
if b.left < 0 {
|
||||
b.left = len(b.data) - 1 // Wrap around
|
||||
}
|
||||
|
||||
b.resize()
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *unboundedSliceDeque[T]) PopLeft() (T, bool) {
|
||||
if b.size == 0 {
|
||||
return utils.Zero[T](), false
|
||||
}
|
||||
idx := b.leftmostEltIdx()
|
||||
elt := b.data[idx]
|
||||
// Zero out to prevent memory leak.
|
||||
b.data[idx] = utils.Zero[T]()
|
||||
b.size--
|
||||
b.left++
|
||||
b.left %= len(b.data)
|
||||
return elt, true
|
||||
}
|
||||
|
||||
func (b *unboundedSliceDeque[T]) PeekLeft() (T, bool) {
|
||||
if b.size == 0 {
|
||||
return utils.Zero[T](), false
|
||||
}
|
||||
idx := b.leftmostEltIdx()
|
||||
return b.data[idx], true
|
||||
}
|
||||
|
||||
func (b *unboundedSliceDeque[T]) PopRight() (T, bool) {
|
||||
if b.size == 0 {
|
||||
return utils.Zero[T](), false
|
||||
}
|
||||
idx := b.rightmostEltIdx()
|
||||
elt := b.data[idx]
|
||||
// Zero out to prevent memory leak.
|
||||
b.data[idx] = utils.Zero[T]()
|
||||
b.size--
|
||||
b.right--
|
||||
if b.right < 0 {
|
||||
b.right = len(b.data) - 1 // Wrap around
|
||||
}
|
||||
return elt, true
|
||||
}
|
||||
|
||||
func (b *unboundedSliceDeque[T]) PeekRight() (T, bool) {
|
||||
if b.size == 0 {
|
||||
return utils.Zero[T](), false
|
||||
}
|
||||
idx := b.rightmostEltIdx()
|
||||
return b.data[idx], true
|
||||
}
|
||||
|
||||
func (b *unboundedSliceDeque[T]) Index(idx int) (T, bool) {
|
||||
if idx < 0 || idx >= b.size {
|
||||
return utils.Zero[T](), false
|
||||
}
|
||||
leftmostIdx := b.leftmostEltIdx()
|
||||
idx = (leftmostIdx + idx) % len(b.data)
|
||||
return b.data[idx], true
|
||||
}
|
||||
|
||||
func (b *unboundedSliceDeque[T]) Len() int {
|
||||
return b.size
|
||||
}
|
||||
|
||||
func (b *unboundedSliceDeque[T]) List() []T {
|
||||
if b.size == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
list := make([]T, b.size)
|
||||
leftmostIdx := b.leftmostEltIdx()
|
||||
if numCopied := copy(list, b.data[leftmostIdx:]); numCopied < b.size {
|
||||
// We copied all of the elements from the leftmost element index
|
||||
// to the end of the underlying slice, but we still haven't copied
|
||||
// all of the elements, so wrap around and copy the rest.
|
||||
copy(list[numCopied:], b.data[:b.right])
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (b *unboundedSliceDeque[T]) leftmostEltIdx() int {
|
||||
if b.left == len(b.data)-1 { // Wrap around case
|
||||
return 0
|
||||
}
|
||||
return b.left + 1 // Normal case
|
||||
}
|
||||
|
||||
func (b *unboundedSliceDeque[T]) rightmostEltIdx() int {
|
||||
if b.right == 0 {
|
||||
return len(b.data) - 1 // Wrap around case
|
||||
}
|
||||
return b.right - 1 // Normal case
|
||||
}
|
||||
|
||||
func (b *unboundedSliceDeque[T]) resize() {
|
||||
if b.size != len(b.data) {
|
||||
return
|
||||
}
|
||||
newData := make([]T, b.size*2)
|
||||
leftmostIdx := b.leftmostEltIdx()
|
||||
copy(newData, b.data[leftmostIdx:])
|
||||
numCopied := len(b.data) - leftmostIdx
|
||||
copy(newData[numCopied:], b.data[:b.right])
|
||||
b.data = newData
|
||||
b.left = len(b.data) - 1
|
||||
b.right = b.size
|
||||
}
|
||||
@@ -0,0 +1,672 @@
|
||||
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package buffer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUnboundedDeque_InitialCapGreaterThanMin(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
bIntf := NewUnboundedDeque[int](10)
|
||||
require.IsType(&unboundedSliceDeque[int]{}, bIntf)
|
||||
b := bIntf.(*unboundedSliceDeque[int])
|
||||
require.Empty(b.List())
|
||||
require.Zero(b.Len())
|
||||
_, ok := b.Index(0)
|
||||
require.False(ok)
|
||||
|
||||
b.PushLeft(1)
|
||||
require.Equal(1, b.Len())
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok := b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
_, ok = b.Index(1)
|
||||
require.False(ok)
|
||||
|
||||
got, ok = b.PopLeft()
|
||||
require.Zero(b.Len())
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
|
||||
b.PushLeft(1)
|
||||
require.Equal(1, b.Len())
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PopRight()
|
||||
require.Zero(b.Len())
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
require.Empty(b.List())
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
|
||||
b.PushRight(1)
|
||||
require.Equal(1, b.Len())
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PopRight()
|
||||
require.Zero(b.Len())
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
require.Empty(b.List())
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
|
||||
b.PushRight(1)
|
||||
require.Equal(1, b.Len())
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PopLeft()
|
||||
require.Zero(b.Len())
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
require.Empty(b.List())
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
|
||||
b.PushLeft(1)
|
||||
require.Equal(1, b.Len())
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
b.PushLeft(2)
|
||||
require.Equal(2, b.Len())
|
||||
require.Equal([]int{2, 1}, b.List())
|
||||
|
||||
got, ok = b.PopLeft()
|
||||
require.Equal(1, b.Len())
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PopLeft()
|
||||
require.Zero(b.Len())
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
require.Empty(b.List())
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
|
||||
b.PushRight(1)
|
||||
require.Equal(1, b.Len())
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
b.PushRight(2)
|
||||
require.Equal(2, b.Len())
|
||||
require.Equal([]int{1, 2}, b.List())
|
||||
|
||||
got, ok = b.PopRight()
|
||||
require.Equal(1, b.Len())
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PopRight()
|
||||
require.Zero(b.Len())
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
require.Empty(b.List())
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
|
||||
b.PushLeft(1)
|
||||
require.Equal(1, b.Len())
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
b.PushLeft(2)
|
||||
require.Equal(2, b.Len())
|
||||
require.Equal([]int{2, 1}, b.List())
|
||||
|
||||
got, ok = b.PopRight()
|
||||
require.Equal(1, b.Len())
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
require.Equal([]int{2}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
|
||||
got, ok = b.PopLeft()
|
||||
require.Zero(b.Len())
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
require.Empty(b.List())
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
|
||||
b.PushRight(1)
|
||||
require.Equal(1, b.Len())
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
b.PushLeft(2)
|
||||
require.Equal(2, b.Len())
|
||||
require.Equal([]int{2, 1}, b.List())
|
||||
|
||||
got, ok = b.PopRight()
|
||||
require.Equal(1, b.Len())
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
require.Equal([]int{2}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
|
||||
got, ok = b.PopLeft()
|
||||
require.Zero(b.Len())
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
require.Empty(b.List())
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
|
||||
b.PushLeft(1)
|
||||
require.Equal(1, b.Len())
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
b.PushRight(2)
|
||||
require.Equal(2, b.Len())
|
||||
require.Equal([]int{1, 2}, b.List())
|
||||
|
||||
got, ok = b.PopLeft()
|
||||
require.Equal(1, b.Len())
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
require.Equal([]int{2}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
|
||||
got, ok = b.PopRight()
|
||||
require.Zero(b.Len())
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
require.Empty(b.List())
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
}
|
||||
|
||||
// Cases we test:
|
||||
// 1. [left] moves to the left (no wrap around).
|
||||
// 2. [left] moves to the right (no wrap around).
|
||||
// 3. [left] wrapping around to the left side.
|
||||
// 4. [left] wrapping around to the right side.
|
||||
// 5. Resize.
|
||||
func TestUnboundedSliceDequePushLeftPopLeft(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Starts empty.
|
||||
bIntf := NewUnboundedDeque[int](2)
|
||||
require.IsType(&unboundedSliceDeque[int]{}, bIntf)
|
||||
b := bIntf.(*unboundedSliceDeque[int])
|
||||
require.Zero(bIntf.Len())
|
||||
require.Len(b.data, 2)
|
||||
require.Zero(b.left)
|
||||
require.Equal(1, b.right)
|
||||
require.Empty(b.List())
|
||||
// slice is [EMPTY]
|
||||
|
||||
_, ok := b.PopLeft()
|
||||
require.False(ok)
|
||||
_, ok = b.PeekLeft()
|
||||
require.False(ok)
|
||||
_, ok = b.PeekRight()
|
||||
require.False(ok)
|
||||
|
||||
b.PushLeft(1) // slice is [1,EMPTY]
|
||||
require.Equal(1, b.Len())
|
||||
require.Len(b.data, 2)
|
||||
require.Equal(1, b.left)
|
||||
require.Equal(1, b.right)
|
||||
require.Equal([]int{1}, b.List())
|
||||
|
||||
got, ok := b.PeekLeft()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PeekRight()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
// This causes a resize
|
||||
b.PushLeft(2) // slice is [2,1,EMPTY,EMPTY]
|
||||
require.Equal(2, b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Equal(3, b.left)
|
||||
require.Equal(2, b.right)
|
||||
require.Equal([]int{2, 1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
got, ok = b.Index(1)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PeekLeft()
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
|
||||
got, ok = b.PeekRight()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
// Tests left moving left with no wrap around.
|
||||
b.PushLeft(3) // slice is [2,1,EMPTY,3]
|
||||
require.Equal(3, b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Equal(2, b.left)
|
||||
require.Equal(2, b.right)
|
||||
require.Equal([]int{3, 2, 1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(3, got)
|
||||
got, ok = b.Index(1)
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
got, ok = b.Index(2)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
_, ok = b.Index(3)
|
||||
require.False(ok)
|
||||
|
||||
got, ok = b.PeekLeft()
|
||||
require.True(ok)
|
||||
require.Equal(3, got)
|
||||
|
||||
got, ok = b.PeekRight()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
// Tests left moving right with no wrap around.
|
||||
got, ok = b.PopLeft() // slice is [2,1,EMPTY,EMPTY]
|
||||
require.True(ok)
|
||||
require.Equal(3, got)
|
||||
require.Equal(2, b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Equal(3, b.left)
|
||||
require.Equal(2, b.right)
|
||||
require.Equal([]int{2, 1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
got, ok = b.Index(1)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PeekLeft()
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
|
||||
got, ok = b.PeekRight()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
// Tests left wrapping around to the left side.
|
||||
got, ok = b.PopLeft() // slice is [EMPTY,1,EMPTY,EMPTY]
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
require.Equal(1, b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Zero(b.left)
|
||||
require.Equal(2, b.right)
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PeekLeft()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PeekRight()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
// Test left wrapping around to the right side.
|
||||
b.PushLeft(2) // slice is [2,1,EMPTY,EMPTY]
|
||||
require.Equal(2, b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Equal(3, b.left)
|
||||
require.Equal(2, b.right)
|
||||
require.Equal([]int{2, 1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
got, ok = b.Index(1)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PeekLeft()
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
|
||||
got, ok = b.PeekRight()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PopLeft() // slice is [EMPTY,1,EMPTY,EMPTY]
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
require.Equal(1, b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Zero(b.left)
|
||||
require.Equal(2, b.right)
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PopLeft() // slice is [EMPTY,EMPTY,EMPTY,EMPTY]
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
require.Zero(b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Equal(1, b.left)
|
||||
require.Equal(2, b.right)
|
||||
require.Empty(b.List())
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
|
||||
_, ok = b.PopLeft()
|
||||
require.False(ok)
|
||||
_, ok = b.PeekLeft()
|
||||
require.False(ok)
|
||||
_, ok = b.PeekRight()
|
||||
require.False(ok)
|
||||
}
|
||||
|
||||
func TestUnboundedSliceDequePushRightPopRight(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Starts empty.
|
||||
bIntf := NewUnboundedDeque[int](2)
|
||||
require.IsType(&unboundedSliceDeque[int]{}, bIntf)
|
||||
b := bIntf.(*unboundedSliceDeque[int])
|
||||
require.Zero(bIntf.Len())
|
||||
require.Len(b.data, 2)
|
||||
require.Zero(b.left)
|
||||
require.Equal(1, b.right)
|
||||
require.Empty(b.List())
|
||||
// slice is [EMPTY]
|
||||
|
||||
_, ok := b.PopRight()
|
||||
require.False(ok)
|
||||
_, ok = b.PeekLeft()
|
||||
require.False(ok)
|
||||
_, ok = b.PeekRight()
|
||||
require.False(ok)
|
||||
|
||||
b.PushRight(1) // slice is [1,EMPTY]
|
||||
require.Equal(1, b.Len())
|
||||
require.Len(b.data, 2)
|
||||
require.Zero(b.left)
|
||||
require.Zero(b.right)
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok := b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PeekLeft()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PeekRight()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
// This causes a resize
|
||||
b.PushRight(2) // slice is [1,2,EMPTY,EMPTY]
|
||||
require.Equal(2, b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Equal(3, b.left)
|
||||
require.Equal(2, b.right)
|
||||
require.Equal([]int{1, 2}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
got, ok = b.Index(1)
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
|
||||
got, ok = b.PeekLeft()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PeekRight()
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
|
||||
// Tests right moving right with no wrap around
|
||||
b.PushRight(3) // slice is [1,2,3,EMPTY]
|
||||
require.Equal(3, b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Equal(3, b.left)
|
||||
require.Equal(3, b.right)
|
||||
require.Equal([]int{1, 2, 3}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
got, ok = b.Index(1)
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
got, ok = b.Index(2)
|
||||
require.True(ok)
|
||||
require.Equal(3, got)
|
||||
|
||||
got, ok = b.PeekLeft()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PeekRight()
|
||||
require.True(ok)
|
||||
require.Equal(3, got)
|
||||
|
||||
// Tests right moving left with no wrap around
|
||||
got, ok = b.PopRight() // slice is [1,2,EMPTY,EMPTY]
|
||||
require.True(ok)
|
||||
require.Equal(3, got)
|
||||
require.Equal(2, b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Equal(3, b.left)
|
||||
require.Equal(2, b.right)
|
||||
require.Equal([]int{1, 2}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
got, ok = b.Index(1)
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
_, ok = b.Index(2)
|
||||
require.False(ok)
|
||||
|
||||
got, ok = b.PeekLeft()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PeekRight()
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
|
||||
got, ok = b.PopRight() // slice is [1,EMPTY,EMPTY,EMPTY]
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
require.Equal(1, b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Equal(3, b.left)
|
||||
require.Equal(1, b.right)
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
_, ok = b.Index(1)
|
||||
require.False(ok)
|
||||
|
||||
got, ok = b.PeekLeft()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PeekRight()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PopRight() // slice is [EMPTY,EMPTY,EMPTY,EMPTY]
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
require.Zero(b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Equal(3, b.left)
|
||||
require.Zero(b.right)
|
||||
require.Empty(b.List())
|
||||
require.Zero(b.Len())
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
|
||||
_, ok = b.PeekLeft()
|
||||
require.False(ok)
|
||||
_, ok = b.PeekRight()
|
||||
require.False(ok)
|
||||
_, ok = b.PopRight()
|
||||
require.False(ok)
|
||||
|
||||
b.PushLeft(1) // slice is [EMPTY,EMPTY,EMPTY,1]
|
||||
require.Equal(1, b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Equal(2, b.left)
|
||||
require.Zero(b.right)
|
||||
require.Equal([]int{1}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PeekLeft()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
got, ok = b.PeekRight()
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
|
||||
// Test right wrapping around to the right
|
||||
got, ok = b.PopRight() // slice is [EMPTY,EMPTY,EMPTY,EMPTY]
|
||||
require.True(ok)
|
||||
require.Equal(1, got)
|
||||
require.Zero(b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Equal(2, b.left)
|
||||
require.Equal(3, b.right)
|
||||
require.Empty(b.List())
|
||||
require.Zero(b.Len())
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
|
||||
_, ok = b.PeekLeft()
|
||||
require.False(ok)
|
||||
|
||||
_, ok = b.PeekRight()
|
||||
require.False(ok)
|
||||
|
||||
// Tests right wrapping around to the left
|
||||
b.PushRight(2) // slice is [EMPTY,EMPTY,EMPTY,2]
|
||||
require.Equal(1, b.Len())
|
||||
require.Len(b.data, 4)
|
||||
require.Equal(2, b.left)
|
||||
require.Zero(b.right)
|
||||
require.Equal([]int{2}, b.List())
|
||||
got, ok = b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
|
||||
got, ok = b.PeekLeft()
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
|
||||
got, ok = b.PeekRight()
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
|
||||
got, ok = b.PopRight() // slice is [EMPTY,EMPTY,EMPTY,EMPTY]
|
||||
require.True(ok)
|
||||
require.Equal(2, got)
|
||||
require.Empty(b.List())
|
||||
_, ok = b.Index(0)
|
||||
require.False(ok)
|
||||
|
||||
_, ok = b.PeekLeft()
|
||||
require.False(ok)
|
||||
_, ok = b.PeekRight()
|
||||
require.False(ok)
|
||||
_, ok = b.PopRight()
|
||||
require.False(ok)
|
||||
}
|
||||
|
||||
func FuzzUnboundedSliceDeque(f *testing.F) {
|
||||
f.Fuzz(
|
||||
func(t *testing.T, initSize uint, input []byte) {
|
||||
require := require.New(t)
|
||||
b := NewUnboundedDeque[byte](int(initSize))
|
||||
for i, n := range input {
|
||||
b.PushRight(n)
|
||||
gotIndex, ok := b.Index(i)
|
||||
require.True(ok)
|
||||
require.Equal(n, gotIndex)
|
||||
}
|
||||
|
||||
list := b.List()
|
||||
require.Len(list, len(input))
|
||||
for i, n := range input {
|
||||
require.Equal(n, list[i])
|
||||
}
|
||||
|
||||
for i := 0; i < len(input); i++ {
|
||||
_, _ = b.PopLeft()
|
||||
list = b.List()
|
||||
if i == len(input)-1 {
|
||||
require.Empty(list)
|
||||
_, ok := b.Index(0)
|
||||
require.False(ok)
|
||||
} else {
|
||||
require.Equal(input[i+1:], list)
|
||||
got, ok := b.Index(0)
|
||||
require.True(ok)
|
||||
require.Equal(input[i+1], got)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user