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

collatz.gno

5.74 Kb · 222 lines
  1package collatz
  2
  3import (
  4	"chain"
  5	"sort"
  6	"strconv"
  7	"strings"
  8
  9	"gno.land/p/moul/md/v0"
 10	"gno.land/p/nt/avl/v0"
 11)
 12
 13// selfPath is this realm's own page. A root-relative link resolves against
 14// the domain, not the realm, so every link back into it names it in full.
 15const selfPath = "/r/moul/x/daily/collatz/v1"
 16
 17// maxIters caps the Collatz walk so a hostile / huge input cannot loop
 18// forever on-chain. No known n stays below this bound and fails to reach 1.
 19const maxIters = 1000000
 20
 21// MaxResults bounds the leaderboard's state: anyone may Compute, and each new
 22// n is a new entry the root page sorts.
 23const MaxResults = 1000
 24
 25// maxSeqLen caps how many terms Render("/<n>") will print.
 26const maxSeqLen = 500
 27
 28// Result is one recorded submission.
 29type Result struct {
 30	N     int64
 31	Steps int
 32	Peak  int64
 33	Who   address
 34}
 35
 36// results maps a decimal-string key of N -> Result. avl.Tree keeps a
 37// deterministic order for iteration inside Render.
 38var results avl.Tree
 39
 40// maxOdd is the largest odd term whose successor 3n+1 still fits in an
 41// int64. Past it the step wraps: 6148914691236517205 maps to exactly 2^64,
 42// which is 0, and the walk then spins on 0 until maxIters.
 43const maxOdd = (1<<63 - 1 - 1) / 3
 44
 45// stopping returns the number of steps to reach 1 and the peak value seen,
 46// walking the Collatz (hailstone) map. ok is false when a term would overflow
 47// int64, in which case steps and peak describe the walk up to that term. It
 48// never mutates state.
 49func stopping(n int64) (steps int, peak int64, ok bool) {
 50	peak = n
 51	cur := n
 52	for cur != 1 {
 53		if steps >= maxIters {
 54			break
 55		}
 56		if cur%2 == 0 {
 57			cur = cur / 2
 58		} else {
 59			if cur > maxOdd {
 60				return steps, peak, false
 61			}
 62			cur = 3*cur + 1
 63		}
 64		if cur > peak {
 65			peak = cur
 66		}
 67		steps++
 68	}
 69	return steps, peak, true
 70}
 71
 72// Compute walks the hailstone sequence for n and records the result
 73// attributed to the caller. It is a crossing (state-mutating) func.
 74func Compute(cur realm, n int) {
 75	if !cur.IsCurrent() {
 76		panic("collatz: spoofed realm")
 77	}
 78	caller := cur.Previous().Address()
 79	if n <= 0 {
 80		panic("n must be > 0")
 81	}
 82	nn := int64(n)
 83	steps, peak, ok := stopping(nn)
 84	if !ok {
 85		// Recording it would put a wrapped walk on the leaderboard, forever.
 86		panic("collatz: the walk from " + strconv.FormatInt(nn, 10) + " overflows int64")
 87	}
 88
 89	res := Result{N: nn, Steps: steps, Peak: peak, Who: caller}
 90	key := strconv.FormatInt(nn, 10)
 91	if !results.Has(key) && results.Size() >= MaxResults {
 92		panic("collatz: the leaderboard is full at " + strconv.Itoa(MaxResults) + " entries")
 93	}
 94	results.Set(key, res)
 95
 96	chain.Emit(
 97		"Computed",
 98		"n", key,
 99		"steps", strconv.Itoa(steps),
100		"peak", strconv.FormatInt(peak, 10),
101		"who", caller.String(),
102	)
103}
104
105// byLongest implements sort.Interface, ordering results by steps desc,
106// then peak desc, then n asc for stability.
107type byLongest []Result
108
109func (b byLongest) Len() int      { return len(b) }
110func (b byLongest) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
111func (b byLongest) Less(i, j int) bool {
112	if b[i].Steps != b[j].Steps {
113		return b[i].Steps > b[j].Steps
114	}
115	if b[i].Peak != b[j].Peak {
116		return b[i].Peak > b[j].Peak
117	}
118	return b[i].N < b[j].N
119}
120
121// Render shows the leaderboard at root, or the full hailstone sequence for a
122// given n at path "/<n>".
123func Render(path string) string {
124	p := strings.TrimPrefix(path, "/")
125	if p == "" {
126		return renderLeaderboard()
127	}
128	return renderSequence(p)
129}
130
131func renderLeaderboard() string {
132	all := make([]Result, 0, results.Size())
133	results.Iterate("", "", func(_ string, v interface{}) bool {
134		all = append(all, v.(Result))
135		return false
136	})
137
138	var sb strings.Builder
139	sb.WriteString("# Collatz Explorer\n\n")
140	sb.WriteString("Hailstone-sequence explorer. Call `Compute(n)` to record a run, ")
141	sb.WriteString("or view a full sequence at `/<n>`.\n\n")
142
143	if len(all) == 0 {
144		sb.WriteString("_No sequences submitted yet._\n")
145		return sb.String()
146	}
147
148	sort.Stable(byLongest(all))
149
150	sb.WriteString("## Leaderboard — longest sequences\n\n")
151	sb.WriteString("| # | n | steps | peak | who |\n")
152	sb.WriteString("|---|---|-------|------|-----|\n")
153	limit := len(all)
154	if limit > 20 {
155		limit = 20
156	}
157	for i := 0; i < limit; i++ {
158		r := all[i]
159		sb.WriteString("| " + strconv.Itoa(i+1) + " | ")
160		sb.WriteString("[" + strconv.FormatInt(r.N, 10) + "](" + selfPath + ":" + strconv.FormatInt(r.N, 10) + ") | ")
161		sb.WriteString(strconv.Itoa(r.Steps) + " | ")
162		sb.WriteString(strconv.FormatInt(r.Peak, 10) + " | ")
163		sb.WriteString(r.Who.String() + " |\n")
164	}
165	sb.WriteString("\n_" + strconv.Itoa(len(all)) + " sequence(s) recorded._\n")
166	return sb.String()
167}
168
169func renderSequence(p string) string {
170	n, err := strconv.ParseInt(p, 10, 64)
171	var sb strings.Builder
172	if err != nil || n <= 0 {
173		sb.WriteString("# Invalid n\n\n" + md.InlineCode(p) + " is not a positive integer.\n")
174		return sb.String()
175	}
176
177	sb.WriteString("# Hailstone sequence for " + strconv.FormatInt(n, 10) + "\n\n")
178
179	seq := make([]int64, 0, 64)
180	cur := n
181	var peak int64 = n
182	truncated := false
183	overflow := false
184	for {
185		seq = append(seq, cur)
186		if cur == 1 {
187			break
188		}
189		if len(seq) >= maxSeqLen {
190			truncated = true
191			break
192		}
193		if cur%2 == 0 {
194			cur = cur / 2
195		} else {
196			if cur > maxOdd {
197				overflow = true
198				break
199			}
200			cur = 3*cur + 1
201		}
202		if cur > peak {
203			peak = cur
204		}
205	}
206
207	parts := make([]string, len(seq))
208	for i, v := range seq {
209		parts[i] = strconv.FormatInt(v, 10)
210	}
211	sb.WriteString(strings.Join(parts, " → "))
212	sb.WriteString("\n\n")
213	sb.WriteString("- steps: " + strconv.Itoa(len(seq)-1) + "\n")
214	sb.WriteString("- peak: " + strconv.FormatInt(peak, 10) + "\n")
215	if overflow {
216		sb.WriteString("\n_Stopped: the next term would overflow int64._\n")
217	}
218	if truncated {
219		sb.WriteString("\n_Sequence truncated at " + strconv.Itoa(maxSeqLen) + " terms._\n")
220	}
221	return sb.String()
222}