Skip to content

Commit

Permalink
Add Swap function and allow Insert with out of range indexes (#29)
Browse files Browse the repository at this point in the history
* Add Swap function and allow Insert with out of range indexes
  • Loading branch information
gammazero authored Nov 13, 2024
1 parent ac07eb9 commit 81826a9
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 29 deletions.
50 changes: 32 additions & 18 deletions deque.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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++ {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
62 changes: 51 additions & 11 deletions deque_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 81826a9

Please sign in to comment.