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

bidimap.gno

5.44 Kb · 195 lines
  1// Package bidimap is a bidirectional map — unique in both directions — as a
  2// pure, reusable package.
  3//
  4// A normal map answers "what is the value for this key". A bidirectional one
  5// also answers the reverse in O(1), by keeping a second index. The cost is an
  6// invariant a plain pair of maps does not give you: BOTH sides are unique, so
  7// inserting a pair whose value already belongs to another key must do something
  8// deliberate rather than silently corrupt the reverse index.
  9//
 10// This implementation makes that choice explicit. Put REPLACES: it evicts any
 11// existing pairing on either side first, so the two indexes can never disagree.
 12// PutUnique refuses instead, returning false. Pick whichever the caller wants;
 13// what is not on offer is a half-updated map.
 14//
 15// Iteration is over sorted keys, never a built-in map range: gno map iteration
 16// order is unspecified, and a Render built from one can differ between nodes,
 17// which is a consensus bug rather than a cosmetic one.
 18//
 19// A live demo of this package is at
 20// [r/moul/x/daily/bidimapdemo](/r/moul/x/daily/bidimapdemo/v0).
 21package bidimap
 22
 23import "sort"
 24
 25// MaxPairs bounds the map so gas stays predictable.
 26const MaxPairs = 4096
 27
 28// BiMap is a string<->string map, unique in both directions.
 29type BiMap struct {
 30	fwd map[string]string
 31	rev map[string]string
 32}
 33
 34// New returns an empty BiMap.
 35func New() *BiMap {
 36	return &BiMap{fwd: map[string]string{}, rev: map[string]string{}}
 37}
 38
 39// Len returns the number of pairs.
 40func (m *BiMap) Len() int { return len(m.fwd) }
 41
 42// Get returns the value bound to key.
 43func (m *BiMap) Get(key string) (string, bool) {
 44	v, ok := m.fwd[key]
 45	return v, ok
 46}
 47
 48// GetKey returns the key bound to value — the reverse lookup, also O(1).
 49func (m *BiMap) GetKey(value string) (string, bool) {
 50	k, ok := m.rev[value]
 51	return k, ok
 52}
 53
 54// Has reports whether key is present.
 55func (m *BiMap) Has(key string) bool { _, ok := m.fwd[key]; return ok }
 56
 57// HasValue reports whether value is present.
 58func (m *BiMap) HasValue(value string) bool { _, ok := m.rev[value]; return ok }
 59
 60// Put binds key<->value, REPLACING any existing pairing on either side. It
 61// returns the pairs that were evicted to make room, so the caller can see what
 62// it displaced rather than discovering it later.
 63//
 64// Returns ok=false only when the map is full and the pair is entirely new.
 65func (m *BiMap) Put(key, value string) (evicted [][2]string, ok bool) {
 66	oldValue, keyTaken := m.fwd[key]
 67
 68	// Already exactly this pair: nothing to do.
 69	if keyTaken && oldValue == value {
 70		return nil, true
 71	}
 72
 73	oldKey, valueTaken := m.rev[value]
 74
 75	// Only a pair that is new on BOTH sides grows the map. Rebinding either
 76	// side reuses a slot, so it stays allowed at capacity. Checked up front:
 77	// evicting first and rolling back on failure would be unreachable code,
 78	// since any eviction frees the very slot the check is about.
 79	if !keyTaken && !valueTaken && len(m.fwd) >= MaxPairs {
 80		return nil, false
 81	}
 82
 83	if keyTaken {
 84		evicted = append(evicted, [2]string{key, oldValue})
 85		delete(m.rev, oldValue)
 86		delete(m.fwd, key)
 87	}
 88	if valueTaken {
 89		evicted = append(evicted, [2]string{oldKey, value})
 90		delete(m.fwd, oldKey)
 91		delete(m.rev, value)
 92	}
 93
 94	m.fwd[key] = value
 95	m.rev[value] = key
 96	return evicted, true
 97}
 98
 99// PutUnique binds key<->value only when NEITHER side is already taken by a
100// different pairing. Returns false without changing anything otherwise.
101func (m *BiMap) PutUnique(key, value string) bool {
102	if v, exists := m.fwd[key]; exists {
103		return v == value // idempotent for the identical pair
104	}
105	if _, exists := m.rev[value]; exists {
106		return false
107	}
108	if len(m.fwd) >= MaxPairs {
109		return false
110	}
111	m.fwd[key] = value
112	m.rev[value] = key
113	return true
114}
115
116// Delete removes the pair for key. Returns false when key is absent.
117func (m *BiMap) Delete(key string) bool {
118	v, ok := m.fwd[key]
119	if !ok {
120		return false
121	}
122	delete(m.fwd, key)
123	delete(m.rev, v)
124	return true
125}
126
127// DeleteValue removes the pair for value. Returns false when value is absent.
128func (m *BiMap) DeleteValue(value string) bool {
129	k, ok := m.rev[value]
130	if !ok {
131		return false
132	}
133	delete(m.fwd, k)
134	delete(m.rev, value)
135	return true
136}
137
138// Keys returns every key, sorted. Sorted, not map order: a Render built from an
139// unspecified order can differ between nodes.
140func (m *BiMap) Keys() []string { return sortedKeys(m.fwd) }
141
142// Values returns every value, sorted.
143func (m *BiMap) Values() []string { return sortedKeys(m.rev) }
144
145// Iterate calls fn for each pair in sorted key order. Returning true stops.
146func (m *BiMap) Iterate(fn func(key, value string) bool) {
147	for _, k := range m.Keys() {
148		if fn(k, m.fwd[k]) {
149			return
150		}
151	}
152}
153
154// Invert returns a new BiMap with keys and values swapped.
155func (m *BiMap) Invert() *BiMap {
156	out := New()
157	for k, v := range m.fwd {
158		out.fwd[v] = k
159		out.rev[k] = v
160	}
161	return out
162}
163
164// Clone returns an independent copy.
165func (m *BiMap) Clone() *BiMap {
166	out := New()
167	for k, v := range m.fwd {
168		out.fwd[k] = v
169		out.rev[v] = k
170	}
171	return out
172}
173
174// Consistent reports whether the two indexes agree. Always true through the
175// public API; exported so tests and callers can assert the invariant directly.
176func (m *BiMap) Consistent() bool {
177	if len(m.fwd) != len(m.rev) {
178		return false
179	}
180	for k, v := range m.fwd {
181		if back, ok := m.rev[v]; !ok || back != k {
182			return false
183		}
184	}
185	return true
186}
187
188func sortedKeys(m map[string]string) []string {
189	out := make([]string, 0, len(m))
190	for k := range m {
191		out = append(out, k)
192	}
193	sort.Strings(out)
194	return out
195}