Kyori

String distance & similarity for JavaScript

Levenshtein, Damerau-Levenshtein, Hamming, Jaro-Winkler — plus the Kyori ranking algorithm and the ready-made Kyori Index for token-sensitive autocomplete scoring. Every metric is its own package subpath, so you can require methods/levensthein on its own and leave the rest behind. Every metric implemented natively — pure JavaScript, MIT-licensed.

In Japanese, 距離 (きょり) means "distance".

npm install @wizhut_tech/kyori
Library · JavaScript · MIT

Five algorithms, one consistent shape.

Every method exposes the same three functions — distance(), similarity(), and rank() — so you can swap metrics without touching your call sites. Everything arrives in one package under two namespaces — methods for the metrics, indices for the search index — so reach for search() when you want a whole vocabulary ranked.

Edit-distance metrics

Lower is more similar — zero means identical.

const { levensthein } = require(
  '@wizhut_tech/kyori/methods/levensthein')
const { damerau_levensthein } = require(
  '@wizhut_tech/kyori/methods/damerau_levensthein')
const { hamming } = require(
  '@wizhut_tech/kyori/methods/hamming')

// …/methods/<name> — one metric, nothing else

levensthein.distance('foo', 'food')      // → 1
levensthein.similarity('foo', 'food')    // → 0.75
levensthein.rank('foo', ['food', 'foo'])
// → [ { term: 'foo', score: 0 },
//     { term: 'food', score: 1 } ]

// a transposition costs one edit, not two
damerau_levensthein.distance('form', 'from')   // → 1

// equal length only — -1 rather than a guess
hamming.distance('karolin', 'kathrin')   // → 3
hamming.distance('foo', 'food')          // → -1

Similarity & ranking

Jaro-Winkler scores up to 1. The Kyori ranking cost lives on distance() and counts down to 0 — lower is better.

const { jaro_winkler } =
  require('@wizhut_tech/kyori/methods/jaro_winkler')
const { kyori } =
  require('@wizhut_tech/kyori/methods/kyori')

jaro_winkler.similarity('Maria', 'Marie')   // → 0.92

// distance() is the ranking cost — lower is better
// it folds case, accents and hyphens first
kyori.distance('foo-bar', 'foo bar')      // → 0
kyori.distance('cafe', 'café')            // → 0
// a typed prefix ignores what you haven't typed
kyori.distance('foo', 'food')             // → 0
// one mistyped character costs one, not the rest
kyori.distance('fod', 'food')             // → 1
// same keywords in a different order: a flat 1
kyori.distance('hotel bel-air', 'bel-air hotel')
// → 1

// rank(): lowest cost first, and a tie goes to the shorter
kyori.rank('pa', ['paper towels', 'paint', 'pan'])
// → pan, paint, paper towels — all at cost 0

// similarity() is token overlap — higher is better
kyori.similarity('hotel bel-air', 'bel-air hotel')
// → 1

Use case — autocomplete with Kyori Index

Load your vocabulary once, and search() returns every term scored and sorted — best match first, the shorter term first among equals.

const { KyoriIndex } =
  require('@wizhut_tech/kyori/indices/kyori')

const index = new KyoriIndex()
index.addMany(['cart', 'artist'])
index.addOne('art')
index.count()          // → 3

index.search('art')
// → [ { term: 'art',    score: 0 },
//     { term: 'artist', score: 0 },
//     { term: 'cart',   score: 5 } ]

What's in the box

Five algorithms and an index — each one has a job

Four metrics you already know, each with the case it is built for and one line of real output. Then the two pieces that are Kyori’s own.

Edit distance

Levenshtein

methods/levensthein

The classic: the fewest single-character insertions, deletions and substitutions that turn one string into the other.

Best for spell-check suggestions, near-duplicate records, anything diff-shaped.

'kitten', 'sitting'
distance → 3
Edit distance

Damerau-Levenshtein

methods/damerau_levensthein

Levenshtein plus transpositions: two swapped neighbours count as one edit, not two — the shape most typos actually take.

Best for typo-tolerant matching on anything a person typed.

'teh', 'the'
distance → 1
// Levenshtein says 2
Edit distance

Hamming

methods/hamming

Position-by-position mismatches between two strings of equal length. No alignment step, so it is the cheapest of the five.

Best for fixed-width codes, hashes, fingerprints and bit strings.

'101101', '100111'
distance → 2
// lengths differ: -1
Similarity · 0 to 1

Jaro-Winkler

methods/jaro_winkler

Made for short strings like names: rewards characters that match near the same position, with a bonus for a shared prefix.

Best for "did you mean…" on people, places and product names.

'Jon', 'John'
similarity → 0.93
Ranking cost · methods/kyori

The reason this library exists

Kyori ranking

A token-sensitive cost built for one job: ordering suggestions under a query someone is still typing.

  • Folds case, Latin accents and hyphens first — café-bar and Cafe Bar match at cost 0.
  • A typed prefix pays only for its typos. What you have not typed yet is free, and no typo is tolerated before the third character.
  • The same words in a different order cost a flat 1.
  • Among equal costs, rank() puts the shorter completion first.
'São Paulo', 'sao paulo'
distance → 0
'new yrok', 'new york'
distance → 1
Search index · indices/kyori

Autocomplete without a search engine

Kyori Index

Load a vocabulary once and search it on every keystroke. Every term comes back scored by kyori.rank() and sorted — best match first, shorter term first among equals.

  • addMany(terms) at start-up, addOne(term) as new ones arrive.
  • search(query) on each keystroke.
  • Show the top of the list. It is already in order.
search('pan')
→ pan 0 · pancake 0 · paint 1 · paper towels 1

Open source

Ready to drop in

Free, MIT-licensed, no telemetry, no heavyweight dependencies — every metric implemented in-repo. Works in Node, the browser, and edge runtimes.