Skip to content

Quick answers

Every choice on this site, condensed into one page, for when you know what you need and just want the answer. Each section links to the page that explains it.

Which API shape?

You wantShapeExample calls
a result you can hold, index or pass oneagertokenize(), process(), keys_with_prefix()
to consume it once, in order — maybe not all of itlazytokens(), ngrams_iter(), iter_keys_with_prefix()
to do this millions of times with the same shape of outputinto-buffertokenize_into(), pluralize_into()
generic code over the traitbatchtokenize_batch() (sequential today)

The four API shapes

Which tokenization call?

Your situationCall
I name a concrete tokenizer type, and look at each token oncetokens()
I name a concrete type and want a Vec to keep, index or returntokenize()
I name a concrete type and am in a loop over many documentsbuf.clear(); tokenize_into(doc, &mut buf)
My function takes "some tokenizer" and needs owned Stringsverbora_core::Tokenizer
My function takes "some tokenizer" and only needs slicesverbora_core::BorrowingTokenizer (13 of 24 types)
My tokenizer is RegexpTokenizer / WordTokenizer / OrthographyTokenizer / WordPunctTokenizerthe inherent methods — they return Option, None meaning "no match at all"
I have a slice of documents and want one callTokenizer::tokenize_batch — a sequential map; rayon at your call site if the CPU cost justifies threads

Choosing a tokenization API

Which tokenizer?

What you are splittingTokenizer
English words, fast and simpleAggressiveTokenizer
Another languageAggressiveTokenizer{De,Es,Fr,It,Nl,No,Pl,Pt,Ru,Sv,Uk,Vi,Id,Fa,Hi}
Finnish, or another orthography-driven languageOrthographyTokenizer::new("fi")
SentencesSentenceTokenizerwith_abbreviations if you have a list
Words and punctuation as separate tokensWordPunctTokenizer
Penn Treebank conventions (contractions split)TreebankWordTokenizer
JapaneseTokenizerJa
Your own patternRegexpTokenizer::new(Pattern::new(re))

Tokenizers

Which distance metric?

What you are comparingMetricWorking set
Same length by construction (codes, hashes, fixed fields), plain numberhamming()-1 means incomparable
The same, with Rust's vocabulary for "no answer"hamming_checked()Option<u64>
Typos, and a swap is honestly two editslevenshtein()bit-vector / 1 row weighted
Typos, adjacent swaps cost 1, never edited againdamerau_levenshtein(.., restricted: true)bit-vector / 3 rows weighted
Typos, swaps may be arbitrarily far apartdamerau_levenshtein(.., restricted: false)2 rows + per-symbol snapshots
Position of the best approximate occurrence in a longer stringlevenshtein_search() / damerau_levenshtein_search()full matrix
Names or short records, raw scorejaro()
Names or short records, prefix-boosted (the usual choice)jaro_winkler()
Shared content, not order or positiondice_coefficient() — bigram set overlap; NaN on two empties

Choosing a distance API

Which phonetic encoder?

What you needEncoderKey
English surnames, cheapest possible blocking keySoundEx4 characters, very coarse
General English words, one key, better precisionMetaphoneup to 32 characters
English text with names of many origins, two indexable keysDoubleMetaphonetwo keys; match on either
Slavic / Germanic / Ashkenazi-Jewish surnamesSoundExDM6 digits, multi-letter clusters

Phonetics

Which n-gram call?

If you…Call
stop early, or fold windows into a counterngrams_iter()
consume everything and want indexable windows — the defaultngrams() (or bigrams() / trigrams())
need the tuples to outlive the token slicengrams_owned()
need the {ngrams, frequencies, Nr, numberOfNgrams} shapengrams_with_stats()
need counts only, with your own key formatngrams_iter() folded into a HashMap
need many lookups by keyngrams_with_stats(), then index frequencies into a HashMap once
have a string and control the tokenizerngrams_str_with()
have Chinese BMP textzh::ngrams_zh()
have Chinese text that may contain astral characterszh::code_units() + zh::ngrams_zh_utf16()

Choosing an n-gram API

Which trie query?

Your questionCall
"Is this exact string stored?"contains()
"How big is the structure?"get_size() — nodes, not words; O(1)
"Which stored words start with my string?" — all of them, indexablekeys_with_prefix()Vec<String>
The same, but only the first N, or I stop on a conditioniter_keys_with_prefix().take(N)
"Does anything start with this?"iter_keys_with_prefix().next().is_some()
"Give me every word in the trie"keys() — lazy; same as iter_keys_with_prefix("")
"Which stored words are prefixes of my string?" — all, shortest firstfind_matches_on_path()Vec<Cow<str>>
The same, but only the shortest or the first fewiter_matches_on_path().next() / .take(n)
Only the longest stored prefixfind_prefix().0 — one walk, no iterator
"Where does the longest stored prefix end?" — as textfind_prefix()(Option<Cow>, Cow)
The same, but as offsets, exact and allocation-freefind_prefix_lengths()(Option<usize>, usize)

Trie

Which normalizer?

What you are normalizingCall
English contractions, over a token slice (the usual case)normalize(&tokens)
English contractions, exactly one tokennormalize_token(&token)
Latin diacritics, any language, fold everything the table knowsremove_diacritics()
Norwegian — keep ä ö ü å ø ænormalize_no()
Swedish — keep those, plus â ç ê î ñ ó ô û šnormalize_sv()
Japanese, the whole normalizationnormalize_ja()
Japanese, one width/kana conversionja::converters::{alphabet_fh, katakana_hf, …}
Japanese, hiragana and katakana onto one syllabaryja::converters::{hiragana_to_katakana, katakana_to_hiragana}

Normalizers

Should I optimise this?

Ask in this order:

  1. Has a profiler told me this line is hot? No → use the high-level API and stop reading.
  2. Is the cost the container allocation, or the work inside it? The work → a different API will not help; look at the algorithm, the input size, or how many candidates you are comparing.
  3. Do I consume the result once, in order? Yes → tokens(), no container at all. No → tokenize_into(), one container reused.

Still not fast enough, and measured in seconds of CPU? Check whether the operation already has a par_*_batch (thirteen crates ship one, opt-in behind a parallel feature); if not, chunk the input and parallelise at your own call site.

Ergonomics vs throughput · Parallelism

Which workload am I in?

How work arrivesWorkloadWhat you optimise
One input, answer nowInteractivelatency, ergonomics
More input than memory, or output needed before input endsStreamingbounded memory, laziness, early output
Many documents, offlineBatchmemory reuse, shared setup
Many documents, and seconds of CPU to spendParallel corpuschunking, per-worker state

Recipes by workload

Released under the MIT License.