290 lines
5.7 KiB
Go
290 lines
5.7 KiB
Go
package maps
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"codeberg.org/gruf/go-byteutil"
|
|
"codeberg.org/gruf/go-kv"
|
|
)
|
|
|
|
// ordered provides a common ordered hashmap base, storing order in a doubly-linked list.
|
|
type ordered[K comparable, V any] struct {
|
|
hmap map[K]*elem[K, V]
|
|
list list[K, V]
|
|
pool []*elem[K, V]
|
|
rnly bool
|
|
}
|
|
|
|
// write_check panics if map is not in a safe-state to write to.
|
|
func (m ordered[K, V]) write_check() {
|
|
if m.rnly {
|
|
panic("map write during read loop")
|
|
}
|
|
}
|
|
|
|
// Has returns whether key exists in map.
|
|
func (m *ordered[K, V]) Has(key K) bool {
|
|
_, ok := m.hmap[key]
|
|
return ok
|
|
}
|
|
|
|
// Delete will delete given key from map, returns false if not found.
|
|
func (m *ordered[K, V]) Delete(key K) bool {
|
|
// Ensure safe
|
|
m.write_check()
|
|
|
|
// Look for existing elem
|
|
elem, ok := m.hmap[key]
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
// Drop from list
|
|
m.list.Unlink(elem)
|
|
|
|
// Delete from map
|
|
delete(m.hmap, key)
|
|
|
|
// Return to pool
|
|
m.free(elem)
|
|
|
|
return true
|
|
}
|
|
|
|
// Range passes given function over the requested range of the map.
|
|
func (m *ordered[K, V]) Range(start, length int, fn func(int, K, V)) {
|
|
// Disallow writes
|
|
m.rnly = true
|
|
defer func() {
|
|
m.rnly = false
|
|
}()
|
|
|
|
// Nil check
|
|
_ = fn
|
|
|
|
switch end := start + length; {
|
|
// No loop to iterate
|
|
case length == 0:
|
|
if start < 0 || (m.list.len > 0 && start >= m.list.len) {
|
|
panic("index out of bounds")
|
|
}
|
|
|
|
// Step backwards
|
|
case length < 0:
|
|
// Check loop indices are within map bounds
|
|
if end < -1 || start >= m.list.len || m.list.len == 0 {
|
|
panic("index out of bounds")
|
|
}
|
|
|
|
// Get starting index elem
|
|
elem := m.list.Index(start)
|
|
|
|
for i := start; i > end; i-- {
|
|
fn(i, elem.K, elem.V)
|
|
elem = elem.prev
|
|
}
|
|
|
|
// Step forwards
|
|
case length > 0:
|
|
// Check loop indices are within map bounds
|
|
if start < 0 || end > m.list.len || m.list.len == 0 {
|
|
panic("index out of bounds")
|
|
}
|
|
|
|
// Get starting index elem
|
|
elem := m.list.Index(start)
|
|
|
|
for i := start; i < end; i++ {
|
|
fn(i, elem.K, elem.V)
|
|
elem = elem.next
|
|
}
|
|
}
|
|
}
|
|
|
|
// RangeIf passes given function over the requested range of the map. Returns early on 'fn' -> false.
|
|
func (m *ordered[K, V]) RangeIf(start, length int, fn func(int, K, V) bool) {
|
|
// Disallow writes
|
|
m.rnly = true
|
|
defer func() {
|
|
m.rnly = false
|
|
}()
|
|
|
|
// Nil check
|
|
_ = fn
|
|
|
|
switch end := start + length; {
|
|
// No loop to iterate
|
|
case length == 0:
|
|
if start < 0 || (m.list.len > 0 && start >= m.list.len) {
|
|
panic("index out of bounds")
|
|
}
|
|
|
|
// Step backwards
|
|
case length < 0:
|
|
// Check loop indices are within map bounds
|
|
if end < -1 || start >= m.list.len || m.list.len == 0 {
|
|
panic("index out of bounds")
|
|
}
|
|
|
|
// Get starting index elem
|
|
elem := m.list.Index(start)
|
|
|
|
for i := start; i > end; i-- {
|
|
if !fn(i, elem.K, elem.V) {
|
|
return
|
|
}
|
|
elem = elem.prev
|
|
}
|
|
|
|
// Step forwards
|
|
case length > 0:
|
|
// Check loop indices are within map bounds
|
|
if start < 0 || end > m.list.len || m.list.len == 0 {
|
|
panic("index out of bounds")
|
|
}
|
|
|
|
// Get starting index elem
|
|
elem := m.list.Index(start)
|
|
|
|
for i := start; i < end; i++ {
|
|
if !fn(i, elem.K, elem.V) {
|
|
return
|
|
}
|
|
elem = elem.next
|
|
}
|
|
}
|
|
}
|
|
|
|
// Truncate will truncate the map from the back by given amount, passing dropped elements to given function.
|
|
func (m *ordered[K, V]) Truncate(sz int, fn func(K, V)) {
|
|
// Check size withing bounds
|
|
if sz > m.list.len {
|
|
panic("index out of bounds")
|
|
}
|
|
|
|
if fn == nil {
|
|
// move nil check out of loop
|
|
fn = func(K, V) {}
|
|
}
|
|
|
|
// Disallow writes
|
|
m.rnly = true
|
|
defer func() {
|
|
m.rnly = false
|
|
}()
|
|
|
|
for i := 0; i < sz; i++ {
|
|
// Pop current tail
|
|
elem := m.list.tail
|
|
m.list.Unlink(elem)
|
|
|
|
// Delete from map
|
|
delete(m.hmap, elem.K)
|
|
|
|
// Pass dropped to func
|
|
fn(elem.K, elem.V)
|
|
|
|
// Release to pool
|
|
m.free(elem)
|
|
}
|
|
}
|
|
|
|
// Len returns the current length of the map.
|
|
func (m *ordered[K, V]) Len() int {
|
|
return m.list.len
|
|
}
|
|
|
|
// format implements fmt.Formatter, allowing performant string formatting of map.
|
|
func (m *ordered[K, V]) format(rtype reflect.Type, state fmt.State, verb rune) {
|
|
var (
|
|
kvbuf byteutil.Buffer
|
|
field kv.Field
|
|
vbose bool
|
|
)
|
|
|
|
switch {
|
|
// Only handle 'v' verb
|
|
case verb != 'v':
|
|
panic("invalid verb '" + string(verb) + "' for map")
|
|
|
|
// Prefix with type when verbose
|
|
case state.Flag('#'):
|
|
state.Write([]byte(rtype.String()))
|
|
}
|
|
|
|
// Disallow writes
|
|
m.rnly = true
|
|
defer func() {
|
|
m.rnly = false
|
|
}()
|
|
|
|
// Write map opening brace
|
|
state.Write([]byte{'{'})
|
|
|
|
if m.list.len > 0 {
|
|
// Preallocate buffer
|
|
kvbuf.Guarantee(64)
|
|
|
|
// Start at index 0
|
|
elem := m.list.head
|
|
|
|
for i := 0; i < m.list.len-1; i++ {
|
|
// Append formatted key-val pair to state
|
|
field.K = fmt.Sprint(elem.K)
|
|
field.V = elem.V
|
|
field.AppendFormat(&kvbuf, vbose)
|
|
_, _ = state.Write(kvbuf.B)
|
|
kvbuf.Reset()
|
|
|
|
// Prepare buffer with comma separator
|
|
kvbuf.B = append(kvbuf.B, `, `...)
|
|
|
|
// Jump to next in list
|
|
elem = elem.next
|
|
}
|
|
|
|
// Append formatted key-val pair to state
|
|
field.K = fmt.Sprint(elem.K)
|
|
field.V = elem.V
|
|
field.AppendFormat(&kvbuf, vbose)
|
|
_, _ = state.Write(kvbuf.B)
|
|
}
|
|
|
|
// Write map closing brace
|
|
state.Write([]byte{'}'})
|
|
}
|
|
|
|
// Std returns a clone of map's data in the standard library equivalent map type.
|
|
func (m *ordered[K, V]) Std() map[K]V {
|
|
std := make(map[K]V, m.list.len)
|
|
for _, elem := range m.hmap {
|
|
std[elem.K] = elem.V
|
|
}
|
|
return std
|
|
}
|
|
|
|
// alloc will acquire list element from pool, or allocate new.
|
|
func (m *ordered[K, V]) alloc() *elem[K, V] {
|
|
if len(m.pool) == 0 {
|
|
return &elem[K, V]{}
|
|
}
|
|
idx := len(m.pool) - 1
|
|
elem := m.pool[idx]
|
|
m.pool = m.pool[:idx]
|
|
return elem
|
|
}
|
|
|
|
// free will reset elem fields and place back in pool.
|
|
func (m *ordered[K, V]) free(elem *elem[K, V]) {
|
|
var (
|
|
zk K
|
|
zv V
|
|
)
|
|
elem.K = zk
|
|
elem.V = zv
|
|
elem.next = nil
|
|
elem.prev = nil
|
|
m.pool = append(m.pool, elem)
|
|
}
|