DEV Community

Gil Rudolf Härdi
Gil Rudolf Härdi

Posted on

Why this regex it's not working properly? [solved]

I have a function that filter a food list and returns a fruit list.
The expected result is: ["apple", "banana", "watermelon"] but the next code don't push watermelon in the array.

const foodList = ["apple", "soap", "banana", "watermelon", "pizza"];

function getFruits(foodList) {
  const fruitRGX = /apple|banana|watermelon/g;
  const fruitList = [];

  for (const food of foodList) {
    if (!fruitRGX.test(food)) continue;

    fruitList.push(food);
  }

  return fruitList;
}

const fruits = getFruits(foodList);
const expect = ["apple", "banana", "watermelon"];

console.log({ fruits, expect });
// output:
// {
//  fruits: [ "apple", "banana" ],
//  expect: [ "apple", "banana", "watermelon" ]
// }
Enter fullscreen mode Exit fullscreen mode

If I remove the g flag in the fruitRGX or I move the constant declaration inside the for loop then fruits is equal to expect.

Can someone explain what happening?

Edit

I already get the answer since the first comment, see my reply or Ben Calder's comment if you want see a quick explain about this problem.

Discussion (8)

Collapse
blindfish3 profile image
Ben Calder

This had me scratching my head for a few minutes - I don't typically use RegExp.test...

So it looks like the problem is caused by this documented behaviour:

When a regex has the global flag set, test() will advance the lastIndex of the regex.
Note: As long as test() returns true, lastIndex will not reset—even when testing a different string!

So since watermelon follows banana - which returned true - the stored lastIndex influences the point from which the next test is made and the match fails. The solution is simple: remove the g flag. You'd only use that in a regex if you were searching for multiple matches in the same string (which would go some way to explaining the exhibited behaviour).

Collapse
blindfish3 profile image
Ben Calder • Edited on

Oh - and one suggestion in terms of naming - I wouldn't use food in your iterator or array name:

for (const food of foodList) {
}
Enter fullscreen mode Exit fullscreen mode

It might seem pedantic; but at this point you're testing whether they are food; so I'd use generic terms item and itemList.

Collapse
myogeshchavan97 profile image
Yogesh Chavan

That's true. I have explained this weird behavior in my this article

Collapse
ghaerdi profile image
Gil Rudolf Härdi Author

I already found that document since the first comment. Anyway, I edited the post mentioning your comment. Thank you.

Collapse
mireslami809 profile image
mireslami-hossein

you can do like this:

const foodList = ["apple", "soap", "banana", "watermelon", "pizza"];

let fruitList = []
function getFruits(foodList) {
  const fruitRGX = /apple|banana|watermelon/g;
  foodList.forEach(food =>{
      if(food.match(fruitRGX)){
        fruitList.push(food)
      }
  })

  return fruitList
}

console.log(getFruits(foodList))
// output: ["apple", "banana", "watermelon"]

Enter fullscreen mode Exit fullscreen mode

I think if you use match(regex) instead of test() is better.
and you can use array.forEach() instead of for() for better readability!

Collapse
ghaerdi profile image
Gil Rudolf Härdi Author

Thanks for the advise, what I really need is to know what happened with my code.

What is the reason for not output watermelon?

But somehow your code is very helpful, you used match and it worked well so my question has changed to why test don't worked but match yes?. This time I found the info that I want:
The RegEx object has a property called lastIndex updated with test method, that property don't reset when test returns true. If the same regex is used again then can happen what happened with my code.

Collapse
myogeshchavan97 profile image
Yogesh Chavan

@ghaerdi I have explained this weird behavior of test method in my this article.

Collapse
frondor profile image
Federico Vázquez

Or better yet, a pure function:

const knownFruits = new Set(["apple", "banana", "watermelon"])

function getFruits(foodList) {
  return foodList.filter(food => knownFruits.has(food))
}
Enter fullscreen mode Exit fullscreen mode