Home > Software design >  JavaScript regular expression for string that starts with a character(s) or contains character(s)
JavaScript regular expression for string that starts with a character(s) or contains character(s)

Time:03-30

I am querying a json file with recipes for an autocomplete search input field. As an example imagine that someone is searching for recipes with fish. When the user types f, the outcome should be (an array of 10 objects):

0: {label: 'fig', value: 'fig'}
1: {label: 'fish', value: 'fish'}
2: {label: 'flax', value: 'flax'}
3: {label: 'flour', value: 'flour'}
4: {label: 'fruit', value: 'fruit'}
5: {label: 'farro', value: 'farro'}
6: {label: 'fudge', value: 'fudge'}
7: {label: 'fries', value: 'fries'}
8: {label: 'frank', value: 'frank'}
9: {label: 'fennel', value: 'fennel'}  

So 10 objects starting with f (single word)

Now when the user types fi the outcome should be:

0: {label: 'fig', value: 'fig'}
1: {label: 'fish', value: 'fish'}
2: {label: 'fig jam', value: 'fig jam'}
3: {label: 'fish stock', value: 'fish stock'}
4: {label: 'filo dough', value: 'filo dough'}
5: {label: 'firm tofu', value: 'firm tofu'}
6: {label: 'file powder', value: 'file powder'}
7: {label: 'filet mignon', value: 'filet mignon'}
8: {label: 'fillo shells', value: 'fillo shells'}
9: {label: 'five spice powder', value: 'five spice powder'}

So again 10 objects starting with fi with more than 1 word in some cases.

Now when the user types fis the outcome should be:

0: {label: 'fish', value: 'fish'}
1: {label: 'fish stock', value: 'fish stock'}
2: {label: 'fish seasoning', value: 'fish seasoning'}
3: {label: 'fish roe', value: 'fish roe'}
4: {label: 'Fischsoße', value: 'Fischsoße'}
5: {label: 'Fischflocken', value: 'Fischflocken'}
6: {label: 'cod fish', value: 'cod fish'}
7: {label: 'cat fish', value: 'cat fish'}
8: {label: 'rock fish', value: 'rock fish'}
9: {label: 'thai fish sauce', value: 'thai fish sauce'}

Now we see words starting with fis, words that are composite, like Fischflocken and words that fis is in the middle or the end of the results, like thai fish sauce or cat fish.

Finally if we write the whole fish word we should get the following results:

0: {label: 'fish', value: 'fish'}
1: {label: 'fish stock', value: 'fish stock'}
2: {label: 'fish seasoning', value: 'fish seasoning'}
3: {label: 'fish roe', value: 'fish roe'}
4: {label: 'Fish Sauce', value: 'Fish Sauce'}
5: {label: 'catfish', value: 'catfish'}
6: {label: 'codfish', value: 'codfish'}
7: {label: 'redfish', value: 'redfish'}
8: {label: 'crawfish', value: 'crawfish'}
9: {label: 'monkfish', value: 'monkfish'}

which is more or less what I described above.

I use the filter method and I am looking for a regular expression which is able to produce this behaviour. They way I understand it, is that the expression should test/match if the character(s) provided by the user match the beginning of the strings we query, or if they contain it, either as a whole word or composite. And that should be the order of searching.

It would be nice if someone can help me with that, and it would be even nicer if an explanation of the regexp is provided. Thank you for reading this.

CodePudding user response:

This will sort and filter the results in the way you were looking for.

There is no 1 regex that can handle the sorting you're looking for, so this solution makes two, one that searches the start and one that searches all, and merges the results.

const allData = [
  {label: 'fish', value: 'fish'},
  {label: 'fish stock', value: 'fish stock'},
  {label: 'fish seasoning', value: 'fish seasoning'},
  {label: 'fish roe', value: 'fish roe'},
  {label: 'Fischsoße', value: 'Fischsoße'},
  {label: 'Fischflocken', value: 'Fischflocken'},
  {label: 'cod fish', value: 'cod fish'},
  {label: 'cat fish', value: 'cat fish'},
  {label: 'rock fish', value: 'rock fish'},
  {label: 'thai fish sauce', value: 'thai fish sauce'}
]

function escapeRegExp(text) {
  return text.replace(/[-[\]{}()* ?.,\\^$|#\s]/g, '\\$&');
}

const search = (str) => {
  const startingWithSet = new Set()

  const startingWithRegex = new RegExp('^'   escapeRegExp(str), 'i');
  const startingWithSearch = allData.filter(({ value }) => {
    const matches = startingWithRegex.test(value)
    if (matches) startingWithSet.add(value)
    return matches    
  })

  const includesRegex = new RegExp(escapeRegExp(str), 'ig')
  const includesSearch = allData.filter(({ value }) => !startingWithSet.has(value) && includesRegex.test(value))

  console.log([
    ...startingWithSearch,
    ...includesSearch
  ])
}

search("c")

CodePudding user response:

const arr = [
    { label: 'fig', value: 'fig' },
    { label: 'fish', value: 'fish' },
    { label: 'flax', value: 'flax' },
    { label: 'flour', value: 'flour' },
    { label: 'fruit', value: 'fruit' },
    { label: 'farro', value: 'farro' },
    { label: 'fudge', value: 'fudge' },
    { label: 'fries', value: 'fries' },
    { label: 'frank', value: 'frank' },
    { label: 'fennel', value: 'fennel' },
    { label: 'crawfish', value: 'crawfish' },
    { label: 'cat fish', value: 'cat fish' },
    { label: 'fish sauce', value: 'fish sauce' },
];

const filterFromInput = (input) => {
    const regex = new RegExp(`${input}`, 'gi');

    return arr.filter((item) => item.label.match(regex));
};

console.log(filterFromInput('fish'));

  • Related