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 }