-
Notifications
You must be signed in to change notification settings - Fork 0
Fuzzy matching
Two strings that are nearly the same, and the question of how nearly.
Lodestar.Fuzzy reproduces rapidfuzz: fuzz.* scorers in [0, 100], process.extract over a
list of candidates, and blocking deduplication over a whole dataset.
flowchart TD
A["What are you comparing?"] --> B{"Two strings of<br/>similar length?"}
B -->|yes| C["Fuzz.Ratio"]
B -->|"no — one is much longer,<br/>and may contain the other"| D["Fuzz.PartialRatio"]
A --> E{"Are the words the same<br/>but the order different?"}
E -->|yes| F["Fuzz.TokenSortRatio"]
E -->|"and one side has extra words"| G["Fuzz.TokenSetRatio"]
A --> H["Don't know, or mixed input"] --> I["Fuzz.WRatio"]
Fuzz.Ratio is the base: an edit-distance similarity over the whole of
both strings. Everything else is that scorer applied to something other than the raw pair.
Length is what breaks Ratio. Comparing apple to an apple a day scores poorly, not
because they disagree but because most of the second string is absent from the first.
PartialRatio scores the best-matching window instead, and
answers 100 there.
Word order is what breaks both. new york mets and mets new york are the same words,
and TokenSortRatio sorts the words before comparing, giving
100. TokenSetRatio goes further and compares the sets, so
extra words on one side stop counting against it.
WRatio picks among these by inspecting the input, which is what to
use when the input is not known in advance.
Process.Extract ranks a list of candidates and
Process.ExtractOne returns the best, or nothing when
none clears the cutoff — a null that is the honest answer to "which of these is it" when the
answer is "none of them".
Both hand back ExtractResult, which carries the index as well as
the score, so the match can be traced back to the row it came from.
Deduplicator.FindClusters groups records that are
near-duplicates of each other. Comparing every pair is quadratic and unusable past a few thousand
rows, so it blocks first: records sharing a key are compared, records that do not are never
considered. Choosing that key is the whole performance question, and it is the caller's.
| Type | What it is |
|---|---|
Deduplicator |
Near-duplicate clustering over a dataset, with blocking. |
ExtractResult |
One candidate's choice, score and index. |
Fuzz |
The seven scorers, all in [0, 100]. |
Process |
One query against many candidates. |
- Migrating from rapidfuzz — the guide, call by call.
- Python → C# equivalence.
- 0001-target-framework
- 0002-unicode-comparison-unit
- 0003-provenance-and-licensing
- 0004-levenshtein-myers-backlog
- 0005-hamming-jellyfish-divergence
- 0006-ratcliff-autojunk
- 0007-metaphone-scope
- 0008-italian-enza-nltk-divergence
- 0009-sample-consumes-a-local-feed
- 0010-stop-word-list-provenance
- 0011-persistence-format
- 0012-per-package-versioning
- 0013-sentencepiece-parity-scope
- 0014-precompiled-normalizer
- 0015-sonar-rules-in-the-build
- 0016-metrics-package-placement
- 0017-bpe-parity-scope
- 0018-multiclass-roc-auc-parallelism-is-opt-in
- 0019-the-net-analysers-run-in-the-build-too
- 0020-normalize-is-a-projection-not-a-parameter
- 0021-multioutput-is-a-method-not-an-enum
- 0022-added-token-matching-flags
- 0023-byte-level-decode-substitutes
- 0024-weighted-median-averages-within-scikit-learns-epsilon
- 0025-quickselect-replaces-a-full-sort-for-the-median
- 0026-r2-and-explainedvariance-split-their-undefined-cases-differently
- 0027-r2-and-explainedvariance-vectorize-only-a-single-output
- 0028-log1p-is-kahans-identity-not-math-log-1-plus-x
- 0029-balanced-accuracy-adjusted-is-left-to-ieee-754-at-the-edge
- 0030-cohen-kappa-keeps-scikit-learns-expected-matrix-orientation
- 0031-nosamplecorrect-mirrors-numpys-float64-upcast
- 0032-fbeta-substitutes-tp-predicted-and-support-algebraically
- 0033-compensated-sum-is-neumaiers-variant
- 0034-dropout-is-refused-for-want-of-a-user
- 0035-a-null-pre-split-is-removed-with-invert-not-isolated
- 0036-a-member-may-ship-without-an-oracle-if-it-says-so
- 0037-the-guards-run-before-the-commit
- 0038-the-gate-confronts-an-exception-tag-with-the-page-that-documents-it
- 0039-mutual-information-returns-zero-on-an-empty-input
- 0040-a-curve-is-a-sealed-class-per-curve
- 0041-one-sample-file-per-public-class
- 0042-phonetic-encoders-refuse-a-null-word
- 0043-the-equality-table-is-sized-to-the-pattern
- 0044-compression-belongs-to-the-caller
- 0045-a-console-call-carries-its-reason-on-the-line
- 0046-check-adr-immutable-runs-in-ci-only
- 0047-one-gate-per-kernel-not-one-per-alphabet
- 0048-the-gate-depends-on-the-kernel-and-the-alphabet
- 0049-two-gates-per-kernel-tested-where-the-width-is-known
- 0050-the-sentencepiece-bpe-lineage-stays-a-bpe-model
- benchmark_latest
- decisions
- equivalence
- matplotlib
- migration
- nightly_run
- numpy
- pandas
- performance
- pytorch
- seaborn
- sklearn
- statsmodels