nx_didyoumean.nx
buildroot/runtime/nx_didyoumean.nx
about
nx_didyoumean.nx -- SEARCH RUNG F4: THE DID-YOU-MEAN LINE, BUILT FROM THE INDEX'S OWN TERM DICTIONARY (2026-09-18).
WHAT IT DECIDES. For each eligible word of a query, one question: is the word RARE in the index while a one-edit
neighbour of it is COMMON? When it is, the commonest such neighbour is offered in the word's place ("Did you mean
...?"), every other byte of the query kept. It ranks, filters and rewrites nothing: the typed query is still the one
searched, and the line only offers the alternative.
THE CANDIDATES ARE GENERATED, NOT SEARCHED FOR. The seg's zero-hit corrector (dss_correct_raw) walks the sorted run of
dictionary terms sharing the word's first byte and runs a bounded Levenshtein per entry -- the right shape for a
two-edit repair, and the reason it only runs when a query finds nothing: that run is a large slice of a multi-segment
dictionary. This lib asks the converse question. It writes out every string exactly ONE Damerau edit from the word --
deletion, adjacent transposition, substitution, insertion over the tokenizer's letter class -- and asks the dictionary
for each one's document frequency by EXACT lookup (ss_term_dcount, the sampled-window path the ranker's idf already
reads). The work is a function of the word's LENGTH and never of the dictionary's size, which is what lets it run on
queries that DO find results.
EVERY BOUND IS DERIVED FROM THE RESOURCE IT DESCRIBES:
- THE EDIT BOUND comes from the word's length: an edit is admitted only while it leaves a strict majority of the typed
characters untouched (2 x edits < length). A two-letter word is never corrected; a word of three or more letters may
take the one edit this lib generates. Two-edit repairs stay where they already live, in the zero-result fallback
(dss_correct), unchanged.
- RARE and COMMON come from the index's own document-frequency scale, the one the ranker weighs terms by (nx_intlog
idf_q10). The widest idf the index can give any term is idf(N, 1). A word is RARE when its idf exceeds half that
span; a candidate is COMMON when its idf does not. That is the midpoint of the log-df scale -- df below, or at or
above, sqrt(N + 1) -- found without a square root and drawn from the LIVE document count N, so it moves with the
corpus. dym_mid names the value by bisecting the predicate itself, so the announced number and the decision agree.
- THE FIRST BYTE IS KEPT: the zero-hit corrector's documented choice, kept for the same determinism and because an
edit at position 0 is where a short rare word most often lands on an unrelated common one.
- THE WORK IS BOUNDED BY THE QUERY: at most `maxwords` words (the caller passes the ranker's own term cap), each
costing at most dym_lookup_bound(length) exact lookups, every one of them counted in the stats the caller publishes.
DOCUMENTED IMPRECISION -- the price of document frequency alone, with no query log and no context:
- a rare real word one edit from a far commoner real word draws a suggestion. The typed word's results are still
served, so this error costs one line of text and never a result.
- a misspelling that is itself common (df at or above the midpoint) is taken at its word.
- a slip in the FIRST letter is out of scope (uantum is not offered quantum), as it is for the incumbent corrector.
license_tier: ORIGINAL No hw writes (Rule 26). Read-only on the index.
dependencies 2 imports · 1 importers
imports: nx_seg_store.nxnx_intlog.nx
imported by: nx_docportal_search_seg.nx
structs
| none |
consts
| 40 | const DYM_LOW_A: i64 = 97 // 'a' -- the edit alphabet is the tokenizer's lowercase letter class, a..z |
| 41 | const DYM_LOW_Z: i64 = 122 // 'z' |
| 42 | const DYM_UP_A: i64 = 65 // 'A' |
| 43 | const DYM_UP_Z: i64 = 90 // 'Z' |
| 44 | const DYM_CASE: i64 = 32 // 'a' - 'A' |
| 45 | const DYM_DIG_0: i64 = 48 // '0' |
| 46 | const DYM_DIG_9: i64 = 57 // '9' |
| 47 | const DYM_HIGH: i64 = 128 // a byte of a multi-byte UTF-8 sequence: a word holding one is left as typed |
| 48 | const DYM_COLON: i64 = 58 // site:host, inurl:frag -- an operator name, or the value bound to it |
| 49 | const DYM_DOT: i64 = 46 // host labels (reddit.com) |
| 50 | const DYM_SLASH: i64 = 47 // paths and the r/<name> idiom |
| 51 | const DYM_EDIT: i64 = 1 // the neighbourhood this lib generates is exactly one edit wide |
| 52 | const DYM_KEEP: i64 = 1 // edits start at byte 1: the first byte of the word is kept |
| 54 | const DYM_ST_DOCS: i64 = 0 // N, the live document count the scale is drawn from |
| 55 | const DYM_ST_MID: i64 = 1 // the smallest COMMON df on that scale (dym_mid) |
| 56 | const DYM_ST_WORDS: i64 = 2 // eligible words examined |
| 57 | const DYM_ST_RARE: i64 = 3 // of them, rare |
| 58 | const DYM_ST_LOOKUPS: i64 = 4 // one-edit candidates looked up |
| 59 | const DYM_ST_HELD: i64 = 5 // of them, held by the dictionary (df > 0) |
| 60 | const DYM_ST_FIXED: i64 = 6 // words replaced in the suggestion |
| 61 | const DYM_ST_US: i64 = 7 // microseconds this answer cost |
| 62 | const DYM_ST_TDF: i64 = 8 // df of the last rare word examined |
| 63 | const DYM_ST_CDF: i64 = 9 // df of its commonest neighbour (0 = the dictionary holds none) |
| 64 | const DYM_ST_ABSTAIN: i64 = 10 // 1 = the dictionary could not be read or the answer did not fit: nothing is offered |
| 65 | const DYM_ST_MEMO: i64 = 11 // set by a caller that answered from a memo; this lib always writes 0 |
| 66 | const DYM_ST_SLOTS: i64 = 12 // the vector's size: the slots above |
functions
| 68 | func dym_is_letter(c: i64) -> i64 |
| 73 | func dym_is_wordbyte(c: i64) -> i64 |
| 80 | func dym_is_joiner(c: i64) -> i64 called by 1: dym_suggest |
| 86 | func dym_lower(c: i64) -> i64 |
| 91 | func dym_letters() -> i64 { return DYM_LOW_Z - DYM_LOW_A + 1 } called by 1: dym_lookup_bound |
| 94 | func dym_edits_allowed(n: i64) -> i64 |
| 100 | func dym_lookup_bound(n: i64) -> i64 |
| 108 | func dym_rare(bign: i64, df: i64) -> i64 |
| 114 | func dym_common(bign: i64, df: i64) -> i64 |
| 121 | func dym_mid(bign: i64) -> i64 |
| 133 | func dym_consider(h: *i64, cand: *u8, cl: i64, bestdf: i64, best: *u8, st: *i64) -> i64 |
| 149 | func dym_best_edit1(h: *i64, t: *u8, n: i64, cand: *u8, best: *u8, st: *i64) -> i64 |
| 208 | func dym_emit(out: *u8, o: i64, cap: i64, src: *u8, n: i64) -> i64 |
| 219 | func dym_suggest(h: *i64, q: *u8, qn: i64, maxwords: i64, out: *u8, outcap: i64, st: *i64) -> i64 called by 2: maindss_spell_suggest calls 12: ss_doc_countdym_middym_is_wordbytedym_emitdym_is_letterdym_edits_allowed+6 |