Search code examples
javascriptregextypescriptstring-matching

Alternative to .replace() replacer function needed for multiple matches in a string


I'm working on a project that does some string parsing (for a song lyrics format) and I ran across a usage of the .replace() method that feels a bit hacky to me... but it does work.

This is how it currently works right now, and it logs out the expected result.

const scan = (str, pattern) => {
  const results = [];

  str.replace(pattern, function() {
    results.push(Array.prototype.slice.call(arguments, 1, -2));
  });

  return _.flatten(results)
}

console.log("Lyrics", scan(
  "I don't see! a bad, moon a-rising. (a-rising)",
  /(\[[\w#b/]+])?([\w\s',.!()_\-"]*)/gi
));

console.log("Chords", scan(
  "[D] [D/F#] [C] [A7]",
  /\[([\w#b+/]+)]?/gi
));

console.log("Measures", scan(
  `
# Instrumental

| [A] [B] | [C] | [D] [E] [F] [G] |
`,
  /([[\w#b/\]+\]\s]+)[|]*/gi
));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.21/lodash.min.js" integrity="sha512-WFN04846sdKMIP5LKNphMaWzU7YpMyCU245etK3g/2ARYbPK9Ub18eG+ljU96qKRCWh+quCY7yefSmlkQw1ANQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

The usage of .replace() feels wrong since it's not actually doing any replacing but relying on the looping behavior to push items into an array. That returns an array with a single item, of an array of strings. Lodash is then used to flatten it out into a single string array.

I feel like this could be improved to use .matchAll() instead, but I haven't been able to get it quite right just yet. Here's what I have so far, which works but the output is wrong since it's not identical to the above example.

const scan = (str, pattern) => {
  return [...str.matchAll(pattern)].map(r=>{
    return r[1]
});
}

console.log("Lyrics", scan(
  "I don't see! a bad, moon a-rising. (a-rising)",
  /(\[[\w#b/]+])?([\w\s',.!()_\-"]*)/gi
));

console.log("Chords", scan(
  "[D] [D/F#] [C] [A7]",
  /\[([\w#b+/]+)]?/gi
));

console.log("Measures", scan(
  `
# Instrumental

| [A] [B] | [C] | [D] [E] [F] [G] |
`,
  /([[\w#b/\]+\]\s]+)[|]*/gi
));

Is .matchAll() the correct alternative to .replace() in this case? Is there a better way to get the desired output instead of what I am currently doing? I know I'm currently not calling the .slice() method that the first example has yet because I'm not entirely sure I understand what this is trying to accomplish here since this code was not originally written by me.


Solution

  • You can use flatMap and slice.

    const pattern = /(\[[\w#b/]+])?([\w\s',.!()_\-"]*)/gi;
    const str = "I don't see! a bad, moon a-rising. (a-rising)";
    
    const results =  [...str.matchAll(pattern)].flatMap(m => m.slice(1));
    
    console.log('results', results)