How can I do a lazy regex matchAll with JavaSCript?

Viewed 80

I want to detect job and jobs in a piece of text. So The jobs report came out and it was a great job! should match job twice and jobs once.

If I do:

const res = str.matchAll(/job|jobs/gi)

Then it matches jobs once and job once. How can I modify it to do what I want?

UPDATE Ultimately, I have a lot more like /job|jobs|get\sa\sjob/

3 Answers

The most naive approach for achieving the OP's result was to just concat each match(ing) array of two separate regular expressions (here, the singular and the plural variant of 'job') ...

const text = 'The jobs report came out and it was a great JOB!';

console.log(
  text.match(/jobs/gi).concat(text.match(/job/gi))
);
console.log(
  [...text.match(/jobs/gi), ...text.match(/job/gi)]
);
.as-console-wrapper { min-height: 100%!important; top: 0; }

If one also wants to take into account word boundaries, as done by other answers, one has to change e.g. the second example of the above first approach to ...

const text = 'The jobs report came out and it was a great NUTJOB of a JOB!';

// does not take word boundaries into account ...
console.log(
  [...text.match(/jobs/gi), ...text.match(/job/gi)]
);

// ... changed in order to support word boundaries.
console.log([
  ...text.match(/\bjobs\b/gi),
  ...[...text.matchAll(/\b(job)s?\b/gi)].map(([match, $1]) => $1)
]);
.as-console-wrapper { min-height: 100%!important; top: 0; }

I am not a JavaScript person, and I couldn't get matchAll to work in a snippet here, nor in any other demo site. So, I used an old-fashioned regex iterator.

For one solution, we can try matching on the following regex pattern:

\bjob(?=s|\b)|(?<=\bjob)s\b

This will match job, when it appears either as a standalone word, or when it appears as a substring of jobs. The above alternation also matches the letter s when preceded by job. Then, as we iterate, we replace the s match with jobs, to get the final result.

var input = "The jobs report came out and it was a great job!";
var matches = [];
var counter = 0;
var re = /\bjob(?=s|\b)|(?<=\bjob)s\b/g;
var m;

do {
    m = re.exec(input);
    if (m) {
        matches[counter++] = m[0] === "job" ? "job" : "jobs";
    }
} while (m);

console.log(matches);

You could also match either job or jobs by making the s optional.

Then afterwards loop the array, and if the string equals jobs, then add another job.

let str = "The jobs report came out and it was a great job!";
const res = str.match(/\bjobs?\b/gi);
res.forEach(s => {
  if (s.toLowerCase() === "jobs") {
    res.push("job");
  }
});
console.log(res);

Related