Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

flatmap.gno

4.64 Kb · 157 lines
  1// Package flatmap is a sorted-vector map — the STL flat_map / Abseil
  2// btree_map trade — as a pure, reusable package.
  3//
  4// Keys and values live in two parallel sorted slices instead of a hash table or
  5// a tree of nodes. Lookup is a binary search, O(log n) rather than O(1), but it
  6// touches contiguous memory instead of chasing pointers, iteration is already
  7// in key order with nothing to sort, and there is no per-entry node overhead.
  8// Insertion in the middle is O(n) because it shifts the tail. That is the whole
  9// bargain: cheap reads and cheap ordered iteration, paid for at write time.
 10//
 11// On chain the ordering is the real draw. A built-in gno map iterates in an
 12// unspecified order, so a Render built from one can differ between nodes — a
 13// consensus bug rather than a cosmetic one. A flat map is sorted by
 14// construction, so iteration is deterministic without a sort on every read.
 15//
 16// Appending in ascending key order is the fast path: it hits the end of the
 17// slice and shifts nothing.
 18//
 19// A live demo of this package is at
 20// [r/moul/x/daily/flatmapdemo](/r/moul/x/daily/flatmapdemo/v0).
 21package flatmap
 22
 23import "sort"
 24
 25// MaxEntries bounds the map so gas stays predictable.
 26const MaxEntries = 4096
 27
 28// FlatMap is a string->string map backed by parallel sorted slices.
 29type FlatMap struct {
 30	keys []string
 31	vals []string
 32}
 33
 34// New returns an empty FlatMap.
 35func New() *FlatMap { return &FlatMap{} }
 36
 37// Len returns the number of entries.
 38func (f *FlatMap) Len() int { return len(f.keys) }
 39
 40// IsEmpty reports whether the map holds nothing.
 41func (f *FlatMap) IsEmpty() bool { return len(f.keys) == 0 }
 42
 43// search returns the index where key is, or would be inserted, plus whether it
 44// is actually present.
 45func (f *FlatMap) search(key string) (int, bool) {
 46	i := sort.SearchStrings(f.keys, key)
 47	return i, i < len(f.keys) && f.keys[i] == key
 48}
 49
 50// Get returns the value for key.
 51func (f *FlatMap) Get(key string) (string, bool) {
 52	i, found := f.search(key)
 53	if !found {
 54		return "", false
 55	}
 56	return f.vals[i], true
 57}
 58
 59// Has reports whether key is present.
 60func (f *FlatMap) Has(key string) bool { _, found := f.search(key); return found }
 61
 62// Set inserts or updates key. Returns false only when the map is full and key
 63// is new — updating an existing key always succeeds.
 64func (f *FlatMap) Set(key, value string) bool {
 65	i, found := f.search(key)
 66	if found {
 67		f.vals[i] = value
 68		return true
 69	}
 70	if len(f.keys) >= MaxEntries {
 71		return false
 72	}
 73	// Grow by one, then shift the tail right to open a slot at i.
 74	f.keys = append(f.keys, "")
 75	f.vals = append(f.vals, "")
 76	copy(f.keys[i+1:], f.keys[i:])
 77	copy(f.vals[i+1:], f.vals[i:])
 78	f.keys[i] = key
 79	f.vals[i] = value
 80	return true
 81}
 82
 83// Delete removes key. Returns false when absent.
 84func (f *FlatMap) Delete(key string) bool {
 85	i, found := f.search(key)
 86	if !found {
 87		return false
 88	}
 89	copy(f.keys[i:], f.keys[i+1:])
 90	copy(f.vals[i:], f.vals[i+1:])
 91	f.keys = f.keys[:len(f.keys)-1]
 92	f.vals = f.vals[:len(f.vals)-1]
 93	return true
 94}
 95
 96// Keys returns the keys in sorted order, as a copy.
 97func (f *FlatMap) Keys() []string {
 98	out := make([]string, len(f.keys))
 99	copy(out, f.keys)
100	return out
101}
102
103// Values returns the values ordered by their keys, as a copy.
104func (f *FlatMap) Values() []string {
105	out := make([]string, len(f.vals))
106	copy(out, f.vals)
107	return out
108}
109
110// At returns the i-th entry in key order — the indexed access a hash map cannot
111// offer, and one reason to pay for sorted storage.
112func (f *FlatMap) At(i int) (key, value string, ok bool) {
113	if i < 0 || i >= len(f.keys) {
114		return "", "", false
115	}
116	return f.keys[i], f.vals[i], true
117}
118
119// Iterate calls fn for each entry in key order. Returning true stops.
120func (f *FlatMap) Iterate(fn func(key, value string) bool) {
121	for i := range f.keys {
122		if fn(f.keys[i], f.vals[i]) {
123			return
124		}
125	}
126}
127
128// Range calls fn for entries with lo <= key < hi, in key order. An empty hi
129// means "to the end". This is the other thing sorted storage buys: a range
130// query is two binary searches and a walk.
131func (f *FlatMap) Range(lo, hi string, fn func(key, value string) bool) {
132	start := sort.SearchStrings(f.keys, lo)
133	for i := start; i < len(f.keys); i++ {
134		if hi != "" && f.keys[i] >= hi {
135			return
136		}
137		if fn(f.keys[i], f.vals[i]) {
138			return
139		}
140	}
141}
142
143// Clone returns an independent copy.
144func (f *FlatMap) Clone() *FlatMap {
145	return &FlatMap{keys: f.Keys(), vals: f.Values()}
146}
147
148// Sorted reports whether the backing slice is in strictly ascending order.
149// Always true through the public API; exported so callers can assert it.
150func (f *FlatMap) Sorted() bool {
151	for i := 1; i < len(f.keys); i++ {
152		if f.keys[i-1] >= f.keys[i] {
153			return false
154		}
155	}
156	return len(f.keys) == len(f.vals)
157}