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

levenshteindemo.gno

4.77 Kb · 150 lines
  1// Package levenshteindemo is a small gnoweb demo of the Levenshtein edit-distance
  2// library provided by [p/moul/x/daily/levenshtein](/p/moul/x/daily/levenshtein/v0):
  3// it renders an explanation, worked examples, and an interactive distance
  4// calculator with the full dynamic-programming table.
  5//
  6// It contains no distance logic of its own — everything comes from
  7// `levenshtein.Distance`, `levenshtein.Matrix` and `levenshtein.Similarity`.
  8package levenshteindemo
  9
 10import (
 11	"strconv"
 12	"strings"
 13
 14	"gno.land/p/moul/x/daily/levenshtein/v0"
 15)
 16
 17// Render implements the gnoweb view.
 18//
 19//   - "" or "/"            : explanation + examples.
 20//   - "/<a>/<b>"           : distance between a and b, with the DP table.
 21func Render(path string) string {
 22	path = strings.TrimPrefix(path, "/")
 23	if path == "" {
 24		return renderHome()
 25	}
 26
 27	parts := strings.SplitN(path, "/", 2)
 28	if len(parts) != 2 {
 29		var sb strings.Builder
 30		sb.WriteString("# Levenshtein\n\n")
 31		sb.WriteString("Provide two words as `/<a>/<b>`, e.g. [`/kitten/sitting`](/r/moul/x/daily/levenshteindemo/v0:kitten/sitting).\n\n")
 32		sb.WriteString("[← back](/r/moul/x/daily/levenshteindemo/v0)\n")
 33		return sb.String()
 34	}
 35
 36	a := parts[0]
 37	b := parts[1]
 38	dist := levenshtein.Distance(a, b)
 39	sim := levenshtein.Similarity(a, b)
 40
 41	var sb strings.Builder
 42	sb.WriteString("# Levenshtein distance\n\n")
 43	sb.WriteString("Transforming **`")
 44	sb.WriteString(a)
 45	sb.WriteString("`** → **`")
 46	sb.WriteString(b)
 47	sb.WriteString("`**\n\n")
 48	sb.WriteString("- **Edit distance:** `")
 49	sb.WriteString(strconv.Itoa(dist))
 50	sb.WriteString("` single-character edits (insert / delete / substitute)\n")
 51	sb.WriteString("- **Similarity:** `")
 52	sb.WriteString(strconv.Itoa(sim))
 53	sb.WriteString("%`\n\n")
 54
 55	sb.WriteString("## DP table\n\n")
 56	sb.WriteString("Each cell `d[i][j]` is the distance between the first *i* runes of `")
 57	sb.WriteString(a)
 58	sb.WriteString("` and the first *j* runes of `")
 59	sb.WriteString(b)
 60	sb.WriteString("`. The bottom-right cell is the answer.\n\n")
 61	sb.WriteString(renderTable(a, b))
 62	sb.WriteString("\n[← back](/r/moul/x/daily/levenshteindemo/v0)\n")
 63	return sb.String()
 64}
 65
 66func renderHome() string {
 67	var sb strings.Builder
 68	sb.WriteString("# Levenshtein edit distance\n\n")
 69	sb.WriteString("Demo of the [`p/moul/x/daily/levenshtein`](/p/moul/x/daily/levenshtein/v0) library. ")
 70	sb.WriteString("The **Levenshtein distance** between two strings is the minimum number of ")
 71	sb.WriteString("single-character edits — *insertions*, *deletions*, or *substitutions* — ")
 72	sb.WriteString("needed to turn one string into the other. The library implements the classic ")
 73	sb.WriteString("dynamic-programming algorithm (à la Go's `agext/levenshtein`) fully rune-aware and on-chain.\n\n")
 74
 75	sb.WriteString("## Try it\n\n")
 76	sb.WriteString("Append two words as `/<a>/<b>`:\n\n")
 77	examples := [][2]string{
 78		{"kitten", "sitting"},
 79		{"flaw", "lawn"},
 80		{"sunday", "saturday"},
 81		{"gno", "gnoland"},
 82	}
 83	for _, ex := range examples {
 84		a, b := ex[0], ex[1]
 85		d := levenshtein.Distance(a, b)
 86		sb.WriteString("- [`/")
 87		sb.WriteString(a)
 88		sb.WriteString("/")
 89		sb.WriteString(b)
 90		sb.WriteString("`](/r/moul/x/daily/levenshteindemo/v0:")
 91		sb.WriteString(a)
 92		sb.WriteString("/")
 93		sb.WriteString(b)
 94		sb.WriteString(") → distance **")
 95		sb.WriteString(strconv.Itoa(d))
 96		sb.WriteString("**\n")
 97	}
 98
 99	sb.WriteString("\n## The classic example\n\n")
100	sb.WriteString("`kitten` → `sitting` = **3**:\n\n")
101	sb.WriteString("1. `kitten` → `sitten` (substitute *k* → *s*)\n")
102	sb.WriteString("2. `sitten` → `sittin` (substitute *e* → *i*)\n")
103	sb.WriteString("3. `sittin` → `sitting` (insert *g* at the end)\n\n")
104
105	sb.WriteString("## API\n\n")
106	sb.WriteString("- `Distance(a, b string) int` — the edit distance.\n")
107	sb.WriteString("- `Matrix(a, b string) [][]int` — the full DP matrix.\n")
108	sb.WriteString("- `Similarity(a, b string) int` — a 0..100 similarity percentage.\n")
109	return sb.String()
110}
111
112// renderTable formats the DP matrix as a Markdown table with a and b as headers.
113func renderTable(a, b string) string {
114	ra := []rune(a)
115	rb := []rune(b)
116	d := levenshtein.Matrix(a, b)
117
118	var sb strings.Builder
119	// Header row: blank | "" | each rune of b.
120	sb.WriteString("|   | ε |")
121	for _, r := range rb {
122		sb.WriteString(" `")
123		sb.WriteString(string(r))
124		sb.WriteString("` |")
125	}
126	sb.WriteString("\n")
127	// Separator.
128	sb.WriteString("|---|")
129	for j := 0; j <= len(rb); j++ {
130		sb.WriteString("---|")
131	}
132	sb.WriteString("\n")
133	// Data rows.
134	for i := 0; i <= len(ra); i++ {
135		if i == 0 {
136			sb.WriteString("| **ε** |")
137		} else {
138			sb.WriteString("| **`")
139			sb.WriteString(string(ra[i-1]))
140			sb.WriteString("`** |")
141		}
142		for j := 0; j <= len(rb); j++ {
143			sb.WriteString(" ")
144			sb.WriteString(strconv.Itoa(d[i][j]))
145			sb.WriteString(" |")
146		}
147		sb.WriteString("\n")
148	}
149	return sb.String()
150}