Skip to content

String Comparators

Every matching rule in Kanoniv compares two field values and returns a similarity score between 0.0 (no match) and 1.0 (perfect match). This page explains each algorithm, when to use it, and how to configure it.

Decision Framework

Start here. Pick the algorithm based on your data type:

Data TypeRecommendedWhyExample
Email, SSN, phoneexactIdentifiers should match exactly after normalization[email protected] = [email protected]
Person namesjaro_winklerWeights prefix matches, handles typos"Jon Smith" ≈ "John Smith"
Addresses, free textlevenshteinCounts insertions/deletions/substitutions"123 Main St" ≈ "123 Main Street"
Names with spelling variantssoundexFast phonetic encoding"Smith" ≈ "Smyth"
International namesmetaphoneBetter phonetic rules for non-English"Schmidt" ≈ "Smith"
Company names, descriptionscosineToken-overlap handles word reordering"Acme Inc" ≈ "Inc Acme Corp"
Lat/long coordinateshaversineGreat-circle distance on Earth's surface40.7128,-74.0060 ≈ 40.7130,-74.0058
Numeric ranges (age, revenue)exact with toleranceExact match with configurable tolerance32 ≈ 33 (within range)

Exact

How It Works

Binary comparison: returns 1.0 if two values are identical after normalization, 0.0 otherwise. No partial credit.

This is the fastest comparator and should be your first choice for structured identifiers — email addresses, phone numbers, tax IDs, and any field where a partial match is meaningless.

Example

Value AValue BScore
[email protected][email protected]1.0
[email protected][email protected]0.0
555-0100555-01001.0
555-0100555-01010.0

YAML Config

yaml
rules:
  - name: email_exact
    type: exact
    field: email
    weight: 1.0

Best for: Structured identifiers (email, phone, SSN, tax ID) where partial matches are meaningless.

Jaro-Winkler

How It Works

Jaro-Winkler is a two-step algorithm:

  1. Jaro distance counts the number of matching characters (characters that appear in both strings within a window) and transpositions (matching characters in different order). The formula produces a score between 0 and 1.

  2. Winkler modification boosts the score when the first 1-4 characters match exactly. The idea: if two strings share a prefix, they're more likely to be the same name with a typo in the middle.

This prefix bonus makes Jaro-Winkler ideal for person names, where the first few characters are almost always correct ("Jon" vs "John", "Catherine" vs "Katherine").

Scoring Behavior

  • Range: 0.0 to 1.0 (continuous)
  • Prefix bonus: up to +0.1 for matching prefixes
  • Typical match threshold: 0.85 – 0.92

Example

CRM NameStripe NameScore
John SmithJon Smith0.96
John SmithJohn Smyth0.93
Catherine LeeKatherine Lee0.88
John SmithJane Doe0.40
Acme CorporationAcme Corp0.91

YAML Config

yaml
rules:
  - name: name_fuzzy
    type: similarity
    field: name
    algorithm: jaro_winkler
    threshold: 0.88
    weight: 0.6

Best for: Person names, short strings where prefix accuracy matters.

Levenshtein

How It Works

Levenshtein distance counts the minimum number of single-character edits (insertions, deletions, substitutions) needed to transform one string into another. Kanoniv normalizes this to a similarity score:

score = 1 - (edit_distance / max(len(a), len(b)))

Unlike Jaro-Winkler, Levenshtein treats all positions equally — there's no prefix bonus. This makes it better for addresses and free-text fields where errors can occur anywhere in the string.

Scoring Behavior

  • Range: 0.0 to 1.0 (continuous)
  • Each edit (insert, delete, substitute) reduces the score proportionally
  • Typical match threshold: 0.80 – 0.90

Example

CRM AddressBilling AddressScore
123 Main Street123 Main St0.81
123 Main Street123 Main Street1.00
456 Oak Avenue456 Oak Ave0.85
123 Main Street789 Elm Road0.25
San FranciscoSan Fransisco0.93

YAML Config

yaml
rules:
  - name: address_fuzzy
    type: similarity
    field: street
    algorithm: levenshtein
    threshold: 0.80
    weight: 0.4

Best for: Addresses, company names, any field where edits can occur anywhere in the string.

Soundex

How It Works

Soundex encodes a string into a four-character code based on how it sounds in English: the first letter is kept, and subsequent consonants are mapped to digits (B/F/P/V → 1, C/G/J/K/Q/S/X/Z → 2, etc.). Vowels and repeated codes are dropped.

Two strings match if their Soundex codes are identical. This makes Soundex a binary comparator — it returns 1.0 (same code) or 0.0 (different code), with no partial scores.

Soundex is fast and useful for catching phonetic variations, but it's coarse: "Smith" and "Smyth" both encode to S530, but so might unrelated names. Use it as a supplementary rule, not your primary comparator.

Scoring Behavior

  • Range: binary (1.0 or 0.0)
  • No partial scoring — codes either match or they don't
  • Threshold: not applicable (always use 1.0)

Example

Value AValue BSoundex ASoundex BScore
SmithSmythS530S5301.0
RobertRupertR163R1631.0
JohnJonJ500J5001.0
SmithJonesS530J5200.0
CatherineKatherineC365K3650.0

YAML Config

yaml
rules:
  - name: name_phonetic
    type: similarity
    field: name
    algorithm: soundex
    threshold: 1.0
    weight: 0.3

TIP

Soundex misses Catherine / Katherine because the first letter differs. Use metaphone for better cross-initial phonetic matching.

Best for: Supplementary name matching in English-language datasets. Pair with jaro_winkler for best results.

Metaphone

How It Works

Metaphone (specifically Double Metaphone) generates one or two phonetic codes per string using rules that better model English pronunciation than Soundex. It handles silent letters, consonant clusters, and common spelling variations.

Key advantages over Soundex:

  • Handles "ph" → "f", "ck" → "k", "wr" → "r" and similar patterns
  • Generates two codes (primary and alternate) to handle words with multiple pronunciations
  • Better accuracy for non-Anglo names

Like Soundex, it's a binary comparator: 1.0 if any code matches, 0.0 otherwise.

Scoring Behavior

  • Range: binary (1.0 or 0.0)
  • Matches if primary or alternate code of A equals primary or alternate code of B
  • Threshold: not applicable (always use 1.0)

Example

Value AValue BScoreWhy
CatherineKatherine1.0Both produce code K0RN
SchmidtSmith1.0Both produce code XMT/SMT
WrightRight1.0"Wr" → "R"
StephenSteven1.0"ph" → "f" → same code
JohnJane0.0Different phonetic codes

YAML Config

yaml
rules:
  - name: name_phonetic
    type: similarity
    field: name
    algorithm: metaphone
    threshold: 1.0
    weight: 0.3

Best for: Name matching with international spelling variants, datasets with mixed-origin names.

Cosine

How It Works

Cosine similarity treats each string as a bag of tokens (words), builds a vector of token frequencies, and computes the cosine of the angle between the two vectors. The result measures token overlap regardless of word order.

This makes cosine ideal for company names and descriptions where:

  • Words may appear in different order: "Acme Inc" vs "Inc Acme"
  • Extra words may be added: "Acme" vs "Acme Corporation"
  • Abbreviations change token count: "International Business Machines" vs "IBM" (low score — use exact for abbreviations)

Scoring Behavior

  • Range: 0.0 to 1.0 (continuous)
  • Order-independent — "A B C" and "C B A" score 1.0
  • Sensitive to token overlap, not character-level edits
  • Typical match threshold: 0.70 – 0.85

Example

CRM CompanyStripe CompanyScore
Acme IncAcme Incorporated0.71
Acme IncInc Acme1.00
Global Tech SolutionsGlobal Tech Solutions Inc0.87
Acme IncBeta Corp0.00
First National BankNational First Bank1.00

YAML Config

yaml
rules:
  - name: company_match
    type: similarity
    field: company_name
    algorithm: cosine
    threshold: 0.75
    weight: 0.5

Best for: Company names, product descriptions, any multi-word field where word order varies.

Haversine

How It Works

Haversine computes the great-circle distance between two points on Earth's surface given their latitude and longitude. Kanoniv converts this to a similarity score based on a configurable maximum distance:

distance = haversine(lat1, lon1, lat2, lon2)  // in km
score = max(0, 1 - distance / max_distance)

Points within max_distance km get a score > 0; points farther apart score 0. Points at the same location score 1.0.

Scoring Behavior

  • Range: 0.0 to 1.0 (continuous, linear decay)
  • 0.0 when distance >= max_distance
  • 1.0 when distance = 0

Example

With max_distance = 10 km:

Location ALocation BDistanceScore
40.7128, -74.0060 (NYC)40.7130, -74.00580.03 km0.997
40.7128, -74.0060 (NYC)40.7580, -73.9855 (Central Park)5.1 km0.49
40.7128, -74.0060 (NYC)34.0522, -118.2437 (LA)3,944 km0.00

YAML Config

yaml
rules:
  - name: location_match
    type: similarity
    field: coordinates
    algorithm: haversine
    threshold: 0.5
    weight: 0.3

Best for: Matching records with geographic coordinates — store locations, delivery addresses with geocoding, IoT device positions.

Comparison Table

AlgorithmSpeedPartial ScoresOrder SensitiveBest Data Type
exactFastestNo (binary)YesIdentifiers
jaro_winklerFastYesYes (prefix-weighted)Person names
levenshteinMediumYesYesAddresses, free text
soundexFastNo (binary)YesEnglish names (supplementary)
metaphoneFastNo (binary)YesInternational names (supplementary)
cosineMediumYesNoCompany names, descriptions
haversineFastYesN/AGeographic coordinates

Pre-Match Normalization

Before comparison, Kanoniv can normalize field values using blocking transformations. These run at the blocking stage but also improve comparison quality:

NormalizerWhat It DoesExample
normalize_phoneStrips formatting, adds country code(555) 010-0100+15550100100
normalize_emailLowercases, strips dots/plus aliases[email protected][email protected]
normalize_nameLowercases, removes titles/suffixesDr. John Smith Jr.john smith
normalize_domainExtracts domain from URL/emailhttps://www.acme.com/aboutacme.com

See Blocking Strategies for the full list of transformations.

Combining Comparators

Use composite rules to combine multiple comparators with AND/OR logic:

yaml
rules:
  - name: address_match
    type: composite
    operator: and
    children:
      - name: street_fuzzy
        type: similarity
        field: street
        algorithm: levenshtein
        threshold: 0.85
        weight: 0.4
      - name: zip_exact
        type: exact
        field: zip_code
        weight: 0.3
  • AND: All sub-rules must meet their thresholds. Use for multi-field matching where every field must agree.
  • OR: Any sub-rule meeting its threshold counts. Use when records may match on different fields.

Next Steps

The identity and delegation layer for AI agents.