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

fenwick.gno

6.78 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/moul/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}