soundexdemo.gno
2.40 Kb · 93 lines
1// Package soundexdemo is a small gnoweb demo of the Soundex phonetic algorithm
2// provided by the [p/moul/x/daily/soundex](/p/moul/x/daily/soundex/v0) library:
3// it groups a small name list by phonetic key so the "sounds alike" clusters
4// are visible.
5//
6// It contains no phonetic logic of its own. Stateless, so Render is
7// deterministic — names are grouped in first-seen order, never by iterating a
8// map.
9package soundexdemo
10
11import (
12 "strings"
13
14 "gno.land/p/moul/x/daily/soundex/v0"
15)
16
17// names include several deliberate near-collisions.
18var names = []string{
19 "Robert", "Rupert", "Rubin",
20 "Ashcraft", "Ashcroft",
21 "Tymczak", "Pfister", "Honeyman",
22}
23
24// Render renders the demo for gnoweb.
25//
26// Render("") / Render("/") -> the name table + clusters
27// Render("/<name>") -> that name's key
28func Render(path string) string {
29 var b strings.Builder
30 b.WriteString("# Soundex\n\n")
31 b.WriteString("Phonetic keys for English names, demoing the ")
32 b.WriteString("[`p/moul/x/daily/soundex`](/p/moul/x/daily/soundex/v0) library.\n\n")
33
34 if q := parseArg(path); q != "" {
35 k := soundex.Encode(q)
36 b.WriteString("## `")
37 b.WriteString(q)
38 b.WriteString("`\n\n")
39 if k == "" {
40 b.WriteString("_No ASCII letters — no key._\n")
41 } else {
42 b.WriteString("key: **")
43 b.WriteString(k)
44 b.WriteString("** (normalized: `")
45 b.WriteString(soundex.Normalize(q))
46 b.WriteString("`)\n")
47 }
48 return b.String()
49 }
50
51 b.WriteString("| name | key |\n|---|---|\n")
52 for _, n := range names {
53 b.WriteString("| ")
54 b.WriteString(n)
55 b.WriteString(" | `")
56 b.WriteString(soundex.Encode(n))
57 b.WriteString("` |\n")
58 }
59
60 // group by key, preserving first-seen order (never iterate a map)
61 b.WriteString("\n## Sounds alike\n\n")
62 keys := []string{}
63 byKey := map[string][]string{}
64 for _, n := range names {
65 k := soundex.Encode(n)
66 if _, seen := byKey[k]; !seen {
67 keys = append(keys, k)
68 }
69 byKey[k] = append(byKey[k], n)
70 }
71 for _, k := range keys {
72 if len(byKey[k]) < 2 {
73 continue
74 }
75 b.WriteString("- **")
76 b.WriteString(k)
77 b.WriteString("**: ")
78 b.WriteString(strings.Join(byKey[k], ", "))
79 b.WriteString("\n")
80 }
81
82 b.WriteString("\n> A *blocking* key for finding candidates, never proof that two names match.\n")
83 return b.String()
84}
85
86func parseArg(path string) string {
87 s := strings.TrimSpace(path)
88 s = strings.TrimPrefix(s, "/")
89 if i := strings.IndexByte(s, '/'); i >= 0 {
90 s = s[:i]
91 }
92 return s
93}