diff --git a/README.md b/README.md index 3fe4785..f891834 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ func main() { // Print: hello bar world for i := 0; i < q.Len(); i++ { - fmt.Print(q.PeekAt(i), " ") + fmt.Print(q.Get(i), " ") } fmt.Println() } diff --git a/deque.go b/deque.go index fb61e38..94b8b99 100644 --- a/deque.go +++ b/deque.go @@ -123,23 +123,109 @@ func (q *Deque) Back() interface{} { return q.buf[(q.tail-1)&(len(q.buf)-1)] } -// PeekAt returns the element at index i in the queue. This method accepts -// both positive and negative index values. PeekAt(0) refers to the first -// (earliest added) element and is the same as Front(). PeekAt(-1) refers to -// the last (latest added) element and is the same as Back(). If the index -// is invalid, the call panics. -func (q *Deque) PeekAt(i int) interface{} { - // If indexing backwards, convert to positive index. - if i < 0 { - i += q.count - } +// Get returns the element at index i in the queue. This method accepts only +// non-negative index values. Get(0) refers to the first element and is the +// same as Front(). Get(Len()-1) refers to the last element and is the same +// as Back(). If the index is invalid, the call panics. +func (q *Deque) Get(i int) interface{} { if i < 0 || i >= q.count { - panic("deque: PeekAt() called with index out of range") + panic("deque: Get() called with index out of range") } // bitwise modulus return q.buf[(q.head+i)&(len(q.buf)-1)] } +// Insert is used to insert an element into the middle of the queue. +// 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. +func (q *Deque) Insert(i int, elem interface{}) { + if i < 0 || i > q.count { + panic("deque: Insert() called with index out of range") + } + if i == 0 { + q.PushFront(elem) + return + } + if i == q.count { + q.PushBack(elem) + return + } + if i <= q.count/2 { + // If inserting closer to front, rotate front to back i places, put + // element at front, then rotate back to front i places. + for j := 0; j < i; j++ { + q.PushBack(q.PopFront()) + } + q.PushFront(elem) + for j := 0; j < i; j++ { + q.PushFront(q.PopBack()) + } + } else { + // If inserting closer to back, rotate back to front Len() - i places, + // put element at back, then rotate front to back same amount. + rots := q.count - i + for j := 0; j < rots; j++ { + q.PushFront(q.PopBack()) + } + q.PushBack(elem) + for j := 0; j < rots; j++ { + q.PushBack(q.PopFront()) + } + } +} + +// Remove removes and returns an element from the middle of the queue. +// Remove(0) is the same as PopFront() and Remove(Len()-1) is the same as +// PopBack(). Accepts only non-negative index values, and panics if index is +// out of range. +func (q *Deque) Remove(i int) interface{} { + if i < 0 || i >= q.count { + panic("deque: Remove() called with index out of range") + } + if i == 0 { + return q.PopFront() + } + if i == q.count-1 { + return q.PopBack() + } + var elem interface{} + if i <= q.count/2 { + // If removing closer to front, rotate front to back i places, remove + // element at front, then rotate back to front i places. + for j := 0; j < i; j++ { + q.PushBack(q.PopFront()) + } + elem = q.PopFront() + for j := 0; j < i; j++ { + q.PushFront(q.PopBack()) + } + } else { + // If removing closer to back, rotate back to front Len() - 1 - i + // places, remove element at back, then rotate front to back same + // amount. + rots := (q.count - 1) - i + for j := 0; j < rots; j++ { + q.PushFront(q.PopBack()) + } + elem = q.PopBack() + for j := 0; j < rots; j++ { + q.PushBack(q.PopFront()) + } + } + return elem +} + +// Replace replaces the element at position i with the given element. Accepts +// only non-negative index values, and panics if index is out of range +func (q *Deque) Replace(i int, elem interface{}) { + if i < 0 || i >= q.count { + panic("deque: Remove() called with index out of range") + } + // bitwise modulus + q.buf[(q.head+i)&(len(q.buf)-1)] = elem +} + // Clear removes all elements from the queue, but retains the current capacity. // The queue will not be resized smaller as long as items are only added. // Only when items are removed is the queue subject to getting resized smaller. diff --git a/deque_test.go b/deque_test.go index 83216cc..1d2b87b 100644 --- a/deque_test.go +++ b/deque_test.go @@ -52,8 +52,8 @@ func TestGrowShrink(t *testing.T) { } // Check that all values are as expected. for i := 0; i < minCapacity*2; i++ { - if q.PeekAt(i) != i { - t.Errorf("q.PeekAt(%d) = %d, expected %d", i, q.PeekAt(i), i) + if q.Get(i) != i { + t.Errorf("q.Get(%d) = %d, expected %d", i, q.Get(i), i) } } bufLen := len(q.buf) @@ -102,8 +102,8 @@ func TestGrowShrink(t *testing.T) { } } -func TestDequeSimple(t *testing.T) { - q := new(Deque) +func TestSimple(t *testing.T) { + var q Deque for i := 0; i < minCapacity; i++ { q.PushBack(i) @@ -130,8 +130,8 @@ func TestDequeSimple(t *testing.T) { } } -func TestDequeWrap(t *testing.T) { - q := new(Deque) +func TestBufferWrap(t *testing.T) { + var q Deque for i := 0; i < minCapacity; i++ { q.PushBack(i) @@ -150,8 +150,8 @@ func TestDequeWrap(t *testing.T) { } } -func TestDequeWrapReverse(t *testing.T) { - q := new(Deque) +func TestBufferWrapReverse(t *testing.T) { + var q Deque for i := 0; i < minCapacity; i++ { q.PushFront(i) @@ -169,8 +169,8 @@ func TestDequeWrapReverse(t *testing.T) { } } -func TestDequeLen(t *testing.T) { - q := new(Deque) +func TestLen(t *testing.T) { + var q Deque if q.Len() != 0 { t.Error("empty queue length not 0") @@ -190,34 +190,30 @@ func TestDequeLen(t *testing.T) { } } -func TestDequePeekAt(t *testing.T) { - q := new(Deque) +func TestGet(t *testing.T) { + var q Deque for i := 0; i < 1000; i++ { q.PushBack(i) - for j := 0; j < q.Len(); j++ { - if q.PeekAt(j).(int) != j { - t.Errorf("index %d doesn't contain %d", j, j) - } + } + + // Front to back. + for j := 0; j < q.Len(); j++ { + if q.Get(j).(int) != j { + t.Errorf("index %d doesn't contain %d", j, j) + } + } + + // Back to front + for j := 1; j <= q.Len(); j++ { + if q.Get(q.Len()-j).(int) != q.Len()-j { + t.Errorf("index %d doesn't contain %d", q.Len()-j, q.Len()-j) } } } -func TestDequePeekAtNegative(t *testing.T) { - q := new(Deque) - - for i := 0; i < 1000; i++ { - q.PushBack(i) - for j := 1; j <= q.Len(); j++ { - if q.PeekAt(-j).(int) != q.Len()-j { - t.Errorf("index %d doesn't contain %d", -j, q.Len()-j) - } - } - } -} - -func TestDequeBack(t *testing.T) { - q := new(Deque) +func TestBack(t *testing.T) { + var q Deque for i := 0; i < minCapacity+5; i++ { q.PushBack(i) @@ -228,7 +224,7 @@ func TestDequeBack(t *testing.T) { } func TestCopy(t *testing.T) { - q := new(Deque) + var q Deque a := make([]interface{}, minCapacity) if q.Copy(a) != 0 { t.Error("Copied wrong size, expected 0") @@ -265,7 +261,7 @@ func TestCopy(t *testing.T) { } func TestRotate(t *testing.T) { - q := new(Deque) + var q Deque for i := 0; i < 10; i++ { q.PushBack(i) } @@ -300,8 +296,8 @@ func TestRotate(t *testing.T) { } } -func TestDequeClear(t *testing.T) { - q := new(Deque) +func TestClear(t *testing.T) { + var q Deque for i := 0; i < 100; i++ { q.PushBack(i) @@ -327,25 +323,119 @@ func TestDequeClear(t *testing.T) { } } -func TestPeekAtOutOfRangePanics(t *testing.T) { - q := new(Deque) +func TestInsert(t *testing.T) { + var q Deque + q.PushBack("A") + q.PushBack("B") + q.PushBack("C") + q.PushBack("D") + q.PushBack("E") + q.PushBack("F") + q.PushBack("G") + + q.Insert(4, "x") + if q.Get(4) != "x" { + t.Error("expected x at position 4") + } + + q.Insert(2, "y") + if q.Get(2) != "y" { + t.Error("expected y at position 2") + } + + if q.Get(5) != "x" { + t.Error("expected x at position 5") + } + + q.Insert(0, "b") + if q.Front() != "b" { + t.Error("expected b inserted at front") + } + + q.Insert(q.Len(), "e") + if q.Back() != "e" { + t.Error("expected e inserted at back") + } +} + +func TestRemove(t *testing.T) { + var q Deque + q.PushBack("A") + q.PushBack("B") + q.PushBack("C") + q.PushBack("D") + q.PushBack("E") + q.PushBack("F") + q.PushBack("G") + + if q.Remove(4) != "E" { + t.Error("expected E from position 4") + } + if q.Get(4) != "F" { + t.Error("expected F at position 4") + } + + if q.Remove(2) != "C" { + t.Error("expected C at position 2") + } + if q.Get(2) != "D" { + t.Error("expected D at position 4") + } + + if q.Get(4) != "G" { + t.Error("expected G at position 4") + } + + if q.Remove(0) != "A" { + t.Error("expected to remove A from front") + } + + if q.Remove(q.Len()-1) != "G" { + t.Error("expected to remove G from back") + } +} + +func TestReplace(t *testing.T) { + var q Deque + q.PushBack("a") + q.PushBack("b") + q.PushBack("c") + + q.Replace(0, "A") + if q.Front() != "A" { + t.Error("expected A at front") + } + + q.Replace(q.Len()-1, "C") + if q.Back() != "C" { + t.Error("expected C at back") + } + + q.Replace(1, "-") + if q.Get(1) != "-" { + t.Error("expected - at position 1") + } +} + +func TestGetOutOfRangePanics(t *testing.T) { + var q Deque q.PushBack(1) q.PushBack(2) q.PushBack(3) assertPanics(t, "should panic when negative index", func() { - q.PeekAt(-4) + q.Get(-4) }) assertPanics(t, "should panic when index greater than length", func() { - q.PeekAt(4) + q.Get(4) }) } func TestFrontBackOutOfRangePanics(t *testing.T) { const msg = "should panic when peeking empty queue" - q := new(Deque) + var q Deque assertPanics(t, msg, func() { q.Front() }) @@ -365,7 +455,7 @@ func TestFrontBackOutOfRangePanics(t *testing.T) { } func TestPopFrontOutOfRangePanics(t *testing.T) { - q := new(Deque) + var q Deque assertPanics(t, "should panic when removing empty queue", func() { q.PopFront() @@ -380,7 +470,7 @@ func TestPopFrontOutOfRangePanics(t *testing.T) { } func TestPopBackOutOfRangePanics(t *testing.T) { - q := new(Deque) + var q Deque assertPanics(t, "should panic when removing empty queue", func() { q.PopBack() @@ -394,6 +484,60 @@ func TestPopBackOutOfRangePanics(t *testing.T) { }) } +func TestInsertOutOfRangePanics(t *testing.T) { + var q Deque + + assertPanics(t, "should panic when inserting out of range", func() { + q.Insert(1, "X") + }) + + q.PushBack("A") + + assertPanics(t, "should panic when inserting at negative index", func() { + q.Insert(-1, "Y") + }) + + assertPanics(t, "should panic when inserting out of range", func() { + q.Insert(2, "B") + }) +} + +func TestRemoveOutOfRangePanics(t *testing.T) { + var q Deque + + assertPanics(t, "should panic when removing from empty queue", func() { + q.Remove(0) + }) + + q.PushBack("A") + + assertPanics(t, "should panic when removing at negative index", func() { + q.Remove(-1) + }) + + assertPanics(t, "should panic when removing out of range", func() { + q.Remove(1) + }) +} + +func TestReplaceOutOfRangePanics(t *testing.T) { + var q Deque + + assertPanics(t, "should panic when replacing in empty queue", func() { + q.Replace(0, "x") + }) + + q.PushBack("A") + + assertPanics(t, "should panic when replacing at negative index", func() { + q.Replace(-1, "Z") + }) + + assertPanics(t, "should panic when replacing out of range", func() { + q.Replace(1, "Y") + }) +} + func assertPanics(t *testing.T, name string, f func()) { defer func() { if r := recover(); r == nil { @@ -456,7 +600,7 @@ func BenchmarkSerialReverse(b *testing.B) { } func BenchmarkRotate(b *testing.B) { - q := new(Deque) + var q Deque for i := 0; i < size; i++ { q.PushBack(i) } @@ -470,7 +614,7 @@ func BenchmarkRotate(b *testing.B) { } func BenchmarkRotateReverse(b *testing.B) { - q := new(Deque) + var q Deque for i := 0; i < size; i++ { q.PushBack(i) } @@ -483,21 +627,21 @@ func BenchmarkRotateReverse(b *testing.B) { } } -func BenchmarkDequePeekAt(b *testing.B) { - q := new(Deque) +func BenchmarkDequeGet(b *testing.B) { + var q Deque for i := 0; i < size; i++ { q.PushBack(i) } b.ResetTimer() for i := 0; i < b.N; i++ { for j := 0; j < size; j++ { - q.PeekAt(j) + q.Get(j) } } } func BenchmarkDequePushPop(b *testing.B) { - q := new(Deque) + var q Deque for i := 0; i < b.N; i++ { for n := 0; n < size; n++ { q.PushBack(nil) diff --git a/go.test.sh b/go.test.sh index 34dbbfb..494b176 100755 --- a/go.test.sh +++ b/go.test.sh @@ -4,7 +4,7 @@ set -e echo "" > coverage.txt for d in $(go list ./... | grep -v vendor); do - go test -race -coverprofile=profile.out -covermode=atomic $d + go test -coverprofile=profile.out -covermode=atomic $d if [ -f profile.out ]; then cat profile.out >> coverage.txt rm profile.out