Skip to content

Commit

Permalink
generic queue (#1)
Browse files Browse the repository at this point in the history
* use generic queue

* Drop unused linter
  • Loading branch information
dbrajovic authored Jul 7, 2024
1 parent 295d334 commit 2fab362
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 63 deletions.
1 change: 0 additions & 1 deletion .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ linters:
- gosimple # Code simplification
- govet # Official Go tool
- ineffassign # Detects when assignments to existing variables are not used
- interfacer # Top-level interface suggestions
- nakedret # Finds naked/bare returns and requires change them
- nilerr # Requires explicit returns
- nilnil # Requires explicit returns
Expand Down
14 changes: 7 additions & 7 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ func benchmarkPush(b *testing.B, items []*mockItem) {
b.Helper()

for i := 0; i < b.N; i++ {
q := NewQueue()
q := NewQueue[*mockItem]()
for _, item := range items {
q.Push(item)
}
Expand All @@ -32,12 +32,12 @@ func BenchmarkHeap_Push100(b *testing.B) {
func benchmarkPop(
b *testing.B,
items []*mockItem,
popCallback func(q *Queue),
popCallback func(q *Queue[*mockItem]),
) {
b.Helper()

for i := 0; i < b.N; i++ {
q := NewQueue()
q := NewQueue[*mockItem]()

b.StopTimer()

Expand All @@ -57,7 +57,7 @@ func BenchmarkHeap_PopFront10(b *testing.B) {
items := generateRandomItems(10)

b.ResetTimer()
benchmarkPop(b, items, func(q *Queue) {
benchmarkPop(b, items, func(q *Queue[*mockItem]) {
q.PopFront()
})
}
Expand All @@ -66,7 +66,7 @@ func BenchmarkHeap_PopFront100(b *testing.B) {
items := generateRandomItems(100)

b.ResetTimer()
benchmarkPop(b, items, func(q *Queue) {
benchmarkPop(b, items, func(q *Queue[*mockItem]) {
q.PopFront()
})
}
Expand All @@ -75,7 +75,7 @@ func BenchmarkHeap_PopBack10(b *testing.B) {
items := generateRandomItems(10)

b.ResetTimer()
benchmarkPop(b, items, func(q *Queue) {
benchmarkPop(b, items, func(q *Queue[*mockItem]) {
q.PopBack()
})
}
Expand All @@ -84,7 +84,7 @@ func BenchmarkHeap_PopBack100(b *testing.B) {
items := generateRandomItems(100)

b.ResetTimer()
benchmarkPop(b, items, func(q *Queue) {
benchmarkPop(b, items, func(q *Queue[*mockItem]) {
q.PopBack()
})
}
20 changes: 10 additions & 10 deletions examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,47 @@ type number struct {
value int
}

func (i number) Less(other Item) bool {
return i.value < other.(number).value
func (i number) Less(other number) bool {
return i.value < other.value
}

func ExampleQueue_Push() {
q := NewQueue()
q := NewQueue[number]()

q.Push(number{1}) // 1
q.Push(number{2}) // 2
q.Push(number{3}) // 3

fmt.Println(q[0].(number).value)
fmt.Println(q[0].value)
// Output: 1
}

func ExampleQueue_PopFront() {
q := NewQueue()
q := NewQueue[number]()

q.Push(number{1}) // 1
q.Push(number{2}) // 2
q.Push(number{3}) // 3

popped := q.PopFront()
fmt.Println(popped.(number).value)
fmt.Println(popped.value)
// Output: 1
}

func ExampleQueue_PopBack() {
q := NewQueue()
q := NewQueue[number]()

q.Push(number{1}) // 1
q.Push(number{2}) // 2
q.Push(number{3}) // 3

popped := q.PopBack()
fmt.Println(popped.(number).value)
fmt.Println(popped.value)
// Output: 3
}

func ExampleQueue_Fix() {
q := NewQueue()
q := NewQueue[number]()

q.Push(number{1}) // 1
q.Push(number{2}) // 2
Expand All @@ -55,6 +55,6 @@ func ExampleQueue_Fix() {
q[0] = number{4} // 1 -> 4
q.Fix()

fmt.Println(q[0].(number).value)
fmt.Println(q[0].value)
// Output: 2
}
11 changes: 3 additions & 8 deletions mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package queue

import "fmt"

type lessDelegate func(Item) bool
type lessDelegate func(item *mockItem) bool

// mockItem is a mockable Item
type mockItem struct {
Expand All @@ -11,17 +11,12 @@ type mockItem struct {
value int
}

func (m *mockItem) Less(i Item) bool {
func (m *mockItem) Less(i *mockItem) bool {
if m.lessFn != nil {
return m.lessFn(i)
}

other, ok := i.(*mockItem)
if !ok {
return false
}

return m.value < other.value
return m.value <= i.value
}

func (m *mockItem) String() string {
Expand Down
22 changes: 11 additions & 11 deletions queue.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
package queue

// Queue is the priority queue based on insertion sort
type Queue []Item
type Queue[T Item[T]] []T

// NewQueue creates an instance of the priority queue
func NewQueue() Queue {
return make(Queue, 0)
func NewQueue[T Item[T]]() Queue[T] {
return make(Queue[T], 0)
}

// Len returns the length of the queue
func (q *Queue) Len() int {
func (q *Queue[T]) Len() int {
return len(*q)
}

// Index returns the element at the specified index, if any.
// NOTE: panics if out of bounds
func (q *Queue) Index(index int) Item {
func (q *Queue[T]) Index(index int) T {
return (*q)[index]
}

// Push adds a new element to the priority queue
func (q *Queue) Push(item Item) {
func (q *Queue[T]) Push(item T) {
*q = append(*q, item)
for i := len(*q) - 1; i > 0; i-- {
if (*q)[i].Less((*q)[i-1]) {
Expand All @@ -33,7 +33,7 @@ func (q *Queue) Push(item Item) {
}

// Fix makes sure the priority queue is properly sorted
func (q *Queue) Fix() {
func (q *Queue[T]) Fix() {
for i := 1; i < len(*q); i++ {
for j := i - 1; j >= 0; j-- {
if (*q)[j].Less((*q)[j+1]) {
Expand All @@ -46,25 +46,25 @@ func (q *Queue) Fix() {
}

// PopFront removes the first element in the queue, if any
func (q *Queue) PopFront() Item {
func (q *Queue[T]) PopFront() *T {
if len(*q) == 0 {
return nil
}

el := (*q)[0]
*q = (*q)[1:]

return el
return &el
}

// PopBack removes the last element in the queue, if any
func (q *Queue) PopBack() Item {
func (q *Queue[T]) PopBack() *T {
if len(*q) == 0 {
return nil
}

el := (*q)[len(*q)-1]
*q = (*q)[:len(*q)-1]

return el
return &el
}
36 changes: 12 additions & 24 deletions queue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,9 @@ import (
)

// isSorted checks if the queue is sorted
func isSorted(q Queue, ascending bool) bool {
cmp := func(a, b *mockItem) bool {
return a.value <= b.value
}

if !ascending {
cmp = func(a, b *mockItem) bool {
return a.value >= b.value
}
}

func isSorted[T Item[T]](q Queue[T]) bool {
for i := 0; i < len(q)-1; i++ {
if !cmp(q[i].(*mockItem), q[i+1].(*mockItem)) {
if !q[i].Less(q[i+1]) {
return false
}
}
Expand Down Expand Up @@ -76,26 +66,24 @@ func TestQueue_Insert(t *testing.T) {
)

// Create a new queue
q := NewQueue()
q := NewQueue[*mockItem]()

// Push items
for _, item := range items {
item := item

if !testCase.ascending {
// Prep the items if needed (min / max)
item.lessFn = func(i Item) bool {
other, _ := i.(*mockItem)

return item.value >= other.value
item.lessFn = func(i *mockItem) bool {
return item.value >= i.value
}
}

q.Push(item)
}

assert.Len(t, q, numItems)
assert.True(t, isSorted(q, testCase.ascending))
assert.True(t, isSorted(q))
})
}
}
Expand All @@ -109,7 +97,7 @@ func TestQueue_PopFront(t *testing.T) {
)

// Create a new queue
q := NewQueue()
q := NewQueue[*mockItem]()

// Push items
for _, item := range items {
Expand All @@ -127,7 +115,7 @@ func TestQueue_PopFront(t *testing.T) {
for _, item := range items {
popped := q.PopFront()

assert.Equal(t, item, popped)
assert.Equal(t, item, *popped)
}

assert.Len(t, q, 0)
Expand All @@ -143,7 +131,7 @@ func TestQueue_PopBack(t *testing.T) {
)

// Create a new queue
q := NewQueue()
q := NewQueue[*mockItem]()

// Push items
for _, item := range items {
Expand All @@ -161,7 +149,7 @@ func TestQueue_PopBack(t *testing.T) {
for i := len(items) - 1; i >= 0; i-- {
popped := q.PopBack()

assert.Equal(t, items[i], popped)
assert.Equal(t, items[i], *popped)
}

assert.Len(t, q, 0)
Expand All @@ -177,7 +165,7 @@ func TestQueue_Fix(t *testing.T) {
)

// Create a new queue
q := NewQueue()
q := NewQueue[*mockItem]()

// Push items
for _, item := range items {
Expand All @@ -197,6 +185,6 @@ func TestQueue_Fix(t *testing.T) {
q.Fix()

assert.Equal(t, q.Len(), numItems)
assert.True(t, isSorted(q, true))
assert.True(t, isSorted(q))
assert.Equal(t, newItem, q.Index(0))
}
4 changes: 2 additions & 2 deletions types.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package queue

// Item represents a single queue item
type Item interface {
type Item[T any] interface {
// Less returns a flag indicating if the element
// has a lower value than another element.
// For max-priority queue implementations, Less should return true if A > B
Less(b Item) bool
Less(b T) bool
}

0 comments on commit 2fab362

Please sign in to comment.