Transaction
D5100AB44FB162…606B1FEC17C4
Block 25,926 · index 0 · indexed
Summary
- Hash
- D5100AB44FB1620678BCBBA1DACC239781ED459575834B42B2FA606B1FEC17C4
- Block
- 25,926
- Size
- 8214 bytes
- Gas used
- 12,943,696 / 15,532,495
- Fee
- 1000000ugnot
- Memo
- gnopublish
- Status
- success
Messages
Arguments · 9
- #1bidimap
- #2README.md
- #3# `gno.land/p/moul/x/daily/bidimap/v0` **Bidirectional map, unique both ways** — `New`, `Put`, `PutUnique`, `Get`, `GetKey`, `Has`, `HasValue`, `Delete`, `DeleteValue`, `Keys`, `Values`, `Iterate`, `Invert`, `Clone`, `Consistent`, `MaxPairs`. ```go import "gno.land/p/moul/x/daily/bidimap/v0" m := bidimap.New() m.Put("alice", "admin") m.Get("alice") // "admin", true m.GetKey("admin") // "alice", true — O(1), not a scan ``` Both sides are unique, which is the interesting constraint: inserting a pair whose value already belongs to another key must do something deliberate rather than silently corrupt the reverse index. - **`Put` replaces**, and returns the pairs it displaced, so the caller sees what it evicted rather than discovering it later. - **`PutUnique` refuses** instead, returning `false` and changing nothing. What is not on offer is a half-updated map. `Consistent()` is exported so callers and tests can assert the two indexes agree; it is true through the whole public API. One subtlety with `MaxPairs` (4096): a full map **still accepts** a `Put` that rebinds an existing key or steals an existing value, because that reuses a slot rather than growing the map. Only a pair new on *both* sides is refused. `Keys`/`Values` come back **sorted**, never in map order: gno map iteration order is unspecified, and a `Render` built from one can differ between nodes. **Live demo:** [`r/moul/x/daily/bidimapdemo`](https://github.com/moul/gno-contracts/tree/main/r/moul/x/daily/bidimapdemo/v0) · render it at [`/r/moul/x/daily/bidimapdemo/v0`](https://gno.land/r/moul/x/daily/bidimapdemo/v0). <!-- BEGIN GNOCONTRACTS FOOTER (generated by `make readmes`; do not edit below) --> --- Part of **[moul/gno-contracts](https://github.com/moul/gno-contracts)** — moul's versioned gno.land contracts. See the repository for the full catalog, build/test tooling, and usage. > 🧪 **Highly experimental — potentially vibe-coded.** Not audited; may break, change, or be removed at any time. Do not use with anything of value. Full disclaimer: [DISCLAIMER](https://github.com/moul/gno-contracts/blob/main/DISCLAIMER.md). <!-- END GNOCONTRACTS FOOTER -->
- #4bidimap.gno
- #5// Package bidimap is a bidirectional map — unique in both directions — as a // pure, reusable package. // // A normal map answers "what is the value for this key". A bidirectional one // also answers the reverse in O(1), by keeping a second index. The cost is an // invariant a plain pair of maps does not give you: BOTH sides are unique, so // inserting a pair whose value already belongs to another key must do something // deliberate rather than silently corrupt the reverse index. // // This implementation makes that choice explicit. Put REPLACES: it evicts any // existing pairing on either side first, so the two indexes can never disagree. // PutUnique refuses instead, returning false. Pick whichever the caller wants; // what is not on offer is a half-updated map. // // Iteration is over sorted keys, never a built-in map range: gno map iteration // order is unspecified, and a Render built from one can differ between nodes, // which is a consensus bug rather than a cosmetic one. // // A live demo of this package is at // [r/moul/x/daily/bidimapdemo](/r/moul/x/daily/bidimapdemo/v0). package bidimap import "sort" // MaxPairs bounds the map so gas stays predictable. const MaxPairs = 4096 // BiMap is a string<->string map, unique in both directions. type BiMap struct { fwd map[string]string rev map[string]string } // New returns an empty BiMap. func New() *BiMap { return &BiMap{fwd: map[string]string{}, rev: map[string]string{}} } // Len returns the number of pairs. func (m *BiMap) Len() int { return len(m.fwd) } // Get returns the value bound to key. func (m *BiMap) Get(key string) (string, bool) { v, ok := m.fwd[key] return v, ok } // GetKey returns the key bound to value — the reverse lookup, also O(1). func (m *BiMap) GetKey(value string) (string, bool) { k, ok := m.rev[value] return k, ok } // Has reports whether key is present. func (m *BiMap) Has(key string) bool { _, ok := m.fwd[key]; return ok } // HasValue reports whether value is present. func (m *BiMap) HasValue(value string) bool { _, ok := m.rev[value]; return ok } // Put binds key<->value, REPLACING any existing pairing on either side. It // returns the pairs that were evicted to make room, so the caller can see what // it displaced rather than discovering it later. // // Returns ok=false only when the map is full and the pair is entirely new. func (m *BiMap) Put(key, value string) (evicted [][2]string, ok bool) { oldValue, keyTaken := m.fwd[key] // Already exactly this pair: nothing to do. if keyTaken && oldValue == value { return nil, true } oldKey, valueTaken := m.rev[value] // Only a pair that is new on BOTH sides grows the map. Rebinding either // side reuses a slot, so it stays allowed at capacity. Checked up front: // evicting first and rolling back on failure would be unreachable code, // since any eviction frees the very slot the check is about. if !keyTaken && !valueTaken && len(m.fwd) >= MaxPairs { return nil, false } if keyTaken { evicted = append(evicted, [2]string{key, oldValue}) delete(m.rev, oldValue) delete(m.fwd, key) } if valueTaken { evicted = append(evicted, [2]string{oldKey, value}) delete(m.fwd, oldKey) delete(m.rev, value) } m.fwd[key] = value m.rev[value] = key return evicted, true } // PutUnique binds key<->value only when NEITHER side is already taken by a // different pairing. Returns false without changing anything otherwise. func (m *BiMap) PutUnique(key, value string) bool { if v, exists := m.fwd[key]; exists { return v == value // idempotent for the identical pair } if _, exists := m.rev[value]; exists { return false } if len(m.fwd) >= MaxPairs { return false } m.fwd[key] = value m.rev[value] = key return true } // Delete removes the pair for key. Returns false when key is absent. func (m *BiMap) Delete(key string) bool { v, ok := m.fwd[key] if !ok { return false } delete(m.fwd, key) delete(m.rev, v) return true } // DeleteValue removes the pair for value. Returns false when value is absent. func (m *BiMap) DeleteValue(value string) bool { k, ok := m.rev[value] if !ok { return false } delete(m.fwd, k) delete(m.rev, value) return true } // Keys returns every key, sorted. Sorted, not map order: a Render built from an // unspecified order can differ between nodes. func (m *BiMap) Keys() []string { return sortedKeys(m.fwd) } // Values returns every value, sorted. func (m *BiMap) Values() []string { return sortedKeys(m.rev) } // Iterate calls fn for each pair in sorted key order. Returning true stops. func (m *BiMap) Iterate(fn func(key, value string) bool) { for _, k := range m.Keys() { if fn(k, m.fwd[k]) { return } } } // Invert returns a new BiMap with keys and values swapped. func (m *BiMap) Invert() *BiMap { out := New() for k, v := range m.fwd { out.fwd[v] = k out.rev[k] = v } return out } // Clone returns an independent copy. func (m *BiMap) Clone() *BiMap { out := New() for k, v := range m.fwd { out.fwd[k] = v out.rev[v] = k } return out } // Consistent reports whether the two indexes agree. Always true through the // public API; exported so tests and callers can assert the invariant directly. func (m *BiMap) Consistent() bool { if len(m.fwd) != len(m.rev) { return false } for k, v := range m.fwd { if back, ok := m.rev[v]; !ok || back != k { return false } } return true } func sortedKeys(m map[string]string) []string { out := make([]string, 0, len(m)) for k := range m { out = append(out, k) } sort.Strings(out) return out }
- #6gnomod.toml
- #7module = "gno.land/p/moul/x/daily/bidimap/v0" gno = "0.9"
- #8/gno.MemPackageType
- #9 MPUserProd
Result log
msg:0,success:true,log:,events:[]