quick-score
is a JavaScript string-scoring and fuzzy-matching library based on the Quicksilver algorithm, designed for smart auto-complete.
QuickScore improves on the original Quicksilver algorithm by tuning the scoring for long strings, such as webpage titles or URLs, so that the order of the search results makes more sense. It's used by the QuicKey extension for Chrome to enable users to easily find an open tab via search.
QuickScore is fast, dependency-free, and is less than 2KB when minified and gzipped.
See QuickScore in action, and compare its results to other scoring and matching libraries.
npm install --save quick-score
You can import the quickScore()
function from the ES6 module:
import {quickScore} from "quick-score";
Or from a property of the CommonJS module:
const quickScore = require("quick-score").quickScore;
You can then call quickScore()
with a string
and a query
to score against that string. It will return a floating point score between 0
and 1
. A higher score means that string is a better match for the query. A 1
means the query is the highest match for the string, though the two strings may still differ in case and whitespace characters.
quickScore("thought", "gh"); // 0.7000000000000001
quickScore("GitHub", "gh"); // 0.9166666666666666
Matching gh
against GitHub
returns a higher score than thought
, because it matches the capital letters in GitHub
, which are weighted more highly.
A typical use-case for string scoring is auto-completion, where you want the user to get to the desired result by typing as few characters as possible. Instead of calling quickScore()
directly for every item in a list and then sorting it based on the score, it's simpler to use an instance of the QuickScore
class:
import {QuickScore} from "quick-score";
const qs = new QuickScore(["thought", "giraffe", "GitHub", "hello, Garth"]);
const results = qs.search("gh");
//=>
[
{
"item": "GitHub",
"score": 0.9166666666666666,
"matches": [[0, 1], [3, 4]]
},
{
"item": "hello, Garth",
"score": 0.6263888888888888,
"matches": [[7, 8], [11, 12]]
},
...
The results
array is a list of objects that represent the results of matching the query against each string that was passed to the constructor. It's sorted high to low on each item's score. Strings with identical scores are sorted alphabetically and case-insensitively. In the simple case of scoring bare strings, each item in the results array has three properties:
item
: the string that was scoredscore
: the floating point score of the string for the current querymatches
: an array of arrays that specifies the character ranges where the query matched the string
This array could then be used to render a list of matching results as the user types a query.
Typically, you'll be sorting items more complex than a bare string. To tell QuickScore which of an object's keys to score a query against, pass an array of key names or dot-delimited paths as the second parameter to the QuickScore()
constructor:
const bookmarks = [
{
"title": "lodash documentation",
"url": "https://lodash.com/docs"
},
{
"title": "Supplying Images - Google Chrome",
"url": "developer.chrome.com/webstore/images"
},
...
];
const qs = new QuickScore(bookmarks, ["title", "url"]);
const results = qs.search("devel");
//=>
[
{
"item": {
"title": "Supplying Images - Google Chrome",
"url": "developer.chrome.com/webstore/images"
},
"score": 0.9138888888888891,
"scoreKey": "url",
"scores": {
"title": 0,
"url": 0.9138888888888891
},
"matches": {
"title": [],
"url": [[0, 5]]
}
},
...
Each item in the results array has a few more properties when matching against objects:
item
: the object that was scoredscore
: the highest score from among the individual key scoresscoreKey
: the name of the key with the highest score, which will be an empty string if they're all zeroscores
: a hash of the individual scores for each keymatches
: a hash of arrays that specify the character ranges of the query match for each key
When two items have the same score, they're sorted alphabetically and case-insensitively on the first key in the keys array. In the example above, that would be title
.
Many search interfaces highlight the letters in each item that match what the user has typed. The matches
property of each item in the results array contains information that can be used to highlight those matching letters.
This function is an example of how an item could be highlighted using React. It surrounds each sequence of matching letters in a <mark>
tag and then returns the full string in a <span>
. You could then style the <mark>
tag to be bold or a different color to highlight the matches. (Something similar could be done by concatenating plain strings of HTML tags, though you'll need to be careful to escape the substrings.)
function highglight(string, matches) {
const substrings = [];
let previousEnd = 0;
for (let [start, end] of matches) {
const prefix = string.substring(previousEnd, start);
const match = <mark>{string.substring(start, end)}</mark>;
substrings.push(prefix, match);
previousEnd = end;
}
substrings.push(string.substring(previousEnd));
return <span>{React.Children.toArray(substrings)}</span>;
}
The QuickScore demo uses this approach to highlight the matches, via the MatchedString component.
See the API docs for a full description of the QuickScore class and the quickScore function.