Search Apps Documentation Source Content File Folder Download Copy Actions Download

collatz.gno

4.43 Kb · 186 lines
  1package collatz
  2
  3import (
  4	"chain"
  5	"chain/runtime/unsafe"
  6	"sort"
  7	"strconv"
  8	"strings"
  9
 10	"gno.land/p/nt/avl/v0"
 11)
 12
 13// maxIters caps the Collatz walk so a hostile / huge input cannot loop
 14// forever on-chain. No known n stays below this bound and fails to reach 1.
 15const maxIters = 1000000
 16
 17// maxSeqLen caps how many terms Render("/<n>") will print.
 18const maxSeqLen = 500
 19
 20// Result is one recorded submission.
 21type Result struct {
 22	N     int64
 23	Steps int
 24	Peak  int64
 25	Who   address
 26}
 27
 28// results maps a decimal-string key of N -> Result. avl.Tree keeps a
 29// deterministic order for iteration inside Render.
 30var results avl.Tree
 31
 32// stopping returns the number of steps to reach 1 and the peak value seen,
 33// walking the Collatz (hailstone) map. It never mutates state.
 34func stopping(n int64) (steps int, peak int64) {
 35	peak = n
 36	cur := n
 37	for cur != 1 {
 38		if steps >= maxIters {
 39			break
 40		}
 41		if cur%2 == 0 {
 42			cur = cur / 2
 43		} else {
 44			cur = 3*cur + 1
 45		}
 46		if cur > peak {
 47			peak = cur
 48		}
 49		steps++
 50	}
 51	return steps, peak
 52}
 53
 54// Compute walks the hailstone sequence for n and records the result
 55// attributed to the caller. It is a crossing (state-mutating) func.
 56func Compute(cur realm, n int) {
 57	if n <= 0 {
 58		panic("n must be > 0")
 59	}
 60	caller := unsafe.PreviousRealm().Address()
 61	nn := int64(n)
 62	steps, peak := stopping(nn)
 63
 64	res := Result{N: nn, Steps: steps, Peak: peak, Who: caller}
 65	key := strconv.FormatInt(nn, 10)
 66	results.Set(key, res)
 67
 68	chain.Emit(
 69		"Computed",
 70		"n", key,
 71		"steps", strconv.Itoa(steps),
 72		"peak", strconv.FormatInt(peak, 10),
 73		"who", caller.String(),
 74	)
 75}
 76
 77// byLongest implements sort.Interface, ordering results by steps desc,
 78// then peak desc, then n asc for stability.
 79type byLongest []Result
 80
 81func (b byLongest) Len() int      { return len(b) }
 82func (b byLongest) Swap(i, j int) { b[i], b[j] = b[j], b[i] }
 83func (b byLongest) Less(i, j int) bool {
 84	if b[i].Steps != b[j].Steps {
 85		return b[i].Steps > b[j].Steps
 86	}
 87	if b[i].Peak != b[j].Peak {
 88		return b[i].Peak > b[j].Peak
 89	}
 90	return b[i].N < b[j].N
 91}
 92
 93// Render shows the leaderboard at root, or the full hailstone sequence for a
 94// given n at path "/<n>".
 95func Render(path string) string {
 96	p := strings.TrimPrefix(path, "/")
 97	if p == "" {
 98		return renderLeaderboard()
 99	}
100	return renderSequence(p)
101}
102
103func renderLeaderboard() string {
104	all := make([]Result, 0, results.Size())
105	results.Iterate("", "", func(_ string, v interface{}) bool {
106		all = append(all, v.(Result))
107		return false
108	})
109
110	var sb strings.Builder
111	sb.WriteString("# Collatz Explorer\n\n")
112	sb.WriteString("Hailstone-sequence explorer. Call `Compute(n)` to record a run, ")
113	sb.WriteString("or view a full sequence at `/<n>`.\n\n")
114
115	if len(all) == 0 {
116		sb.WriteString("_No sequences submitted yet._\n")
117		return sb.String()
118	}
119
120	sort.Stable(byLongest(all))
121
122	sb.WriteString("## Leaderboard — longest sequences\n\n")
123	sb.WriteString("| # | n | steps | peak | who |\n")
124	sb.WriteString("|---|---|-------|------|-----|\n")
125	limit := len(all)
126	if limit > 20 {
127		limit = 20
128	}
129	for i := 0; i < limit; i++ {
130		r := all[i]
131		sb.WriteString("| " + strconv.Itoa(i+1) + " | ")
132		sb.WriteString("[" + strconv.FormatInt(r.N, 10) + "](/r:" + strconv.FormatInt(r.N, 10) + ") | ")
133		sb.WriteString(strconv.Itoa(r.Steps) + " | ")
134		sb.WriteString(strconv.FormatInt(r.Peak, 10) + " | ")
135		sb.WriteString(r.Who.String() + " |\n")
136	}
137	sb.WriteString("\n_" + strconv.Itoa(len(all)) + " sequence(s) recorded._\n")
138	return sb.String()
139}
140
141func renderSequence(p string) string {
142	n, err := strconv.ParseInt(p, 10, 64)
143	var sb strings.Builder
144	if err != nil || n <= 0 {
145		sb.WriteString("# Invalid n\n\n`" + p + "` is not a positive integer.\n")
146		return sb.String()
147	}
148
149	sb.WriteString("# Hailstone sequence for " + strconv.FormatInt(n, 10) + "\n\n")
150
151	seq := make([]int64, 0, 64)
152	cur := n
153	var peak int64 = n
154	truncated := false
155	for {
156		seq = append(seq, cur)
157		if cur == 1 {
158			break
159		}
160		if len(seq) >= maxSeqLen {
161			truncated = true
162			break
163		}
164		if cur%2 == 0 {
165			cur = cur / 2
166		} else {
167			cur = 3*cur + 1
168		}
169		if cur > peak {
170			peak = cur
171		}
172	}
173
174	parts := make([]string, len(seq))
175	for i, v := range seq {
176		parts[i] = strconv.FormatInt(v, 10)
177	}
178	sb.WriteString(strings.Join(parts, " → "))
179	sb.WriteString("\n\n")
180	sb.WriteString("- steps: " + strconv.Itoa(len(seq)-1) + "\n")
181	sb.WriteString("- peak: " + strconv.FormatInt(peak, 10) + "\n")
182	if truncated {
183		sb.WriteString("\n_Sequence truncated at " + strconv.Itoa(maxSeqLen) + " terms._\n")
184	}
185	return sb.String()
186}