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.79 Kb · 211 lines
  1// Package fenwick is a Binary Indexed Tree (Peter Fenwick, 1994): an array of
  2// int64 that answers a prefix sum and applies a point update in O(log n), with
  3// no extra storage beyond the array itself.
  4//
  5// The tradeoff it occupies is the whole reason to reach for one. A plain slice
  6// updates in O(1) and sums in O(n). A slice of running totals sums in O(1) and
  7// updates in O(n). A Fenwick tree does both in O(log n), which is what a realm
  8// wants when reads and writes are interleaved: a scoreboard written by every
  9// player and read by every render.
 10//
 11// The operation that actually justifies the package is [Tree.SearchPrefix]: the
 12// smallest index whose prefix sum exceeds a target, in O(log n), by descending
 13// the tree rather than scanning it. That is the primitive behind a
 14// stake-weighted draw (pick a number below Total, ask which holder owns it) and
 15// behind a leaderboard's rank lookup. Prefix sums on their own would not be
 16// worth a data structure.
 17//
 18// Reads are total and writes are strict, deliberately: see the package README.
 19//
 20// A live demo of this package is at
 21// [r/moul/x/daily/fenwickdemo](/r/nym-moultest123/x/daily/fenwickdemo/v0).
 22package fenwick
 23
 24// MaxSize bounds the number of slots so a caller cannot size the tree from
 25// unbounded user input and make the allocation itself the attack. It is not a
 26// gas figure: a query costs O(log n) regardless, and the cap exists so the one
 27// O(n) operation in the package, construction, stays bounded.
 28const MaxSize = 65536
 29
 30// Tree is a Binary Indexed Tree over int64 values, addressed 0-based.
 31//
 32// The zero value is not usable; build one with [New] or [FromSlice].
 33type Tree struct {
 34	n int
 35	// t is 1-based internally: t[i] holds the sum of the LowBit(i) values
 36	// ending at i. Index 0 is unused, which is what makes the bit tricks work.
 37	t []int64
 38}
 39
 40// New returns a Tree of n zero-valued slots.
 41//
 42// It panics if n is negative or above [MaxSize]: both are programmer errors
 43// that a caller cannot recover from, and a silently clamped size would hand
 44// back a tree that disagrees with the caller about its own length.
 45func New(n int) *Tree {
 46	if n < 0 {
 47		panic("fenwick: negative size")
 48	}
 49	if n > MaxSize {
 50		panic("fenwick: size above MaxSize")
 51	}
 52	return &Tree{n: n, t: make([]int64, n+1)}
 53}
 54
 55// FromSlice returns a Tree holding vals, built in O(n) rather than by n
 56// successive Add calls, which would cost O(n log n).
 57//
 58// The slice is copied; later writes to vals do not reach the tree.
 59func FromSlice(vals []int64) *Tree {
 60	tr := New(len(vals))
 61	// Seed each slot, then push it into its parent once. Every slot is visited
 62	// exactly once, which is the whole O(n) construction.
 63	for i, v := range vals {
 64		tr.t[i+1] += v
 65		if parent := (i + 1) + lowBit(i+1); parent <= tr.n {
 66			tr.t[parent] += tr.t[i+1]
 67		}
 68	}
 69	return tr
 70}
 71
 72// Len returns the number of slots.
 73func (tr *Tree) Len() int { return tr.n }
 74
 75// Add adds delta to the value at i.
 76//
 77// It panics if i is out of range. A write is a transaction: failing loudly is
 78// the correct outcome, and the caller still has the chance to abort.
 79func (tr *Tree) Add(i int, delta int64) {
 80	if i < 0 || i >= tr.n {
 81		panic("fenwick: index out of range")
 82	}
 83	for j := i + 1; j <= tr.n; j += lowBit(j) {
 84		tr.t[j] += delta
 85	}
 86}
 87
 88// Set replaces the value at i, returning the value it displaced.
 89//
 90// It panics if i is out of range, for the same reason [Tree.Add] does.
 91func (tr *Tree) Set(i int, v int64) int64 {
 92	old := tr.At(i)
 93	if i < 0 || i >= tr.n {
 94		panic("fenwick: index out of range")
 95	}
 96	tr.Add(i, v-old)
 97	return old
 98}
 99
100// Prefix returns the sum of the first i values, that is of [0, i).
101//
102// It is total: i is clamped into [0, Len], so Prefix(-5) is 0 and Prefix(1e9)
103// is Total. Reads are what Render calls, and a panicking Render is a page that
104// can never be displayed again, on a path that can never be redeployed.
105func (tr *Tree) Prefix(i int) int64 {
106	if i > tr.n {
107		i = tr.n
108	}
109	var sum int64
110	for ; i > 0; i -= lowBit(i) {
111		sum += tr.t[i]
112	}
113	return sum
114}
115
116// Range returns the sum of [lo, hi). It is total: bounds are clamped, and an
117// empty or inverted range is 0.
118func (tr *Tree) Range(lo, hi int) int64 {
119	if lo < 0 {
120		lo = 0
121	}
122	if hi > tr.n {
123		hi = tr.n
124	}
125	if lo >= hi {
126		return 0
127	}
128	return tr.Prefix(hi) - tr.Prefix(lo)
129}
130
131// At returns the value at i, or 0 if i is out of range. Total, like the other
132// reads.
133func (tr *Tree) At(i int) int64 {
134	if i < 0 || i >= tr.n {
135		return 0
136	}
137	return tr.Range(i, i+1)
138}
139
140// Total returns the sum of every value.
141func (tr *Tree) Total() int64 { return tr.Prefix(tr.n) }
142
143// Slice materializes the tree back into a plain slice, in O(n log n).
144//
145// It exists for rendering and for tests. A hot path should not call it.
146func (tr *Tree) Slice() []int64 {
147	out := make([]int64, tr.n)
148	for i := 0; i < tr.n; i++ {
149		out[i] = tr.At(i)
150	}
151	return out
152}
153
154// SearchPrefix returns the smallest index i for which Prefix(i+1) > target,
155// or Len if no prefix exceeds it. It runs in O(log n) by descending the tree.
156//
157// With non-negative values this is the weighted select: draw a target in
158// [0, Total) and SearchPrefix names the slot that owns it, each slot chosen in
159// proportion to its value. A zero-valued slot can never be selected, which is
160// the property that makes it usable for a stake-weighted draw.
161//
162// It is total, and a negative target returns 0.
163//
164// The result is only meaningful when every value is non-negative: with a
165// negative value in the tree the prefix sums stop increasing and "the smallest
166// index whose prefix exceeds target" stops being well defined. The package does
167// not forbid negative values, because Add with a negative delta is the ordinary
168// way to decrement a counter; it is SearchPrefix alone that needs them absent.
169func (tr *Tree) SearchPrefix(target int64) int {
170	if target < 0 {
171		return 0
172	}
173	pos := 0
174	// Walk the powers of two downward, largest first. At each step, take the
175	// jump if the value it would accumulate still does not exceed target.
176	for step := highBit(tr.n); step > 0; step >>= 1 {
177		if next := pos + step; next <= tr.n && tr.t[next] <= target {
178			pos = next
179			target -= tr.t[next]
180		}
181	}
182	return pos
183}
184
185// lowBit returns the lowest set bit of i, the range width the slot covers.
186func lowBit(i int) int { return i & (-i) }
187
188// highBit returns the highest power of two that is <= n, or 0 when n is 0.
189func highBit(n int) int {
190	p := 1
191	for p<<1 <= n {
192		p <<= 1
193	}
194	if n == 0 {
195		return 0
196	}
197	return p
198}
199
200// Covers returns the half-open range [lo, hi) of 0-based slots that the
201// internal node at 1-based position i summarizes, which is what makes the
202// structure legible rather than magic. It is exported for the demo and for
203// anyone trying to see the shape; it is not needed to use the tree.
204//
205// It returns (0, 0) when i is outside [1, Len].
206func (tr *Tree) Covers(i int) (int, int) {
207	if i < 1 || i > tr.n {
208		return 0, 0
209	}
210	return i - lowBit(i), i
211}