gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

common.go (5827B)


      1 package maps
      2 
      3 import (
      4 	"fmt"
      5 	"reflect"
      6 
      7 	"codeberg.org/gruf/go-byteutil"
      8 	"codeberg.org/gruf/go-kv"
      9 )
     10 
     11 // ordered provides a common ordered hashmap base, storing order in a doubly-linked list.
     12 type ordered[K comparable, V any] struct {
     13 	hmap map[K]*elem[K, V]
     14 	list list[K, V]
     15 	pool []*elem[K, V]
     16 	rnly bool
     17 }
     18 
     19 // write_check panics if map is not in a safe-state to write to.
     20 func (m ordered[K, V]) write_check() {
     21 	if m.rnly {
     22 		panic("map write during read loop")
     23 	}
     24 }
     25 
     26 // Has returns whether key exists in map.
     27 func (m *ordered[K, V]) Has(key K) bool {
     28 	_, ok := m.hmap[key]
     29 	return ok
     30 }
     31 
     32 // Delete will delete given key from map, returns false if not found.
     33 func (m *ordered[K, V]) Delete(key K) bool {
     34 	// Ensure safe
     35 	m.write_check()
     36 
     37 	// Look for existing elem
     38 	elem, ok := m.hmap[key]
     39 	if !ok {
     40 		return false
     41 	}
     42 
     43 	// Drop from list
     44 	m.list.Unlink(elem)
     45 
     46 	// Delete from map
     47 	delete(m.hmap, key)
     48 
     49 	// Return to pool
     50 	m.free(elem)
     51 
     52 	return true
     53 }
     54 
     55 // Range passes given function over the requested range of the map.
     56 func (m *ordered[K, V]) Range(start, length int, fn func(int, K, V)) {
     57 	// Disallow writes
     58 	m.rnly = true
     59 	defer func() {
     60 		m.rnly = false
     61 	}()
     62 
     63 	// Nil check
     64 	_ = fn
     65 
     66 	switch end := start + length; {
     67 	// No loop to iterate
     68 	case length == 0:
     69 		if start < 0 || (m.list.len > 0 && start >= m.list.len) {
     70 			panic("index out of bounds")
     71 		}
     72 
     73 	// Step backwards
     74 	case length < 0:
     75 		// Check loop indices are within map bounds
     76 		if end < -1 || start >= m.list.len || m.list.len == 0 {
     77 			panic("index out of bounds")
     78 		}
     79 
     80 		// Get starting index elem
     81 		elem := m.list.Index(start)
     82 
     83 		for i := start; i > end; i-- {
     84 			fn(i, elem.K, elem.V)
     85 			elem = elem.prev
     86 		}
     87 
     88 	// Step forwards
     89 	case length > 0:
     90 		// Check loop indices are within map bounds
     91 		if start < 0 || end > m.list.len || m.list.len == 0 {
     92 			panic("index out of bounds")
     93 		}
     94 
     95 		// Get starting index elem
     96 		elem := m.list.Index(start)
     97 
     98 		for i := start; i < end; i++ {
     99 			fn(i, elem.K, elem.V)
    100 			elem = elem.next
    101 		}
    102 	}
    103 }
    104 
    105 // RangeIf passes given function over the requested range of the map. Returns early on 'fn' -> false.
    106 func (m *ordered[K, V]) RangeIf(start, length int, fn func(int, K, V) bool) {
    107 	// Disallow writes
    108 	m.rnly = true
    109 	defer func() {
    110 		m.rnly = false
    111 	}()
    112 
    113 	// Nil check
    114 	_ = fn
    115 
    116 	switch end := start + length; {
    117 	// No loop to iterate
    118 	case length == 0:
    119 		if start < 0 || (m.list.len > 0 && start >= m.list.len) {
    120 			panic("index out of bounds")
    121 		}
    122 
    123 	// Step backwards
    124 	case length < 0:
    125 		// Check loop indices are within map bounds
    126 		if end < -1 || start >= m.list.len || m.list.len == 0 {
    127 			panic("index out of bounds")
    128 		}
    129 
    130 		// Get starting index elem
    131 		elem := m.list.Index(start)
    132 
    133 		for i := start; i > end; i-- {
    134 			if !fn(i, elem.K, elem.V) {
    135 				return
    136 			}
    137 			elem = elem.prev
    138 		}
    139 
    140 	// Step forwards
    141 	case length > 0:
    142 		// Check loop indices are within map bounds
    143 		if start < 0 || end > m.list.len || m.list.len == 0 {
    144 			panic("index out of bounds")
    145 		}
    146 
    147 		// Get starting index elem
    148 		elem := m.list.Index(start)
    149 
    150 		for i := start; i < end; i++ {
    151 			if !fn(i, elem.K, elem.V) {
    152 				return
    153 			}
    154 			elem = elem.next
    155 		}
    156 	}
    157 }
    158 
    159 // Truncate will truncate the map from the back by given amount, passing dropped elements to given function.
    160 func (m *ordered[K, V]) Truncate(sz int, fn func(K, V)) {
    161 	// Check size withing bounds
    162 	if sz > m.list.len {
    163 		panic("index out of bounds")
    164 	}
    165 
    166 	if fn == nil {
    167 		// move nil check out of loop
    168 		fn = func(K, V) {}
    169 	}
    170 
    171 	// Disallow writes
    172 	m.rnly = true
    173 	defer func() {
    174 		m.rnly = false
    175 	}()
    176 
    177 	for i := 0; i < sz; i++ {
    178 		// Pop current tail
    179 		elem := m.list.tail
    180 		m.list.Unlink(elem)
    181 
    182 		// Delete from map
    183 		delete(m.hmap, elem.K)
    184 
    185 		// Pass dropped to func
    186 		fn(elem.K, elem.V)
    187 
    188 		// Release to pool
    189 		m.free(elem)
    190 	}
    191 }
    192 
    193 // Len returns the current length of the map.
    194 func (m *ordered[K, V]) Len() int {
    195 	return m.list.len
    196 }
    197 
    198 // format implements fmt.Formatter, allowing performant string formatting of map.
    199 func (m *ordered[K, V]) format(rtype reflect.Type, state fmt.State, verb rune) {
    200 	var (
    201 		kvbuf byteutil.Buffer
    202 		field kv.Field
    203 		vbose bool
    204 	)
    205 
    206 	switch {
    207 	// Only handle 'v' verb
    208 	case verb != 'v':
    209 		panic("invalid verb '" + string(verb) + "' for map")
    210 
    211 	// Prefix with type when verbose
    212 	case state.Flag('#'):
    213 		state.Write([]byte(rtype.String()))
    214 	}
    215 
    216 	// Disallow writes
    217 	m.rnly = true
    218 	defer func() {
    219 		m.rnly = false
    220 	}()
    221 
    222 	// Write map opening brace
    223 	state.Write([]byte{'{'})
    224 
    225 	if m.list.len > 0 {
    226 		// Preallocate buffer
    227 		kvbuf.Guarantee(64)
    228 
    229 		// Start at index 0
    230 		elem := m.list.head
    231 
    232 		for i := 0; i < m.list.len-1; i++ {
    233 			// Append formatted key-val pair to state
    234 			field.K = fmt.Sprint(elem.K)
    235 			field.V = elem.V
    236 			field.AppendFormat(&kvbuf, vbose)
    237 			_, _ = state.Write(kvbuf.B)
    238 			kvbuf.Reset()
    239 
    240 			// Prepare buffer with comma separator
    241 			kvbuf.B = append(kvbuf.B, `, `...)
    242 
    243 			// Jump to next in list
    244 			elem = elem.next
    245 		}
    246 
    247 		// Append formatted key-val pair to state
    248 		field.K = fmt.Sprint(elem.K)
    249 		field.V = elem.V
    250 		field.AppendFormat(&kvbuf, vbose)
    251 		_, _ = state.Write(kvbuf.B)
    252 	}
    253 
    254 	// Write map closing brace
    255 	state.Write([]byte{'}'})
    256 }
    257 
    258 // Std returns a clone of map's data in the standard library equivalent map type.
    259 func (m *ordered[K, V]) Std() map[K]V {
    260 	std := make(map[K]V, m.list.len)
    261 	for _, elem := range m.hmap {
    262 		std[elem.K] = elem.V
    263 	}
    264 	return std
    265 }
    266 
    267 // alloc will acquire list element from pool, or allocate new.
    268 func (m *ordered[K, V]) alloc() *elem[K, V] {
    269 	if len(m.pool) == 0 {
    270 		return &elem[K, V]{}
    271 	}
    272 	idx := len(m.pool) - 1
    273 	elem := m.pool[idx]
    274 	m.pool = m.pool[:idx]
    275 	return elem
    276 }
    277 
    278 // free will reset elem fields and place back in pool.
    279 func (m *ordered[K, V]) free(elem *elem[K, V]) {
    280 	var (
    281 		zk K
    282 		zv V
    283 	)
    284 	elem.K = zk
    285 	elem.V = zv
    286 	elem.next = nil
    287 	elem.prev = nil
    288 	m.pool = append(m.pool, elem)
    289 }