From 81826a99cdf7a9c5da8199f0f365404ba489a7f2 Mon Sep 17 00:00:00 2001 From: Andrew Gillis <11790789+gammazero@users.noreply.github.com> Date: Wed, 13 Nov 2024 11:14:52 -1000 Subject: [PATCH] Add Swap function and allow Insert with out of range indexes (#29) * Add Swap function and allow Insert with out of range indexes --- deque.go | 50 ++++++++++++++++++++++++++--------------- deque_test.go | 62 ++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 83 insertions(+), 29 deletions(-) diff --git a/deque.go b/deque.go index a1743c1..522d54d 100644 --- a/deque.go +++ b/deque.go @@ -172,9 +172,7 @@ func (q *Deque[T]) Back() T { // and when full the oldest is popped from the other end. All the log entries // in the buffer must be readable without altering the buffer contents. func (q *Deque[T]) At(i int) T { - if i < 0 || i >= q.count { - panic(outOfRangeText(i, q.Len())) - } + q.checkRange(i) // bitwise modulus return q.buf[(q.head+i)&(len(q.buf)-1)] } @@ -183,9 +181,7 @@ func (q *Deque[T]) At(i int) T { // as At but perform the opposite operation. If the index is invalid, the call // panics. func (q *Deque[T]) Set(i int, item T) { - if i < 0 || i >= q.count { - panic(outOfRangeText(i, q.Len())) - } + q.checkRange(i) // bitwise modulus q.buf[(q.head+i)&(len(q.buf)-1)] = item } @@ -288,16 +284,21 @@ func (q *Deque[T]) RIndex(f func(T) bool) int { // Insert is used to insert an element into the middle of the queue, before the // element at the specified index. Insert(0,e) is the same as PushFront(e) and -// Insert(Len(),e) is the same as PushBack(e). Accepts only non-negative index -// values, and panics if index is out of range. +// Insert(Len(),e) is the same as PushBack(e). Out of range indexes result in +// pushing the item onto the front of back of the deque. // // Important: Deque is optimized for O(1) operations at the ends of the queue, // not for operations in the the middle. Complexity of this function is // constant plus linear in the lesser of the distances between the index and // either of the ends of the queue. func (q *Deque[T]) Insert(at int, item T) { - if at < 0 || at > q.count { - panic(outOfRangeText(at, q.Len())) + if at <= 0 { + q.PushFront(item) + return + } + if at >= q.Len() { + q.PushBack(item) + return } if at*2 < q.count { q.PushFront(item) @@ -329,10 +330,7 @@ func (q *Deque[T]) Insert(at int, item T) { // constant plus linear in the lesser of the distances between the index and // either of the ends of the queue. func (q *Deque[T]) Remove(at int) T { - if at < 0 || at >= q.Len() { - panic(outOfRangeText(at, q.Len())) - } - + q.checkRange(at) rm := (q.head + at) & (len(q.buf) - 1) if at*2 < q.count { for i := 0; i < at; i++ { @@ -366,6 +364,26 @@ func (q *Deque[T]) SetMinCapacity(minCapacityExp uint) { } } +// Swap exchanges the two values at idxA and idxB. It panics if either index is +// out of range. +func (q *Deque[T]) Swap(idxA, idxB int) { + q.checkRange(idxA) + q.checkRange(idxB) + if idxA == idxB { + return + } + + realA := (q.head + idxA) & (len(q.buf) - 1) + realB := (q.head + idxB) & (len(q.buf) - 1) + q.buf[realA], q.buf[realB] = q.buf[realB], q.buf[realA] +} + +func (q *Deque[T]) checkRange(i int) { + if i < 0 || i >= q.count { + panic(fmt.Sprintf("deque: index out of range %d with length %d", i, q.Len())) + } +} + // prev returns the previous buffer position wrapping around buffer. func (q *Deque[T]) prev(i int) int { return (i - 1) & (len(q.buf) - 1) // bitwise modulus @@ -414,7 +432,3 @@ func (q *Deque[T]) resize() { q.tail = q.count q.buf = newBuf } - -func outOfRangeText(i, len int) string { - return fmt.Sprintf("deque: index out of range %d with length %d", i, len) -} diff --git a/deque_test.go b/deque_test.go index 771292f..396fbb2 100644 --- a/deque_test.go +++ b/deque_test.go @@ -601,6 +601,42 @@ func TestRemove(t *testing.T) { } } +func TestSwap(t *testing.T) { + var q Deque[string] + + q.PushBack("a") + q.PushBack("b") + q.PushBack("c") + q.PushBack("d") + q.PushBack("e") + + q.Swap(0, 4) + if q.Front() != "e" { + t.Fatal("wrong value at front") + } + if q.Back() != "a" { + t.Fatal("wrong value at back") + } + q.Swap(3, 1) + if q.At(1) != "d" { + t.Fatal("wrong value at index 1") + } + if q.At(3) != "b" { + t.Fatal("wrong value at index 3") + } + q.Swap(2, 2) + if q.At(2) != "c" { + t.Fatal("wrong value at index 2") + } + + assertPanics(t, "should panic when removing out of range", func() { + q.Swap(1, 5) + }) + assertPanics(t, "should panic when removing out of range", func() { + q.Swap(5, 1) + }) +} + func TestFrontBackOutOfRangePanics(t *testing.T) { const msg = "should panic when peeking empty queue" var q Deque[int] @@ -687,19 +723,23 @@ func TestSetOutOfRangePanics(t *testing.T) { func TestInsertOutOfRangePanics(t *testing.T) { q := new(Deque[string]) - assertPanics(t, "should panic when inserting out of range", func() { - q.Insert(1, "X") - }) - - q.PushBack("A") + q.Insert(1, "A") + if q.Front() != "A" { + t.Fatal("expected A at front") + } + if q.Back() != "A" { + t.Fatal("expected A at back") + } - assertPanics(t, "should panic when inserting at negative index", func() { - q.Insert(-1, "Y") - }) + q.Insert(-1, "B") + if q.Front() != "B" { + t.Fatal("expected B at front") + } - assertPanics(t, "should panic when inserting out of range", func() { - q.Insert(2, "B") - }) + q.Insert(999, "C") + if q.Back() != "C" { + t.Fatal("expected C at back") + } } func TestRemoveOutOfRangePanics(t *testing.T) {