fuzzysort icon indicating copy to clipboard operation
fuzzysort copied to clipboard

Match Partial Search Query With Spaces

Open AlexAbraham1 opened this issue 3 years ago • 8 comments

Hi! Love this library and really enjoy using it for all my JS projects!

I'm trying to figure out the best way for fuzzysort to match a string if a search query has spaces. I think it's easiest to explain what I am trying to do with a few examples:

query = 'hello world';
fuzzysort.single(query, 'hello world'); //should return an exact match
fuzzysort.single(query, 'hello'); //right now it returns null, I want it to match since it's a partial match of the query

One option I have done so far is to split on ' ' which does properly match partial words but then highlight functionality is off since it's using different fuzzysort.single() queries.

AlexAbraham1 avatar Sep 30 '22 02:09 AlexAbraham1

One option I have done so far is to split on ' ' which does properly match partial words but then highlight functionality is off since it's using different fuzzysort.single() queries.

this is the correct solution. not sure why your highlighting would be off. here's a function to merge the results of multiple searches

const go = (search, targets, options) => {
  if(!Array.isArray(search)) return fuzzysort.go(search, targets, options)
  return search
    .filter((s,i) => search.indexOf(s) === i)
    .flatMap(s => fuzzysort.go(s, targets, options).map(r => { return {...r} }))
    .sort((a,b) => b.score-a.score)
    .filter((result, i, results) => i === results.findIndex(r => r.target === result.target))
}

in your case use it like this

let search = 'hello world'
go([search, ...search.split(' ')], ['hello world']) // returns exact match
go([search, ...search.split(' ')], ['hello']) // returns partial match

farzher avatar Sep 30 '22 07:09 farzher

Thanks so much for the quick response! I am trying to use the example you gave and it's not merging the results the way I expect. When calling the highlight function on the exact match, it only highlights the second word. The score is also -6 on hello world but 0 on hello which I wouldn't expect since the first one should be the higher score.

console.log("test start");

const go = (search, targets, options) => {
  if(!Array.isArray(search)) return fuzzysort.go(search, targets, options)
  return search
    .filter((s,i) => search.indexOf(s) === i)
    .flatMap(s => fuzzysort.go(s, targets, options).map(r => { return {...r} }))
    .sort((a,b) => b.score-a.score)
    .filter((result, i, results) => i === results.findIndex(r => r.target === result.target))
}
let search = 'hello world'
let r1 = go([search, ...search.split(' ')], ['hello world'], {}) // returns exact match
console.log(fuzzysort.highlight(r1[0])); // hello <b>world</b>
let r2 = go([search, ...search.split(' ')], ['hello'], {}) // returns partial match
console.log(fuzzysort.highlight(r2[0])); // <b>hello</b>

console.log("test end");

AlexAbraham1 avatar Sep 30 '22 13:09 AlexAbraham1

oops. you're right. there's 2 issues here.

  1. because of performance optimizations, the highlight information is being overwritten by following searches.
  2. because of the new way spaces behave in 2.0, "hello world" doesn't match against "hello world" with a perfect score anymore .-.

i removed the sort, and added a precomputed highlight field:

const go = (search, targets, options) => {
  if(!Array.isArray(search)) return fuzzysort.go(search, targets, options)
  return search
    .filter((s,i) => search.indexOf(s) === i)
    .flatMap(s => fuzzysort.go(s, targets, options).map(r => { return {...r, highlight:fuzzysort.highlight(r)} }))
    .filter((result, i, results) => i === results.findIndex(r => r.target === result.target))
}

let search = 'hello world'
console.log(go([search, ...search.split(' ')], ['hello world'])[0].highlight)
console.log(go([search, ...search.split(' ')], ['hello'])[0].highlight)

farzher avatar Sep 30 '22 13:09 farzher

This is great! Gives me a good jumping off point to accomplish what I am trying to do 😄

AlexAbraham1 avatar Sep 30 '22 14:09 AlexAbraham1

One more question! 🙈

Would you know why this is highlighting hello instead of hello my?

let test = 'hello my name';
console.log(go([test, ...test.split(' ')], ['hello my test']));

AlexAbraham1 avatar Sep 30 '22 15:09 AlexAbraham1

you're never searching for hello my, you'd need to generate more search permutations than a simple split

farzher avatar Sep 30 '22 17:09 farzher

Right but would it be possible to combine the highlights? You search for hello and that highlights the first word. You then search for my and that highlights the second word. If you combine both you should see a highlight for hello my without needing to look for all the different permutations

AlexAbraham1 avatar Sep 30 '22 17:09 AlexAbraham1

that's possible, but a little complicated. this isn't a well supported use case. btw, what is your use case? why do you want this?

farzher avatar Sep 30 '22 17:09 farzher