Kyori

String 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.

npm install @wizhut_tech/kyori

Five algorithms, one consistent shape

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 and 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
// your words later in the completion: a small cost,
// after everything that starts with what you typed
kyori.distance('pizza', 'margherita pizza')
// → 2

// 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

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: 7 } ]

Five algorithms and an index, each with a job

Levenshtein

methods/levensthein

The classic Levenshtein distance: 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

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

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

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

Kyori ranking

methods/kyori

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.
  • A completion that carries your words somewhere else costs little, and always comes after the ones that start with what you typed.
  • Among equal costs, rank() puts the shorter completion first.
'São Paulo', 'sao paulo'
distance → 0
'new yrok', 'new york'
distance → 1

Kyori Index

indices/kyori

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

How well does it rank? We measured it.

On more than 11,000 real Amazon searches held out of its design, Kyori’s ranking is level with Fuse.js, fuzzysort and sorting by length, and ahead of match-sorter. The progress report has the rules, the method and every number — including what popularity adds that no string score can.

Read the progress report