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