Deepika Deepi
Deepika Deepi

Reputation: 127

Search functionality in Javascript/React

I have to implement a search functionality in an app I am currently working on. However, the problem is how we display the results. For Example,

const queries = ['revised sales report','sales report',  'revision: sales investors report']

function filterResults(term) {
  const search = queries.filter(item =>item.toLowerCase().includes(term.toLowerCase()));
  return search;
  }

  console.log(filterResults('sales report'))

The above function gives me

[
"revised sales report",
"sales report"
]

as results. But however, the desired output should be,

[
"sales report",
"revised sales report",
"revision: sales investors report"
]

That is, my first result should always start with the exact search string i.e, even though sales report is the second item in the array, that is the exact match so it should appear first, second result can contain string at any place, i.e, the first item in the array has the search term but it starts with some other word and hence it should appear second. and subsequent result can have each input in different places of the sentence , i.e, the third item in the array has both the term sales and report but it is in disconnected and appears in different part of the sentence so it should appear after the second result. But the function I made above completely ignores the third result.

I have no clue how to approach this. I just started working 3 months ago. Any pointers on how to approach would be helpful.

Upvotes: 1

Views: 617

Answers (3)

Yevhen Horbunkov
Yevhen Horbunkov

Reputation: 15530

Assuming your actual search string was 'sales report' and your intention was to return

  • exact match (hereafter case-insensitively) as a first search output
  • partial match (where searched string is a substring of some query item)
  • vague match (where both 'sales' and 'report' strings are present in arbitrary order

You may do the following:

const queries = ['revised sales report','sales report',  'revision: sales investors report'],

      searchedString = 'sales report',
      
      search = (arr, str) => {
        const exactMatch = arr.find(s => s.toLowerCase() == str.toLowerCase()),
              partialMatches = arr.filter(s => s.toLowerCase().includes(str.toLowerCase())),
              vagueMatches = arr.filter(s => 
                 str
                  .toLowerCase()
                  .split(' ')
                  .every(w => s.toLowerCase().includes(w))
              )
        return [...new Set([exactMatch, ...partialMatches, ...vagueMatches])]            
      }
      
console.log(search(queries, searchedString))     

p.s. this, of course, does not take punctuation into account (but may be adjusted to do that either), but surely gives an idea on how to approach your problem.

Upvotes: 1

Gabriele Petrioli
Gabriele Petrioli

Reputation: 195952

From your description you seem to want to rank results based on 3 different criteria

  1. result === query
  2. result includes query
  3. results includes all words of query

So you search function, will have to search for each type of match, and assign a rank to the result.

A naive approach

const queries = ['revised sales report', 'sales report', 'revision: sales investors report']


const matchWholeString = (value, term) =>
  value === term;
const matchIncludesString = (value, term) =>
  value.includes(term);
const matchIncludesAllWords = (value, term) =>
  term.split(/\s+/).every(word => value.includes(word));

function filterResults(term) {
  const lowercaseTerm = term.toLowerCase();
  const search = queries.map(query => {
    const lowercaseQuery = query.toLowerCase();
    if (matchWholeString(lowercaseQuery, lowercaseTerm)) {
      return {
        query,
        rank: 1000
      };
    }
    if (matchIncludesString(lowercaseQuery, lowercaseTerm)) {
      return {
        query,
        rank: 100
      }
    }
    if (matchIncludesAllWords(lowercaseQuery, lowercaseTerm)) {
      return {
        query,
        rank: 10
      }
    }
    return {
      query,
      rank: 0
    }
  }).filter(({
    rank
  }) => rank).sort((a, b) => a.rank < b.rank).map(({
    query
  }) => query);
  return search;
}

console.log(filterResults('sales report'))

Upvotes: 1

Ali Almoullim
Ali Almoullim

Reputation: 1038

This is how the includes function works. it simply checks whether the value you provided which is 'sales report' let's say, exist in the string you are checking against which it does in the first two strings in the list, but not in the last one. includes checks the exact value, it won't do a fuzzy search for you.

You can use something like https://fusejs.io/ to achieve what you want. It can do a fuzzy search and will provide you with a score for each found value so you can use that to order the results in the way you described.

an example from their documentation

const list = ["Old Man's War", 'The Lock Artist']
const options = {
  includeScore: true
}

const fuse = new Fuse(list, options)

const result = fuse.search('od man')

result

[
  {
    "item": "Old Man's War",
    "refIndex": 0,
    "score": 0.35
  }
]

Upvotes: 0

Related Questions