From fc4cef0a81082421e6217a1e284ba03833e594c1 Mon Sep 17 00:00:00 2001 From: Josh Baker Date: Mon, 1 May 2017 12:53:51 -0700 Subject: [PATCH] first commit --- LICENSE | 15 +++++++++ README.md | 7 ++++ tinyqueue.go | 84 +++++++++++++++++++++++++++++++++++++++++++++++ tinyqueue_test.go | 65 ++++++++++++++++++++++++++++++++++++ 4 files changed, 171 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 tinyqueue.go create mode 100644 tinyqueue_test.go diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2b7cd9d --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2017, Vladimir Agafonkin + +Permission to use, copy, modify, and/or distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright notice +and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS +OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f4edc91 --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# tinyqueue +GoDoc + +tinyqueue is a Go package for binary heap priority queues. +Ported from the [tinyqueue](https://github.com/mourner/tinyqueue) Javascript library. + + diff --git a/tinyqueue.go b/tinyqueue.go new file mode 100644 index 0000000..4cc2bb4 --- /dev/null +++ b/tinyqueue.go @@ -0,0 +1,84 @@ +package tinyqueue + +type Queue struct { + length int + data []Item +} + +type Item interface { + Less(Item) bool +} + +func New(data []Item) *Queue { + q := &Queue{} + q.data = data + q.length = len(data) + if q.length > 0 { + i := q.length >> 1 + for ; i >= 0; i-- { + q.down(i) + } + } + return q +} + +func (q *Queue) Push(item Item) { + q.data = append(q.data, item) + q.length++ + q.up(q.length - 1) +} +func (q *Queue) Pop() Item { + if q.length == 0 { + return nil + } + top := q.data[0] + q.length-- + if q.length > 0 { + q.data[0] = q.data[q.length] + q.down(0) + } + q.data = q.data[:len(q.data)-1] + return top +} +func (q *Queue) Peek() Item { + if q.length == 0 { + return nil + } + return q.data[0] +} + +func (q *Queue) down(pos int) { + data := q.data + halfLength := q.length >> 1 + item := data[pos] + for pos < halfLength { + left := (pos << 1) + 1 + right := left + 1 + best := data[left] + if right < q.length && data[right].Less(best) { + left = right + best = data[right] + } + if !best.Less(item) { + break + } + data[pos] = best + pos = left + } + data[pos] = item +} + +func (q *Queue) up(pos int) { + data := q.data + item := data[pos] + for pos > 0 { + parent := (pos - 1) >> 1 + current := data[parent] + if !item.Less(current) { + break + } + data[pos] = current + pos = parent + } + data[pos] = item +} diff --git a/tinyqueue_test.go b/tinyqueue_test.go new file mode 100644 index 0000000..9d34df4 --- /dev/null +++ b/tinyqueue_test.go @@ -0,0 +1,65 @@ +package tinyqueue + +import ( + "math/rand" + "sort" + "testing" + "time" + + "github.com/json-iterator/go/assert" +) + +type floatValue float64 + +func (a floatValue) Less(b Item) bool { + return a < b.(floatValue) +} + +var data, sorted = func() ([]Item, []Item) { + rand.Seed(time.Now().UnixNano()) + var data []Item + for i := 0; i < 100; i++ { + data = append(data, floatValue(rand.Float64()*100)) + } + sorted := make([]Item, len(data)) + copy(sorted, data) + sort.Slice(sorted, func(i, j int) bool { + return sorted[i].Less(sorted[j]) + }) + return data, sorted +}() + +func TestMaintainsPriorityQueue(t *testing.T) { + q := New(nil) + for i := 0; i < len(data); i++ { + q.Push(data[i]) + } + assert.Equal(t, q.Peek(), sorted[0]) + var result []Item + for q.length > 0 { + result = append(result, q.Pop()) + } + assert.Equal(t, result, sorted) +} + +func TestAcceptsDataInConstructor(t *testing.T) { + q := New(data) + var result []Item + for q.length > 0 { + result = append(result, q.Pop()) + } + assert.Equal(t, result, sorted) +} +func TestHandlesEdgeCasesWithFewElements(t *testing.T) { + q := New(nil) + q.Push(floatValue(2)) + q.Push(floatValue(1)) + q.Pop() + q.Pop() + q.Pop() + q.Push(floatValue(2)) + q.Push(floatValue(1)) + assert.Equal(t, float64(q.Pop().(floatValue)), 1.0) + assert.Equal(t, float64(q.Pop().(floatValue)), 2.0) + assert.Equal(t, q.Pop(), nil) +}