Skip to content

Commit

Permalink
feat(MATCH): adds close match sort functionality (#27)
Browse files Browse the repository at this point in the history
* feat: Adds close match sort functionality

If the words fall below acronym for matching, it ranks how close the match string is to the test
string. It will return a number between 1 and 2 where 2 is a perfet match and 1 is no match. It does
this by dividing 1 by the difference of the spread of the characters in the match string within the
test string and the length of the match string and subtracting that quotient from 1. It then takes
that differneces and subtracts it from 2.

* Updates README, contributors, comments, and places
close match logic within the match ranking

* Fixes rank/number comments

* fixes contributors and comments

* Corrects rankings.MATCH comments to rankings.MATCHES

* fixes the close sort and the ignore diacritics test

* fixes long comment on line 159

* fixes comment on close match rank

* cleans up directory and resets dependencies

* updates the README for close match examples

* corrects the README close match example
  • Loading branch information
kevindavus authored and Kent C. Dodds committed Apr 3, 2017
1 parent 9accc43 commit c228f5a
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 82 deletions.
10 changes: 10 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@
"test"
]
},
{
"login": "osfan501",
"name": "Kevin Davis",
"avatar_url": "https://avatars3.githubusercontent.com/u/4150097?v=3",
"profile": "kevindav.us",
"contributions": [
"code",
"test"
]
},
{
"login": "nfdjps",
"name": "Denver Chen",
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Simple, expected, and deterministic best-match sorting of an array in JavaScript
[![downloads][downloads-badge]][npm-stat]
[![MIT License][license-badge]][LICENSE]

[![All Contributors](https://img.shields.io/badge/all_contributors-5-orange.svg?style=flat-square)](#contributors)
[![All Contributors](https://img.shields.io/badge/all_contributors-6-orange.svg?style=flat-square)](#contributors)
[![PRs Welcome][prs-badge]][prs]
[![Donate][donate-badge]][donate]
[![Code of Conduct][coc-badge]][coc]
Expand Down Expand Up @@ -38,7 +38,7 @@ To explain the ranking system, I'll use countries as an example:
4. **WORD STARTS WITH**: If the item has multiple words, then if one of those words starts with the given value (ex. `Repub` would match `Dominican Republic`)
5. **CONTAINS**: If the item contains the given value (ex. `ham` would match `Bahamas`)
6. **ACRONYM**: If the item's acronym is the given value (ex. `us` would match `United States`)
7. **SIMPLE MATCH**: If the item has letters in the same order as the letters of the given value (ex. `iw` would match `Zimbabwe`, but not `Kuwait` because it must be in the same order).
7. **SIMPLE MATCH**: If the item has letters in the same order as the letters of the given value (ex. `iw` would match `Zimbabwe`, but not `Kuwait` because it must be in the same order). Furthermore, if the item is a closer match, it will rank higher (ex. `ua` matches `Uruguay` more closely than `United States of America`, therefore `Uruguay` will be ordered before `United States of America`)

This ranking seems to make sense in people's minds. At least it does in mine. Feedback welcome!

Expand Down Expand Up @@ -82,10 +82,10 @@ const objList = [
{name: 'Jen', color: 'Red'},
]
matchSorter(objList, 'g', {keys: ['name', 'color']})
// [{name: 'George', color: 'Blue'}, {name: 'Janice', color: 'Green'}]
// [{name: 'George', color: 'Blue'}, {name: 'Janice', color: 'Green'}, {name: 'Fred', color: 'Orange'}]

matchSorter(objList, 're', {keys: ['color', 'name']})
// [{name: 'Jen', color: 'Red'}, {name: 'Janice', color: 'Green'}, {name: 'Fred', color: 'Orange'}]
// [{name: 'Jen', color: 'Red'}, {name: 'Janice', color: 'Green'}, {name: 'Fred', color: 'Orange'}, {name: 'George', color: 'Blue'}]
```

__Array of values__: When the specified key matches an array of values, the best match from the values of in the array is going to be used for the ranking.
Expand Down Expand Up @@ -193,7 +193,7 @@ You can disable this behavior by specifying `keepDiacritics: true`
```javascript
const thingsWithDiacritics = ['jalapeño', 'à la carte', 'café', 'papier-mâché', 'à la mode']
matchSorter(thingsWithDiacritics, 'aa')
// ['jalapeño', 'à la carte', 'papier-mâché', 'à la mode']
// ['jalapeño', 'à la carte', 'à la mode', 'papier-mâché']

matchSorter(thingsWithDiacritics, 'aa', {keepDiacritics: true})
// ['jalapeño', 'à la carte']
Expand Down Expand Up @@ -221,8 +221,8 @@ You might try [Fuse.js](https://github.com/krisk/Fuse). It uses advanced math fa
Thanks goes to these people ([emoji key][emojis]):

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
| [<img src="https://avatars.githubusercontent.com/u/1500684?v=3" width="100px;"/><br /><sub>Kent C. Dodds</sub>](https://kentcdodds.com)<br />[💻](https://github.com/kentcdodds/match-sorter/commits?author=kentcdodds) [📖](https://github.com/kentcdodds/match-sorter/commits?author=kentcdodds) 🚇 [⚠️](https://github.com/kentcdodds/match-sorter/commits?author=kentcdodds) 👀 | [<img src="https://avatars.githubusercontent.com/u/8263298?v=3" width="100px;"/><br /><sub>Conor Hastings</sub>](http://conorhastings.com)<br />[💻](https://github.com/kentcdodds/match-sorter/commits?author=conorhastings) [📖](https://github.com/kentcdodds/match-sorter/commits?author=conorhastings) [⚠️](https://github.com/kentcdodds/match-sorter/commits?author=conorhastings) 👀 | [<img src="https://avatars.githubusercontent.com/u/574806?v=3" width="100px;"/><br /><sub>Rogelio Guzman</sub>](https://github.com/rogeliog)<br />[📖](https://github.com/kentcdodds/match-sorter/commits?author=rogeliog) | [<img src="https://avatars.githubusercontent.com/u/1416436?v=3" width="100px;"/><br /><sub>Claudéric Demers</sub>](http://ced.io)<br />[💻](https://github.com/kentcdodds/match-sorter/commits?author=clauderic) [📖](https://github.com/kentcdodds/match-sorter/commits?author=clauderic) [⚠️](https://github.com/kentcdodds/match-sorter/commits?author=clauderic) | [<img src="https://avatars1.githubusercontent.com/u/19157735?v=3" width="100px;"/><br /><sub>Denver Chen</sub>](https://github.com/nfdjps)<br />[💻](https://github.com/kentcdodds/match-sorter/commits?author=nfdjps) [📖](https://github.com/kentcdodds/match-sorter/commits?author=nfdjps) [⚠️](https://github.com/kentcdodds/match-sorter/commits?author=nfdjps) |
| :---: | :---: | :---: | :---: | :---: |
| [<img src="https://avatars.githubusercontent.com/u/1500684?v=3" width="100px;"/><br /><sub>Kent C. Dodds</sub>](https://kentcdodds.com)<br />[💻](https://github.com/kentcdodds/match-sorter/commits?author=kentcdodds) [📖](https://github.com/kentcdodds/match-sorter/commits?author=kentcdodds) 🚇 [⚠️](https://github.com/kentcdodds/match-sorter/commits?author=kentcdodds) 👀 | [<img src="https://avatars.githubusercontent.com/u/8263298?v=3" width="100px;"/><br /><sub>Conor Hastings</sub>](http://conorhastings.com)<br />[💻](https://github.com/kentcdodds/match-sorter/commits?author=conorhastings) [📖](https://github.com/kentcdodds/match-sorter/commits?author=conorhastings) [⚠️](https://github.com/kentcdodds/match-sorter/commits?author=conorhastings) 👀 | [<img src="https://avatars.githubusercontent.com/u/574806?v=3" width="100px;"/><br /><sub>Rogelio Guzman</sub>](https://github.com/rogeliog)<br />[📖](https://github.com/kentcdodds/match-sorter/commits?author=rogeliog) | [<img src="https://avatars.githubusercontent.com/u/1416436?v=3" width="100px;"/><br /><sub>Claudéric Demers</sub>](http://ced.io)<br />[💻](https://github.com/kentcdodds/match-sorter/commits?author=clauderic) [📖](https://github.com/kentcdodds/match-sorter/commits?author=clauderic) [⚠️](https://github.com/kentcdodds/match-sorter/commits?author=clauderic) | [<img src="https://avatars3.githubusercontent.com/u/4150097?v=3" width="100px;"/><br /><sub>Kevin Davis</sub>](kevindav.us)<br />[💻](https://github.com/kentcdodds/match-sorter/commits?author=osfan501) [⚠️](https://github.com/kentcdodds/match-sorter/commits?author=osfan501) | [<img src="https://avatars1.githubusercontent.com/u/19157735?v=3" width="100px;"/><br /><sub>Denver Chen</sub>](https://github.com/nfdjps)<br />[💻](https://github.com/kentcdodds/match-sorter/commits?author=nfdjps) [📖](https://github.com/kentcdodds/match-sorter/commits?author=nfdjps) [⚠️](https://github.com/kentcdodds/match-sorter/commits?author=nfdjps) |
| :---: | :---: | :---: | :---: | :---: | :---: |
<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors][all-contributors] specification. Contributions of any kind welcome!
Expand Down
50 changes: 31 additions & 19 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ function getMatchRanking(testString, stringToRank, options) {
return rankings.ACRONYM
}

return stringsByCharOrder(testString, stringToRank)
// will return a number between rankings.MATCHES and
// rankings.MATCHES + 1 depending on how close of a match it is.
return getClosenessRanking(testString, stringToRank)
}

/**
Expand All @@ -148,39 +150,49 @@ function getAcronym(string) {
}

/**
* Returns a rankings.matches or noMatch score based on whether
* the characters in the stringToRank are found in order in the
* testString
* Returns a score based on how spread apart the
* characters from the stringToRank are within the testString.
* A number close to rankings.MATCHES represents a loose match. A number close
* to rankings.MATCHES + 1 represents a loose match.
* @param {String} testString - the string to test against
* @param {String} stringToRank - the string to rank
* @returns {Number} the ranking for how well stringToRank matches testString
* @returns {Number} the number between rankings.MATCHES and
* rankings.MATCHES + 1 for how well stringToRank matches testString
*/
function stringsByCharOrder(testString, stringToRank) {
function getClosenessRanking(testString, stringToRank) {
let charNumber = 0

function findMatchingCharacter(matchChar, string) {
let found = false
for (let j = charNumber; j < string.length; j++) {
function findMatchingCharacter(matchChar, string, index) {
for (let j = index; j < string.length; j++) {
const stringChar = string[j]
if (stringChar === matchChar) {
found = true
charNumber = j + 1
break
return j + 1
}
}
return found
return -1
}

for (let i = 0; i < stringToRank.length; i++) {
function getRanking(spread) {
const matching = spread - stringToRank.length + 1
const ranking = rankings.MATCHES + (1 / (matching))
return ranking
}
const firstIndex = findMatchingCharacter(stringToRank[0], testString, 0)
if (firstIndex < 0) {
return rankings.NO_MATCH
}
charNumber = firstIndex
for (let i = 1; i < stringToRank.length; i++) {
const matchChar = stringToRank[i]
const found = findMatchingCharacter(matchChar, testString)
charNumber = findMatchingCharacter(matchChar, testString, charNumber)
const found = charNumber > -1
if (!found) {
return rankings.NO_MATCH
}
}
return rankings.MATCHES

const spread = charNumber - firstIndex
return getRanking(spread)
}

/**
* Sorts items that have a rank, index, and keyIndex
* @param {Object} a - the first item to sort
Expand Down
7 changes: 3 additions & 4 deletions src/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ const tests = {
'aa',
],
output: [
'jalapeño', 'à la carte', 'papier-mâché', 'à la mode',
'jalapeño', 'à la carte', 'à la mode', 'papier-mâché',
],
},
'takes diacritics in account when keepDiacritics specified as true': {
Expand All @@ -271,15 +271,14 @@ const tests = {
],
},
'sorts items based on how closely they match': {
skip: true, // wanna help make this a thing? https://github.com/kentcdodds/match-sorter/issues/21
input: [
['Antigua and Barbuda', 'India', 'Bosnia and Herzegovina', 'Indonesia'],
'Ina',
],
output: [
// these are sorted based on how closes their letters are to one another based on the input
// 2 6 8 15
'India', 'Indonesia', 'Antigua and Barbuda', 'Bosnia and Herzegovina',
// contains 2 6 8
'Bosnia and Herzegovina', 'India', 'Indonesia', 'Antigua and Barbuda',
// though, technically, `India` comes up first because it matches with STARTS_WITH...
],
},
Expand Down
Loading

0 comments on commit c228f5a

Please sign in to comment.