priority_map

package
v0.0.0-...-881dfb5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 9, 2024 License: MIT Imports: 1 Imported by: 0

README

Priority Map

PriorityMap is a combination of HashMap and Binary Heap. It is useful for workloads that requires

  1. A Key-Value store that supports Get, Set, Delete by key.
  2. A Heap that supports access the key-value pair of the smallest value.

Some good use cases:

  1. Implementing greedy algorithms like the Dijkstra's algorithm and Prim's algorithm.
  2. Job Scheduler that schedules job with the highest priority.

Some similar data structures are:

  1. TreeMap: key-value pairs are sorted by key.
  2. SortedSet: key-values are sorted by value.
  3. PriorityQueue: pop the smallest element out of queue.

Both TreeMap and SortedSet sort all key-value pairs. TreeMap keeps order using a balanced binary tree, and SortedSet uses a skip list. PriorityMap, however, does not sort all key-value pairs. Instead, PriorityMap keeps order using a binary heap. Therefore, only the top of Heap is guaranteed an order, the minimum, among all pairs.

PriorityQueue does not store key-value pairs, so it does not supports access by key. It does not support update an element's priority either.

How to use it

Simple Value Type
func main() {
	pm := prioritymap.NewPriorityMap[int, int](func(a, b int) int {
		return a - b
	})
	pm.Set(1, 10)
	pm.Set(2, 10)
	pm.Set(3, 30)
	fmt.Printf("size: %d\n", pm.Size()) // "size: 3"
	if v, ok := pm.Get(1); ok {
		fmt.Printf("key: 1, value: %d\n", v) // "key: 1, value: 10"
	}
	if k, v, ok := pm.Top(); ok {
		fmt.Printf("key: %d, value: %d\n", k, v) // "key: 1, value: 10" or "key: 2, value: 10"
	}
}
Composite Value Type
type Job struct {
	id         int
	expiration time.Time
	name       string
}

func main() {
	pm := prioritymap.NewPriorityMap[int, *Job](func(v1, v2 *Job) bool {
		switch {
		case v1.expire.Before(v2.expire):
			return -1
		case v1.expire.After(v2.expire):
			return 1
		default:
			return 0
		}
	})
	now := time.Now()
	jobs := []Job{
		{1, now.Add(-1 * time.Minute), "job 1"},
		{2, now.Add(-2 * time.Minute), "job 2"},
		{3, now.Add(-3 * time.Minute), "job 3"},
	}
	for i, _ := range jobs {
		pm.Set(jobs[i].id, &jobs[i])
	}
	id, job, _ := pm.Top()
	fmt.Printf("job with the smallest expiration. id %d, name %s\n", id, job.name)
	job, _ = pm.Get(2)
	fmt.Printf("job 2's expiration is %v\n", job.expiration)
	fmt.Println("taking jobs one by one, in the order of expiration time")
	for pm.Size() > 0 {
		if id, job, ok := pm.Pop(); ok {
			fmt.Printf("job id %d, name %s, expiration %v\n", id, job.name, job.expiration)
		}
	}
}

Performance

We benchmark Add, Update, Delete, Pop on a priority map of 1M key-value pairs. The following is a result on my Mac M1 Max. You can run the benchmark with.

go test -bench BenchmarkPriorityMap -benchmem  -benchtime 10s
goos: darwin
goarch: arm64
pkg: github.com/pengubco/algorithms/priority_map
BenchmarkPriorityMap_Add_1M-10                    66         175442495 ns/op        149403499 B/op       1023415 allocs/op
BenchmarkPriorityMap_Update_1M-10                100         124093262 ns/op           80035 B/op              0 allocs/op
BenchmarkPriorityMap_Del_1M-10                    75         170370006 ns/op               0 B/op              0 allocs/op
BenchmarkPriorityMap_Pop_1M-10                    19         604293805 ns/op               0 B/op              0 allocs/op
PASS
ok      github.com/pengubco/algorithms/priority_map 94.674s

No surprise that Pop is most expensive because the heap may need to go from root to a leaf to maintain the heap structure. It takes 604ms (~0.6 second) to pop 1M key-value pairs. I think this is fast enough for normal production use.

Correctness

We run 1M operations on PriorityMap and a SortedSet in Redis, see redis-compare/main.go. After each operation, we compare the size and the smallest key-value pair from PriorityMap with corresponding values from Redis. This gives us confidence that PriorityMap is correct.

If you found a bug, please open an issue.

Documentation

Overview

Package priority_map implements a key-value store where key-value pairs can be accessed either by key or the order of values. The value must be orderable (a < b) and the key must be comparable (a == b).

1. Get(K) V 2. Set(K, V) 3. Delete(K) 4. Top() (K, V, bool) 5. Pop() (K, V, bool) 6. Size() int

Usage

pm, _ := NewPriorityMap[int, string](func (v1, v2 string) bool {
		return v1 < v2
})

pm.Set(1, "a") pm.Get(1) // returns "a" pm.Set(2, "b") pm.Set(3, "c") pm.Top() // returns (1, "a", true) pm.Pop() // returns (1, "a", true) pm.Delete(1) pm.Size() // returns 2

See more usage example in the priority_map_test.go.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Element

type Element[K comparable, V any] struct {
	Key   K
	Value V
	// contains filtered or unexported fields
}

Element is the unit of data stored in hash map and the heap.

type PriorityMap

type PriorityMap[K comparable, V any] struct {
	// contains filtered or unexported fields
}

PriorityMap keeps key-value pairs in a hash map and provides access to the pair of the minimum value.

func NewPriorityMap

func NewPriorityMap[K comparable, V any](less func(v1, v2 V) bool) *PriorityMap[K, V]

NewPriorityMap returns a PriorityMap where values are ordered by the given less function.

func (*PriorityMap[K, V]) Delete

func (pm *PriorityMap[K, V]) Delete(key K)

Delete deletes the key-value pair of the key.

func (*PriorityMap[K, V]) Get

func (pm *PriorityMap[K, V]) Get(k K) (V, bool)

Get returns the value associated with the key

func (*PriorityMap[K, V]) Map

func (pm *PriorityMap[K, V]) Map() map[K]*Element[K, V]

Map returns the underlying map. It is here to provide an efficient way of iterating over all key-value pairs.

func (*PriorityMap[K, V]) Pop

func (pm *PriorityMap[K, V]) Pop() (K, V, bool)

Pop removes and returns the key-value pair of the smallest value. It returns flase if the set is empty.

func (*PriorityMap[K, V]) Set

func (pm *PriorityMap[K, V]) Set(k K, v V)

Set inserts a k-v pair if the key does not exist. Otherwise, Set updates the value.

func (*PriorityMap[K, V]) Size

func (pm *PriorityMap[K, V]) Size() int

Size returns the number of key-value pairs.

func (*PriorityMap[K, V]) Top

func (pm *PriorityMap[K, V]) Top() (K, V, bool)

Top returns the key-value pair of the smallest value. It returns false if the set is empty.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL