Realm
gno.land/r/moul/x/daily/fenwickdemo/v0
Overview
Realm Path
gno.land/r/moul/x/daily/fenwickdemo/v0
Exported Functions
1
State Entries
3
Source Files
3
Total Package Entries
6
Exported Functions
1 exported function
State
3 state entries
Source Code
FILES
fenwickdemo.gno
go
1// Package fenwickdemo is a small gnoweb demo of the Binary Indexed Tree
2// provided by the [p/moul/x/daily/fenwick](/p/moul/x/daily/fenwick/v0)
3// library: prefix sums, range queries, the internal ranges that make the
4// structure legible, and the weighted draw that is the reason to use one.
5//
6// It contains no tree logic of its own. Stateless, so Render is deterministic:
7// every figure below is computed from the fixed tables at the top of this file,
8// never from chain state, which is what lets an example test pin the page.
9package fenwickdemo
10
11import (
12 "strconv"
13 "strings"
14
15 "gno.land/p/moul/kit/ui/v0"
16 "gno.land/p/moul/x/daily/fenwick/v0"
17)
18
19// scores is the worked example: eight slots, small enough to check by hand.
20var scores = []int64{3, 1, 4, 1, 5, 9, 2, 6}
21
22// stakes is the weighted-draw example. The zero is the interesting entry: a
23// holder with no stake must never be drawn, and that is a property of the
24// search, not of the caller remembering to skip them.
25var (
26 holders = []string{"alice", "bob", "carol", "dave", "erin"}
27 stakes = []int64{30, 0, 45, 5, 20}
28)
29
30// Render renders the demo for gnoweb.
31//
32// Render("") / Render("/") -> the full demo
33func Render(path string) string {
34 var b strings.Builder
35
36 b.WriteString("# Fenwick tree\n\n")
37 b.WriteString("A Binary Indexed Tree: prefix sums and point updates both in ")
38 b.WriteString("`O(log n)`, demoing the ")
39 b.WriteString("[`p/moul/x/daily/fenwick`](/p/moul/x/daily/fenwick/v0) library.\n\n")
40
41 tr := fenwick.FromSlice(scores)
42
43 b.WriteString("## The values\n\n")
44 b.WriteString("`" + joinInts(scores) + "` — " + strconv.Itoa(tr.Len()))
45 b.WriteString(" slots totalling `" + strconv.FormatInt(tr.Total(), 10) + "`.\n\n")
46
47 b.WriteString("## Prefix sums\n\n")
48 pt := ui.NewTable("i", "value", "Prefix(i+1)")
49 for i := 0; i < tr.Len(); i++ {
50 pt.Row(strconv.Itoa(i),
51 strconv.FormatInt(tr.At(i), 10),
52 strconv.FormatInt(tr.Prefix(i+1), 10))
53 }
54 b.WriteString(pt.String() + "\n")
55
56 b.WriteString("## Range queries\n\n")
57 rt := ui.NewTable("call", "meaning", "result")
58 rt.Row("`Range(2, 5)`", "slots 2, 3, 4", strconv.FormatInt(tr.Range(2, 5), 10))
59 rt.Row("`Range(0, 8)`", "everything", strconv.FormatInt(tr.Range(0, 8), 10))
60 rt.Row("`Range(5, 5)`", "empty", strconv.FormatInt(tr.Range(5, 5), 10))
61 rt.Row("`Range(-9, 99)`", "clamped to the ends", strconv.FormatInt(tr.Range(-9, 99), 10))
62 b.WriteString(rt.String() + "\n")
63 b.WriteString("Reads clamp instead of panicking. A realm cannot be redeployed on ")
64 b.WriteString("the same path, so a `Render` that panics on an out-of-range index ")
65 b.WriteString("is a page that is broken for good. Writes do panic: a bad `Add` is ")
66 b.WriteString("a transaction, and aborting it is the useful answer.\n\n")
67
68 b.WriteString("## What the tree actually stores\n\n")
69 b.WriteString("The array is not a copy of the values. Each internal slot holds the ")
70 b.WriteString("sum of a run of them, and the run lengths are the powers of two in ")
71 b.WriteString("the index. That is the whole trick, and `Covers` makes it visible.\n\n")
72 ct := ui.NewTable("node", "covers slots", "sum")
73 for i := 1; i <= tr.Len(); i++ {
74 lo, hi := tr.Covers(i)
75 ct.Row(strconv.Itoa(i),
76 "`["+strconv.Itoa(lo)+", "+strconv.Itoa(hi)+")`",
77 strconv.FormatInt(tr.Range(lo, hi), 10))
78 }
79 b.WriteString(ct.String() + "\n")
80 b.WriteString("A prefix walk visits one node per set bit of the index, and those ")
81 b.WriteString("nodes tile the prefix exactly: no gap, no overlap. Eight slots ")
82 b.WriteString("means at most three nodes per query.\n\n")
83
84 b.WriteString("## The weighted draw\n\n")
85 b.WriteString("`SearchPrefix(target)` names the slot that owns a target drawn ")
86 b.WriteString("below `Total`, in `O(log n)`. Each holder is picked in proportion ")
87 b.WriteString("to their stake, and a zero stake can never be picked at all.\n\n")
88 st := fenwick.FromSlice(stakes)
89 wt := ui.NewTable("holder", "stake", "owns targets", "share")
90 for i, h := range holders {
91 lo := st.Prefix(i)
92 hi := st.Prefix(i + 1)
93 owns := "none"
94 if hi > lo {
95 owns = "`[" + strconv.FormatInt(lo, 10) + ", " + strconv.FormatInt(hi, 10) + ")`"
96 }
97 wt.Row(ui.Cell(h),
98 strconv.FormatInt(stakes[i], 10),
99 owns,
100 pct(stakes[i], st.Total()))
101 }
102 b.WriteString(wt.String() + "\n")
103
104 dt := ui.NewTable("target", "SearchPrefix", "holder")
105 for _, target := range []int64{0, 29, 30, 74, 99} {
106 i := st.SearchPrefix(target)
107 dt.Row(strconv.FormatInt(target, 10), strconv.Itoa(i), ui.Cell(holders[i]))
108 }
109 b.WriteString(dt.String() + "\n")
110 b.WriteString("Note target `30`: it is the first one past alice's share, and it ")
111 b.WriteString("skips bob entirely rather than landing on a holder with nothing ")
112 b.WriteString("staked. The search steps over zero-weight slots because their ")
113 b.WriteString("prefix does not advance, not because anything checks for them.\n\n")
114
115 b.WriteString("## Why not a plain slice\n\n")
116 b.WriteString("Two obvious implementations each win one column and lose another. ")
117 b.WriteString("A realm whose scoreboard is written by every player and read by ")
118 b.WriteString("every page view pays both costs, which is where the middle row ")
119 b.WriteString("earns its keep.\n\n")
120 xt := ui.NewTable("structure", "point update", "prefix sum", "weighted draw")
121 xt.Row("plain slice", "`O(1)`", "`O(n)`", "`O(n)`")
122 xt.Row("**Fenwick tree**", "`O(log n)`", "`O(log n)`", "`O(log n)`")
123 xt.Row("running totals", "`O(n)`", "`O(1)`", "`O(log n)`")
124 b.WriteString(xt.String() + "\n")
125 b.WriteString("The tree also carries no storage overhead: it is exactly `n` ")
126 b.WriteString("values, rearranged.\n")
127
128 return b.String()
129}
130
131// joinInts formats a slice for display. Written out rather than reached for
132// from a library because there is no sensible shared home for it yet.
133func joinInts(vals []int64) string {
134 parts := make([]string, len(vals))
135 for i, v := range vals {
136 parts[i] = strconv.FormatInt(v, 10)
137 }
138 return strings.Join(parts, ", ")
139}
140
141// pct renders part/whole as a whole-number percentage.
142//
143// Integer arithmetic throughout: Render output has to be byte-identical on
144// every validating node, and floating point is the usual way that stops being
145// true.
146func pct(part, whole int64) string {
147 if whole == 0 {
148 return "0%"
149 }
150 return strconv.FormatInt(part*100/whole, 10) + "%"
151}
152Raw Package Data
Raw JSON data