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

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}