You're viewing the readable version of this site. The interactive extras (search, diagrams, read-aloud) need JavaScript and a current browser. Enable JavaScript; if it is already enabled, update your browser.

Learn · The Art of Not Looking

budding

Two Machines That Must Agree

Search cannot retrieve a word that indexing and querying define differently. Analysis is a versioned contract, not harmless cleanup.

search, information-retrieval, unicode, tokenization, normalization, stemming, provenance, learn

When I use a word, it means just what I choose it to mean—neither more nor less.

— Humpty Dumpty, in Lewis Carroll’s “Through the Looking-Glass

This is the third chapter in a book about search from first principles. The previous chapter opened the dictionary hidden inside an inverted index. Here you will derive the function that creates its keys. You will distinguish extraction from normalization, segmentation from stemming, and key equality from display order; prove why some transformations cannot be reversed; build a complete reference analyzer; and preserve the source evidence behind every token. The next chapter will exploit the sorted postings those keys select.

Two clerks, one filing cabinet

Imagine two clerks sharing a cabinet. The first files a card under café. The second receives a request that looks identical on the page, walks to the café drawer, and finds nothing.

The first card used the single code point é. The request used e followed by a combining acute accent. Human perception supplied equality that the cabinet never encoded.

A search engine has these two clerks:

  • the index analyzer turns source records into dictionary keys and postings;
  • the query analyzer turns a request into keys to look up.

Their governing law is agreement. For one pinned analyzer contract AA and one input text xx,

Aindex(x)=Aquery(x). A_{\mathrm{index}}(x) = A_{\mathrm{query}}(x).

The equality covers the ordered token keys and all options that affect them. The two sides may receive different kinds of records—an authored heading at index time and a query box at request time—but the shared text function cannot quietly drift.

Analyzer — the deterministic, versioned function that converts source text into searchable token records. It includes more than splitting on spaces. Learn more.

Prediction — commit to what must happen when the visible text agrees but its code points differ.

Name the whole function

Writing lowercase(split(text)) hides every consequential choice. A useful analyzer accepts source records, not an unmarked string, and emits token records, not bare words.

Let a source record be

r=(text,field,origin,span,confidence). r = (\text{text},\text{field},\text{origin},\text{span},\text{confidence}).

Let an emitted token be

t=(key,field,origin,span,position,confidence,analyzer-id). t = (\text{key},\text{field},\text{origin},\text{span},\text{position}, \text{confidence},\text{analyzer-id}).

The key supports equality and lookup. The remaining coordinates preserve why that key exists. A title token and an inferred caption token may spell the same word and still be different evidence.

A contract must pin at least:

  1. accepted input encoding and malformed-input behavior;
  2. source extraction and field rules;
  3. Unicode normalization form;
  4. segmentation rules and any tailoring;
  5. case mapping or folding rule;
  6. language-specific stemming or lemmatization policy;
  7. retained positions and source spans;
  8. stopword and punctuation policy;
  9. deterministic output order;
  10. an analyzer identifier that changes when any prior item changes.

The identifier is not decoration. The index artifact records it. A query engine that cannot supply the same contract rejects the artifact or selects a compatible analyzer explicitly. Silent fallback converts a detectable version mismatch into unexplained misses.

The stages do different jobs

The pipeline is easier to reason about when each stage has one meaning.

Extraction decides what was said where

Extraction chooses authored fields from a source format: title, heading, body, tag, transcript, alternative text, or another declared region. It also assigns source spans and provenance.

This step precedes text cleanup because structure carries meaning. Flattening a document first can accidentally index navigation, hidden labels, generated controls, or text from the wrong field. The resulting tokens are well formed but falsely attributed. Those errors are more dangerous than a miss because they can receive plausible field boosts later.

Normalization chooses an equivalence relation

Unicode normalization maps code-point sequences into a chosen normal form. NFC composes canonical sequences where possible. NFD decomposes them. NFKC and NFKD additionally apply compatibility mappings, which can erase distinctions such as presentation forms.

The standard defines these forms precisely; “remove accents” and “make ASCII” are separate, stronger policies cite:UAX15.

Normalization is generally non-injective. If distinct inputs xx and yy map to one key,

xyandN(x)=N(y), x \ne y \quad\text{and}\quad N(x)=N(y),

then no inverse can recover which source was supplied. The index must retain the original text or span when display fidelity matters. A normalized key is an access path, not a replacement for the source.

Non-injective — two or more distinct inputs can produce the same output. Once that merge occurs, the output alone cannot identify the original input. Learn more.

Segmentation decides what counts as a boundary

Splitting on the ASCII space recognizes only one separator and mishandles many scripts, combining marks, punctuation patterns, and words with internal marks. Unicode Standard Annex #29 supplies default grapheme, word, and sentence boundary algorithms plus explicit tailoring points cite:UAX29.

Default is not universal intent. Source-code search may split parseHTTPHeader at case transitions. A natural-language index may preserve an apostrophe. A catalog may treat a hyphenated part number as one key and two fallback keys. Each is a defensible contract if both machines share it and tests pin the edge cases.

Case folding chooses caseless matching

Lowercasing is a display-oriented case mapping. Case folding is designed for caseless comparison, and its full mapping may change length. Locale-sensitive behavior adds another policy choice. The analyzer must state whether it uses a default Unicode fold, a locale-tailored mapping, or no folding at all.

Case folding and normalization also have a declared order. Function composition is not automatically commutative:

there exist F,N,x with F(N(x))N(F(x)). \text{there exist }F,N,x\text{ with } F(N(x)) \ne N(F(x)).

The contract names the composition it implements and pins examples from the scripts it claims to support.

Stemming chooses a retrieval equivalence

A stemmer deliberately merges surface forms so a request for one grammatical variant can retrieve another. In 1980, Martin Porter described a suffix-stripping procedure organized as ordered condition-and-replacement rules cite:PORTER. It is a language-specific retrieval algorithm, not generic Unicode cleanup.

Stemming can merge terms a reader considers different and can fail to merge terms that share a meaning. The original token therefore remains available for display and exact modes. An analyzer applies a stemmer only under a declared language policy; guessing a language per token makes reproducibility harder and can make neighboring words follow incompatible rules.

A complete reference analyzer

The following algorithm separates structural extraction from the shared key function. Its helper operations are not implementation defaults: each is named by the contract and covered by fixtures.

Analyze ordered source records under a pinned contract

ANALYZE(records, contract)
Input:  ordered source records carrying text, field, origin, span, confidence
        a contract naming decode, normalize, segment, fold, stem, and filters
Output: ordered token records, or a typed analysis error

if contract identifier is unknown
    return UNKNOWN-CONTRACT
tokens  empty sequence
position  0
for each record r in records
    decoded  contract.DECODE(r.text)
    if decoded is an error
        return decoded
    normalized  contract.NORMALIZE(decoded)
    pieces  contract.SEGMENT(normalized)
    for each piece p in pieces from left to right
        if not contract.ACCEPTS(p)
            continue
        folded  contract.FOLD(p.text)
        renormalized  contract.NORMALIZE(folded)
        key  contract.STEM(renormalized, contract.language)
        if key is empty
            continue
        source-span  MAP-SPAN(r.span, p.span)
        token  TOKEN(key, r.field, r.origin, source-span, position, r.confidence, contract.identifier)
        tokens.APPEND(token)
        position  position + 1
return tokens

The second normalization is explicit because the selected fold or tailoring may emit a sequence outside the chosen normal form. A contract backed by a single standardized combined operation can implement the same interface with one call; its observable token stream, not its number of internal passes, is the promise.

The algorithm stops on malformed input instead of silently substituting a key. Another contract could replace malformed subsequences, but then the replacement rule becomes part of key identity. Either policy is reproducible. An implicit runtime default is not.

The loop is linear in the number of decoded code points plus the work of its declared transforms. That asymptotic statement is intentionally modest. Dictionary-driven segmentation, language analysis, and span mapping can have large constants. The artifact should measure their bytes, allocations, and latency on the scripts and fields it actually admits.

Agreement needs a witness

An analyzer identifier is useful only if it identifies behavior. A robust artifact includes a compact conformance corpus with rows such as:

Input class Paired witness Observation to pin
canonical equivalents composed and decomposed café equal key sequence
compatibility forms full-width and ordinary forms equal or distinct, by declared form
case expansion a fold whose length changes exact emitted key and span
combining marks mark following a base no false word boundary
apostrophes and hyphens language and identifier examples declared segmentation
malformed encoding truncated or invalid sequence typed error or declared replacement
empty and punctuation-only text no accepted token empty output
field collision same spelling in title and inferred caption equal key, distinct provenance
source-code identifier parseHTTPHeader declared whole and/or subtoken order
stemming collision two forms reduced to one key originals remain distinguishable for display

Run the same rows through the index path and query path, then compare canonical token bytes. Testing each implementation only against itself is too weak: two different functions can each be internally deterministic.

A migration needs three analyzer versions at once:

  • the version recorded by the old index;
  • the version used to build the candidate index;
  • the version selected for each query during comparison.

Shadowing the candidate on a pinned query corpus exposes gains and losses before activation. Rebuilding the index is usually the honest migration. A query-time compatibility trick that emits keys for both versions can bridge a small transition, but it broadens retrieval, complicates scoring, and needs an explicit removal condition.

Discrimination — decide whether matching keys are enough to claim matching evidence.

Equality is not ordering

Normalization and case folding help define key equality. They do not define a human-language sort order. Collation can depend on locale, punctuation policy, numeric substrings, and strength. A search artifact therefore separates:

  • canonical key bytes for deterministic identity and reproducible lookup;
  • semantic enumeration order promised by the interface;
  • display collation chosen for a particular reader and locale.

Using locale-sensitive collation as persistent identity is dangerous because implementations and locale data can change. Using canonical byte order as a claim about how names should appear to every reader is equally dishonest. The dictionary chapter's multiple-view lesson applies again: one identity can have separate, explicit orders.

Provenance survives every transformation

Each analyzer stage changes a view of the text. None may launder its origin. If OCR proposes a token from pixels, normalization does not turn that token into authored prose. If speech recognition attaches a time interval and confidence, stemming does not erase either. If a heading extractor points to a source span, a later snippet generator can return to that span rather than inventing context around a normalized key.

Span mapping becomes difficult when a transform changes length. The safe model is not to pretend every output code point has one input offset. Preserve a covering source interval for the token, and retain the original source for display. More precise many-to-many maps are useful when highlighting demands them, but they are additional data with their own bounds and tests.

Eligibility can depend on provenance before ranking begins. A system may admit authored metadata for one query mode and exclude inferred captions below a confidence threshold. Encoding the origin as a tiny relevance bonus would let ineligible evidence participate and perhaps leak. A later chapter will derive that boundary as a set operation.

The historical lesson is reproducibility

Text analysis matured through distinct problems, not one universal cleanup recipe. The Unicode Consortium specified normalization forms so equivalent character sequences could be compared under a stable standard cite:UAX15. Its boundary annex specified default segmentation rules and how implementations may tailor them cite:UAX29. Martin Porter published an English suffix-stripping algorithm in 1980 and later maintained precise reference material for its lineage and variants cite:PORTER. These works solve different layers. Treating any one of them as “tokenization” hides the choices the others make explicit.

Where agreement is not enough

Two machines can agree perfectly on a bad function.

  • Compatibility normalization can collapse a distinction the domain needs.
  • An English stemmer can damage names or text in another language.
  • A segmentation rule tuned for prose can make source-code identifiers hard to find.
  • A field extractor can consistently index boilerplate or private text.
  • A stable analyzer can preserve a discriminatory or historically accidental naming convention.

Agreement is a necessary reproducibility law, not proof of relevance, fairness, authorization, or linguistic truth. Those properties need separate evidence.

The analyzer can also be more expensive than the retrieval it enables. For a tiny static corpus, an exact substring scan may be simpler and more faithful. For a field of opaque identifiers, normalization and stemming may be actively wrong. The interface should permit the identity analyzer when identity is the right relation.

Lessons

  • Indexing and querying are two machines that must share one versioned analyzer contract.
  • Extraction, normalization, segmentation, case folding, stemming, and collation answer different questions.
  • Normalization and stemming can be non-injective, so normalized keys cannot replace original source text.
  • Equal keys establish a lookup relation; they do not erase field, origin, source span, or confidence.
  • Analyzer agreement is proven with shared conformance rows and canonical token bytes, not matching implementation names.
  • Changing any key-affecting rule changes the artifact contract and normally requires rebuilding the index.
  • A shared analyzer can still be wrong for the domain. Reproducibility is necessary, not sufficient.

Practice

Retrieval — reconstruct the analyzer contract from its observations.
Transfer — apply the agreement law to a system outside document search.

Agreement gives us sorted evidence

The two machines now produce the same key for the same declared relation. A lookup can therefore return its postings without wondering whether the miss was created by preprocessing drift.

But a multi-term query still faces several sorted lists. Must it inspect every document identifier in all of them? The next chapter puts one finger on each list and derives exactly when whole regions can be skipped.

References

  1. Carroll. “Through the Looking-Glass.” Project Gutenberg, 1871. — primary text for the epigraph in the Humpty Dumpty dialogue
  2. Unicode Consortium. “Unicode Standard Annex #15: Unicode Normalization Forms.” Unicode Consortium, revision 57. — normative definitions, stability policy, and conformance for normalization
  3. Unicode Consortium. “Unicode Standard Annex #29: Unicode Text Segmentation.” Unicode Consortium, revision 47. — normative default grapheme, word, and sentence boundary algorithms and tailoring
  4. Porter. “An algorithm for suffix stripping.” Program, 1980. — original published English suffix-stripping algorithm
  5. Porter. “The Porter Stemming Algorithm.” Martin Porter, author-maintained page. — author-maintained history, reference implementations, and algorithm variants
  6. Manning, Raghavan, Schütze. “Tokenization.” Introduction to Information Retrieval, 2008. — retrieval-oriented treatment of tokenization decisions