diff options
Diffstat (limited to 'vendor/codeberg.org/gruf/go-maps/common.go')
-rw-r--r-- | vendor/codeberg.org/gruf/go-maps/common.go | 289 |
1 files changed, 289 insertions, 0 deletions
diff --git a/vendor/codeberg.org/gruf/go-maps/common.go b/vendor/codeberg.org/gruf/go-maps/common.go new file mode 100644 index 000000000..f5877ee3a --- /dev/null +++ b/vendor/codeberg.org/gruf/go-maps/common.go @@ -0,0 +1,289 @@ +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) +} |