indexed priority queue
This commit is contained in:
		
							parent
							
								
									e161de1881
								
							
						
					
					
						commit
						bee59b5d0b
					
				
					 5 changed files with 319 additions and 27 deletions
				
			
		
							
								
								
									
										133
									
								
								sorting/priority_queue/index_priority_queue.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								sorting/priority_queue/index_priority_queue.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,133 @@ | ||||||
|  | package priority_queue | ||||||
|  | 
 | ||||||
|  | // TODO: change name to "keyed priority queue" | ||||||
|  | type IndexPriorityQueue[T any] interface { | ||||||
|  | 	top() T                 // get item with biggest priority | ||||||
|  | 	topKey() int            // get key of an item with biggest priority | ||||||
|  | 	remove() T              // removes item with the biggest priority | ||||||
|  | 	removeKey(key int) T    // removes item with specified key | ||||||
|  | 	insert(key int, item T) // adds item with specified key | ||||||
|  | 	change(key int, item T) // changes item with specified key | ||||||
|  | 	contains(key int) bool  // checks if key exists in queue | ||||||
|  | 	isEmpty() bool | ||||||
|  | 	size() int | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type indexPriorityQueue[T any] struct { | ||||||
|  | 	n int | ||||||
|  | 	// unordered items. | ||||||
|  | 	// items[key] = item | ||||||
|  | 	items []T | ||||||
|  | 	// priority queue. Contains keys for items in priority order. | ||||||
|  | 	// items[pq[1]] = item with biggest priority | ||||||
|  | 	pq []int | ||||||
|  | 	// "reverse" for pq. Maps item key to priority | ||||||
|  | 	// qp[key] = priority index, qp[pq[key]] = pq[qp[key]] = key | ||||||
|  | 	qp   []int | ||||||
|  | 	less func(T, T) bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TODO: panic for illegal operations | ||||||
|  | // TODO: can we construct queue without bounded index size? | ||||||
|  | func NewIPQ[T any](less func(T, T) bool, indexSize int) IndexPriorityQueue[T] { | ||||||
|  | 	n := 0 | ||||||
|  | 	// TODO: switch to 0 based index | ||||||
|  | 	items := make([]T, indexSize+1) | ||||||
|  | 	pq := make([]int, indexSize+1) | ||||||
|  | 	qp := make([]int, indexSize+1) | ||||||
|  | 
 | ||||||
|  | 	for i := range qp { | ||||||
|  | 		qp[i] = -1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for i := range pq { | ||||||
|  | 		pq[i] = -1 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return &indexPriorityQueue[T]{n, items, pq, qp, less} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (q *indexPriorityQueue[T]) top() T { | ||||||
|  | 	return q.items[q.pq[1]] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (q *indexPriorityQueue[T]) topKey() int { | ||||||
|  | 	return q.pq[1] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (q *indexPriorityQueue[T]) insert(key int, item T) { | ||||||
|  | 	q.n++ | ||||||
|  | 	q.pq[q.n] = key | ||||||
|  | 	q.qp[key] = q.n | ||||||
|  | 	q.items[key] = item | ||||||
|  | 	q.swim(q.n) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (q *indexPriorityQueue[T]) remove() T { | ||||||
|  | 	topKey := q.topKey() | ||||||
|  | 	q.swap(1, q.n) | ||||||
|  | 	q.n-- | ||||||
|  | 	q.sink(1) | ||||||
|  | 	// TODO: need to remove actual item from items array | ||||||
|  | 	// to allow removed item to be GCed | ||||||
|  | 	q.qp[q.pq[q.n+1]] = -1 | ||||||
|  | 	return q.items[topKey] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (q *indexPriorityQueue[T]) removeKey(key int) T { | ||||||
|  | 	pivot := q.qp[key] | ||||||
|  | 	q.swap(pivot, q.n) | ||||||
|  | 	q.n-- | ||||||
|  | 	q.swim(pivot) | ||||||
|  | 	q.sink(pivot) | ||||||
|  | 	// TODO: need to remove actual item from items array | ||||||
|  | 	// to prevent memory leak | ||||||
|  | 	q.qp[key] = -1 | ||||||
|  | 	q.pq[q.n+1] = -1 | ||||||
|  | 	return q.items[key] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (q *indexPriorityQueue[T]) change(key int, item T) { | ||||||
|  | 	q.items[key] = item | ||||||
|  | 	q.swim(q.qp[key]) | ||||||
|  | 	q.sink(q.qp[key]) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (pq *indexPriorityQueue[_]) size() int { | ||||||
|  | 	return pq.n | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (pq *indexPriorityQueue[_]) isEmpty() bool { | ||||||
|  | 	return pq.n == 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (q *indexPriorityQueue[_]) contains(key int) bool { | ||||||
|  | 	return q.qp[key] != -1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (q *indexPriorityQueue[T]) sink(parent int) { | ||||||
|  | 	for 2*parent <= q.n { | ||||||
|  | 		child := 2 * parent | ||||||
|  | 		if child < q.n && q.less(q.items[q.pq[child]], q.items[q.pq[child+1]]) { | ||||||
|  | 			child++ | ||||||
|  | 		} | ||||||
|  | 		if !q.less(q.items[q.pq[parent]], q.items[q.pq[child]]) { | ||||||
|  | 			break | ||||||
|  | 		} | ||||||
|  | 		q.swap(parent, child) | ||||||
|  | 		parent = child | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (q *indexPriorityQueue[T]) swim(child int) { | ||||||
|  | 	for child > 1 && q.less(q.items[q.pq[child/2]], q.items[q.pq[child]]) { | ||||||
|  | 		q.swap(child/2, child) | ||||||
|  | 		child = child / 2 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (q *indexPriorityQueue[_]) swap(i, j int) { | ||||||
|  | 	q.qp[q.pq[i]] = j | ||||||
|  | 	q.qp[q.pq[j]] = i | ||||||
|  | 	q.pq[i], q.pq[j] = q.pq[j], q.pq[i] | ||||||
|  | } | ||||||
							
								
								
									
										158
									
								
								sorting/priority_queue/index_priority_queue_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								sorting/priority_queue/index_priority_queue_test.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,158 @@ | ||||||
|  | package priority_queue | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/stretchr/testify/assert" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestNewIPQ(t *testing.T) { | ||||||
|  | 	q := NewIPQ(intDescending, 1) | ||||||
|  | 	assert.NotNil(t, q) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 0, q.size()) | ||||||
|  | 	assert.Equal(t, true, q.isEmpty()) | ||||||
|  | 	assert.Equal(t, -1, q.topKey()) | ||||||
|  | 	// TODO: maybe should return nil? | ||||||
|  | 	// assert.Equal(t, 0, q.top()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestIPQInsert(t *testing.T) { | ||||||
|  | 	pq := NewIPQ(intDescending, 10) | ||||||
|  | 
 | ||||||
|  | 	pq.insert(1, 3) | ||||||
|  | 	assert.Equal(t, 3, pq.top()) | ||||||
|  | 
 | ||||||
|  | 	pq.insert(2, 4) | ||||||
|  | 	assert.Equal(t, 4, pq.top()) | ||||||
|  | 
 | ||||||
|  | 	pq.insert(3, 1) | ||||||
|  | 	assert.Equal(t, 4, pq.top()) | ||||||
|  | 
 | ||||||
|  | 	pq.insert(4, 4) | ||||||
|  | 	assert.Equal(t, 4, pq.top()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestMoreIPQInsert(t *testing.T) { | ||||||
|  | 	pq := NewIPQ(intDescending, 10) | ||||||
|  | 
 | ||||||
|  | 	pq.insert(1, 10) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 1, pq.topKey()) | ||||||
|  | 	assert.Equal(t, 10, pq.top()) | ||||||
|  | 	assert.Equal(t, 1, pq.size()) | ||||||
|  | 	assert.Equal(t, true, pq.contains(1)) | ||||||
|  | 	assert.Equal(t, false, pq.isEmpty()) | ||||||
|  | 
 | ||||||
|  | 	pq.insert(2, 20) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 2, pq.topKey()) | ||||||
|  | 	assert.Equal(t, 20, pq.top()) | ||||||
|  | 	assert.Equal(t, 2, pq.size()) | ||||||
|  | 	assert.Equal(t, true, pq.contains(2)) | ||||||
|  | 	assert.Equal(t, false, pq.isEmpty()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestIPQRemove(t *testing.T) { | ||||||
|  | 	pq := NewIPQ(intDescending, 10) | ||||||
|  | 
 | ||||||
|  | 	pq.insert(1, 10) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 1, pq.topKey()) | ||||||
|  | 	assert.Equal(t, 10, pq.top()) | ||||||
|  | 	assert.Equal(t, 1, pq.size()) | ||||||
|  | 	assert.Equal(t, true, pq.contains(1)) | ||||||
|  | 	assert.Equal(t, false, pq.isEmpty()) | ||||||
|  | 
 | ||||||
|  | 	pq.insert(2, 20) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 2, pq.topKey()) | ||||||
|  | 	assert.Equal(t, 20, pq.top()) | ||||||
|  | 	assert.Equal(t, 2, pq.size()) | ||||||
|  | 	assert.Equal(t, true, pq.contains(2)) | ||||||
|  | 	assert.Equal(t, false, pq.isEmpty()) | ||||||
|  | 
 | ||||||
|  | 	removed := pq.remove() | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 20, removed) | ||||||
|  | 	assert.Equal(t, 10, pq.top()) | ||||||
|  | 	assert.Equal(t, 1, pq.size()) | ||||||
|  | 	assert.Equal(t, false, pq.contains(2)) | ||||||
|  | 	assert.Equal(t, true, pq.contains(1)) | ||||||
|  | 	assert.Equal(t, false, pq.isEmpty()) | ||||||
|  | 
 | ||||||
|  | 	removed = pq.remove() | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 10, removed) | ||||||
|  | 	// TODO: should return nil? | ||||||
|  | 	// assert.Equal(t, "", pq.top()) | ||||||
|  | 	assert.Equal(t, 0, pq.size()) | ||||||
|  | 	assert.Equal(t, false, pq.contains(1)) | ||||||
|  | 	assert.Equal(t, true, pq.isEmpty()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestIPQRemoveAtIndex(t *testing.T) { | ||||||
|  | 	pq := NewIPQ(intDescending, 10) | ||||||
|  | 
 | ||||||
|  | 	// top -> 40 - 30 - 20 - 10 | ||||||
|  | 	pq.insert(8, 10) | ||||||
|  | 	pq.insert(5, 20) | ||||||
|  | 	pq.insert(3, 30) | ||||||
|  | 	pq.insert(4, 40) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 40, pq.top()) | ||||||
|  | 	assert.Equal(t, 4, pq.topKey()) | ||||||
|  | 
 | ||||||
|  | 	// top -> 40 - 30 - 10 | ||||||
|  | 	removed := pq.removeKey(5) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 20, removed) | ||||||
|  | 	assert.Equal(t, 40, pq.top()) | ||||||
|  | 	assert.Equal(t, 4, pq.topKey()) | ||||||
|  | 
 | ||||||
|  | 	// top -> 30 - 10 | ||||||
|  | 	removed = pq.removeKey(4) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 40, removed) | ||||||
|  | 	assert.Equal(t, 30, pq.top()) | ||||||
|  | 	assert.Equal(t, 3, pq.topKey()) | ||||||
|  | 
 | ||||||
|  | 	// top -> 30 - 20 - 10 | ||||||
|  | 	pq.insert(5, 20) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 30, pq.top()) | ||||||
|  | 	assert.Equal(t, 3, pq.topKey()) | ||||||
|  | 
 | ||||||
|  | 	// top -> 10 | ||||||
|  | 	removed = pq.removeKey(3) | ||||||
|  | 	assert.Equal(t, 30, removed) | ||||||
|  | 	removed = pq.removeKey(5) | ||||||
|  | 	assert.Equal(t, 20, removed) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 10, pq.top()) | ||||||
|  | 	assert.Equal(t, 8, pq.topKey()) | ||||||
|  | 	assert.Equal(t, 1, pq.size()) | ||||||
|  | 	assert.Equal(t, false, pq.contains(5)) | ||||||
|  | 	assert.Equal(t, false, pq.contains(4)) | ||||||
|  | 	assert.Equal(t, false, pq.contains(3)) | ||||||
|  | 	assert.Equal(t, true, pq.contains(8)) | ||||||
|  | 	assert.Equal(t, false, pq.isEmpty()) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestIndexChange(t *testing.T) { | ||||||
|  | 	pq := NewIPQ(intDescending, 10) | ||||||
|  | 
 | ||||||
|  | 	pq.insert(1, 9) | ||||||
|  | 	pq.insert(2, 8) | ||||||
|  | 	pq.insert(3, 12) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 12, pq.top()) | ||||||
|  | 
 | ||||||
|  | 	pq.change(3, 7) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 9, pq.top()) | ||||||
|  | 
 | ||||||
|  | 	pq.change(2, 10) | ||||||
|  | 
 | ||||||
|  | 	assert.Equal(t, 10, pq.top()) | ||||||
|  | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package sorting | package priority_queue | ||||||
| 
 | 
 | ||||||
| type PriorityQueue[T any] interface { | type PriorityQueue[T any] interface { | ||||||
| 	top() T | 	top() T | ||||||
|  | @ -66,24 +66,6 @@ func (pq *priorityQueue[_]) isEmpty() bool { | ||||||
| 	return pq.n == 0 | 	return pq.n == 0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (pq *priorityQueue[_]) swap(i, j int) { |  | ||||||
| 	pq.heap[i], pq.heap[j] = pq.heap[j], pq.heap[i] |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (pq *priorityQueue[T]) swim(child int) { |  | ||||||
| 	// Until we reach top of the heap |  | ||||||
| 	// and parent node is less than current child |  | ||||||
| 	for child > 1 && pq.less(pq.heap[child/2], pq.heap[child]) { |  | ||||||
| 
 |  | ||||||
| 		// We swap parent with the child |  | ||||||
| 		pq.swap(child/2, child) |  | ||||||
| 
 |  | ||||||
| 		// Parent node becomes new child |  | ||||||
| 		// for next iteration |  | ||||||
| 		child = child / 2 |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func (pq *priorityQueue[T]) sink(parent int) { | func (pq *priorityQueue[T]) sink(parent int) { | ||||||
| 	// While parent has some children | 	// While parent has some children | ||||||
| 	for 2*parent <= pq.n { | 	for 2*parent <= pq.n { | ||||||
|  | @ -111,3 +93,21 @@ func (pq *priorityQueue[T]) sink(parent int) { | ||||||
| 		parent = child | 		parent = child | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (pq *priorityQueue[T]) swim(child int) { | ||||||
|  | 	// Until we reach top of the heap | ||||||
|  | 	// and parent node is less than current child | ||||||
|  | 	for child > 1 && pq.less(pq.heap[child/2], pq.heap[child]) { | ||||||
|  | 
 | ||||||
|  | 		// We swap parent with the child | ||||||
|  | 		pq.swap(child/2, child) | ||||||
|  | 
 | ||||||
|  | 		// Parent node becomes new child | ||||||
|  | 		// for next iteration | ||||||
|  | 		child = child / 2 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (pq *priorityQueue[T]) swap(i, j int) { | ||||||
|  | 	pq.heap[i], pq.heap[j] = pq.heap[j], pq.heap[i] | ||||||
|  | } | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| package sorting | package priority_queue | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  | @ -6,15 +6,13 @@ import ( | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var intCompare = func(t1, t2 int) bool { return t1 < t2 } |  | ||||||
| 
 |  | ||||||
| func TestNew(t *testing.T) { | func TestNew(t *testing.T) { | ||||||
| 	pq := NewPQ(intCompare) | 	pq := NewPQ(intDescending) | ||||||
| 	assert.NotNil(t, pq) | 	assert.NotNil(t, pq) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestSize(t *testing.T) { | func TestSize(t *testing.T) { | ||||||
| 	pq := NewPQ(intCompare) | 	pq := NewPQ(intDescending) | ||||||
| 	assert.Equal(t, 0, pq.size()) | 	assert.Equal(t, 0, pq.size()) | ||||||
| 	pq.insert(1) | 	pq.insert(1) | ||||||
| 	assert.Equal(t, 1, pq.size()) | 	assert.Equal(t, 1, pq.size()) | ||||||
|  | @ -24,14 +22,14 @@ func TestSize(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestIsEmpty(t *testing.T) { | func TestIsEmpty(t *testing.T) { | ||||||
| 	pq := NewPQ(intCompare) | 	pq := NewPQ(intDescending) | ||||||
| 	assert.Equal(t, true, pq.isEmpty()) | 	assert.Equal(t, true, pq.isEmpty()) | ||||||
| 	pq.insert(1) | 	pq.insert(1) | ||||||
| 	assert.Equal(t, false, pq.isEmpty()) | 	assert.Equal(t, false, pq.isEmpty()) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestInsert(t *testing.T) { | func TestInsert(t *testing.T) { | ||||||
| 	pq := NewPQ(intCompare) | 	pq := NewPQ(intDescending) | ||||||
| 	pq.insert(1) | 	pq.insert(1) | ||||||
| 	assert.Equal(t, 1, pq.top()) | 	assert.Equal(t, 1, pq.top()) | ||||||
| 	pq.insert(4) | 	pq.insert(4) | ||||||
|  | @ -43,7 +41,7 @@ func TestInsert(t *testing.T) { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestDelete(t *testing.T) { | func TestDelete(t *testing.T) { | ||||||
| 	pq := NewPQ(intCompare) | 	pq := NewPQ(intDescending) | ||||||
| 	pq.insert(1) | 	pq.insert(1) | ||||||
| 	pq.insert(2) | 	pq.insert(2) | ||||||
| 	pq.insert(6) | 	pq.insert(6) | ||||||
							
								
								
									
										3
									
								
								sorting/priority_queue/testing.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								sorting/priority_queue/testing.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,3 @@ | ||||||
|  | package priority_queue | ||||||
|  | 
 | ||||||
|  | var intDescending = func(t1, t2 int) bool { return t1 < t2 } | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue