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.

fenwick.gno

6.58 Kb · 222 lines
  1// Package fenwick is an order-statistic Fenwick (binary indexed) tree over
  2// non-negative int64 weights, backed by avl.Tree so it can live in realm
  3// state.
  4//
  5// It exists to answer one question in O(log N): given a random unit k drawn
  6// from [0, Total()), which index owns that unit? That is the weighted-raffle
  7// draw — each index's value is its ticket count, and Select maps a ticket
  8// number to its owner. The naive version of that answer walks every index
  9// accumulating values until the running total passes k, which is O(N) and
 10// will eventually exceed a block's gas limit. Select never walks: it descends
 11// the tree's binary structure, touching at most ~log2(N) nodes.
 12//
 13// DESIGN DECISION — negative deltas are rejected, not supported. Add panics
 14// on a negative delta and Append panics on a negative initial value. This
 15// structure is append-and-grow only. The reason is that Select is only
 16// meaningful while prefix sums are monotonically non-decreasing: with a
 17// negative value somewhere, "the index owning unit k" is not well defined and
 18// the binary descent would silently return a wrong owner rather than fail.
 19// Refusing the input loudly is the only honest option, so a caller that needs
 20// decreasing weights gets a panic instead of a corrupt draw. The rest of the
 21// structure (Add, Append, Total, ValueAt, Len) is otherwise a general,
 22// reusable Fenwick tree.
 23//
 24// Complexity: Append O(log N), Add O(log N), Select O(log N), Total O(1),
 25// ValueAt O(log N) (one avl lookup), Len O(1).
 26package fenwick
 27
 28import (
 29	"math/bits"
 30	"strconv"
 31
 32	"gno.land/p/nt/avl/v0"
 33)
 34
 35// keyWidth is enough decimal digits for any non-negative int64 (max is
 36// 9223372036854775807, 19 digits). Keys are fixed-width and zero-padded so
 37// that avl.Tree's lexicographic key order matches numeric index order; this
 38// mirrors roundKey in r/splash/beacon.
 39const keyWidth = 19
 40
 41const zeroPad = "0000000000000000000"
 42
 43// indexKey renders i as a fixed-width, zero-padded decimal string.
 44func indexKey(i int) string {
 45	s := strconv.Itoa(i)
 46	if len(s) < keyWidth {
 47		s = zeroPad[:keyWidth-len(s)] + s
 48	}
 49	return s
 50}
 51
 52// Tree is an order-statistic Fenwick tree.
 53//
 54// Storage is two avl.Trees keyed by index (never a Go map or a slice, so that
 55// realm state stays deterministic and persistent):
 56//
 57//   - values holds the caller-visible value at each 0-based index.
 58//   - nodes holds the Fenwick partial sums, keyed by 1-based position, where
 59//     node j covers the value range (j-lsb(j), j].
 60//
 61// total is maintained incrementally so Total is O(1).
 62type Tree struct {
 63	values *avl.Tree
 64	nodes  *avl.Tree
 65	n      int
 66	total  int64
 67}
 68
 69// NewTree returns an empty tree.
 70func NewTree() *Tree {
 71	return &Tree{
 72		values: avl.NewTree(),
 73		nodes:  avl.NewTree(),
 74	}
 75}
 76
 77// Len returns the number of indices.
 78func (t *Tree) Len() int {
 79	return t.n
 80}
 81
 82// Total returns the sum of all values. O(1).
 83func (t *Tree) Total() int64 {
 84	return t.total
 85}
 86
 87// node reads the 1-based Fenwick node at position j. Every position in
 88// [1, n] is written at Append time, so a live position is always present;
 89// Has is still checked because avl.Get returns a single value and would
 90// otherwise type-assert nil on a missing key.
 91func (t *Tree) node(j int) int64 {
 92	key := indexKey(j)
 93	if !t.nodes.Has(key) {
 94		return 0
 95	}
 96	return t.nodes.Get(key).(int64)
 97}
 98
 99func (t *Tree) setNode(j int, v int64) {
100	t.nodes.Set(indexKey(j), v)
101}
102
103// ValueAt returns the value stored at index i. Panics if i is out of range.
104func (t *Tree) ValueAt(i int) int64 {
105	if i < 0 || i >= t.n {
106		panic("fenwick: index out of range")
107	}
108	key := indexKey(i)
109	if !t.values.Has(key) {
110		return 0
111	}
112	return t.values.Get(key).(int64)
113}
114
115// lsb returns the lowest set bit of j (j > 0).
116func lsb(j int) int {
117	return j & -j
118}
119
120// Append adds a new index at the end with the given initial value and
121// returns its 0-based index. Panics if value is negative: see the
122// append-and-grow note in the package doc.
123func (t *Tree) Append(value int64) int {
124	if value < 0 {
125		panic("fenwick: negative value; tree is append-and-grow only")
126	}
127	t.addTotal(value)
128
129	i := t.n
130	t.n++
131	t.values.Set(indexKey(i), value)
132
133	// The new 1-based position j covers (j-lsb(j), j]. Seed its node with
134	// the new value plus the nodes of the children that decompose
135	// (j-lsb(j), j-1], which is O(log N) reads and keeps the invariant
136	// without rebuilding anything.
137	j := t.n
138	sum := value
139	for c := j - 1; c > j-lsb(j); c -= lsb(c) {
140		sum += t.node(c)
141	}
142	t.setNode(j, sum)
143	return i
144}
145
146// Add increases the value at index i by delta. i must be in [0, Len()).
147// Panics on a negative delta: this structure is append-and-grow only, and
148// silently accepting a decrease would make Select return wrong owners rather
149// than fail (see the package doc). A zero delta is accepted and is a no-op.
150func (t *Tree) Add(i int, delta int64) {
151	if i < 0 || i >= t.n {
152		panic("fenwick: index out of range")
153	}
154	if delta < 0 {
155		panic("fenwick: negative delta; tree is append-and-grow only")
156	}
157	if delta == 0 {
158		return
159	}
160	t.addTotal(delta)
161
162	key := indexKey(i)
163	t.values.Set(key, t.ValueAt(i)+delta)
164
165	for j := i + 1; j <= t.n; j += lsb(j) {
166		t.setNode(j, t.node(j)+delta)
167	}
168}
169
170// addTotal accumulates into total, refusing to wrap. An int64 overflow here
171// would corrupt every later Select, so it is a panic and not a silent wrap.
172func (t *Tree) addTotal(delta int64) {
173	if t.total+delta < t.total {
174		panic("fenwick: total overflows int64")
175	}
176	t.total += delta
177}
178
179// Prefix returns the sum of values at indices [0, i). Panics if i is outside
180// [0, Len()]. O(log N).
181func (t *Tree) Prefix(i int) int64 {
182	if i < 0 || i > t.n {
183		panic("fenwick: index out of range")
184	}
185	var sum int64
186	for j := i; j > 0; j -= lsb(j) {
187		sum += t.node(j)
188	}
189	return sum
190}
191
192// Select returns the index owning cumulative unit k, that is the unique i
193// with Prefix(i) <= k < Prefix(i+1). Panics if k is outside [0, Total()).
194// O(log N): a binary descent over the tree's node positions, never a walk
195// over indices.
196//
197// An index whose value is 0 owns no units and is therefore never returned:
198// the descent takes the largest position whose prefix is still <= k, which
199// steps past any zero-valued index.
200func (t *Tree) Select(k int64) int {
201	if k < 0 || k >= t.total {
202		panic("fenwick: k out of range")
203	}
204
205	// Start from the highest power of two <= n and halve each step.
206	pos := 0
207	rem := k
208	for step := 1 << (bits.Len(uint(t.n)) - 1); step > 0; step >>= 1 {
209		next := pos + step
210		if next > t.n {
211			continue
212		}
213		v := t.node(next)
214		if v <= rem {
215			pos = next
216			rem -= v
217		}
218	}
219	// pos is the count of indices whose cumulative sum is still <= k, which
220	// is exactly the 0-based index owning unit k.
221	return pos
222}