DEV Community

SavagePixie
SavagePixie

Posted on • Edited on

Wielding replace with all its power

Regular expressions can be a great help when we need to analyse or transform user input or other strings the precise content of which we don't know until runtime, especially so if we need to work with complex expressions. String's method .replace() is very versatile. We can use it to look for a pattern in the form of a string or a regular expression within a string and return a new one with the pattern replaced.

Up until here, here, this sounds very good but very bland. The exciting part come when one realises that we can pass a function as a parameter to act on each match.

How does that work?

const newStr = str.replace(pattern, replacement)

This is the method's basic syntax. The pattern will be either a string or a regular expression. The replacement will be a string or a function. If it is a function, it can take several arguments:

  • match the first argument is the entire match in the string
  • group next, there will be one argument for each captured group
  • offset the offset of the match within the string (ie, the index in the string where the match starts)
  • string the whole string

What is it useful for?

A while back, I wrote an article explaining how lookaheads helped me turn camelCase into kebab-case. Let us imagine that I want to do just the opposite this time, turn kebab-case into camelCase. One way to do it would be this.

const toCamelCase = str => str.split("-")
          .map((x, i) => i == 0 ? x : x.slice(0,1).toUpperCase() + x.slice(1))
          .join('')

toCamelCase("some-stuff-written-here") //returns someStuffWrittenHere

If I wanted, instead of six methods and a callback function, I could just use two methods and a callback function.

const toCamelCase = str => {
  const regEx = /-(\w)/g
  return str.replace(regEx, (_, letter) => letter.toUpperCase())
}

toCamelCase("some-stuff-written-here") //returns someStuffWrittenHere

The result is the same, but the statement much shorter. I think it is important to note, however, that some not very conclusive tests seem to indicate that the first function is marginally faster for small amounts of text.

Doing some more complex stuff

Now let's imagine that we have a text that includes an expression for a dice roll. We don't know how many dice were rolled or how many sides those dice had until our website gets the data. But we know that the expression will be in the form of xdy where x indicates the amount of dice rolled and y how many sides those dice had. We want a function that returns a new string with the result of the dice roll in it.

The first thing that we need is a function that simulates a dice roll. Let's make something simple.

const rollDice = (dice, sides) => {
  let result = 0
  for (let i = 0; i < dice; i++) result += Math.ceil(Math.random() * sides)
  return result
}

With that out of the way, let's dive into the function that will add the result next to each dice roll:

const addResult = (string, roller) => {
  const regEx = /(\d+)d(\d+)/g
  return string.replace(regEx, (roll, dice, sides) => `${roll} [${roller(dice, sides)}]`)
}

const string = "The first dice roll is 2d6 and the second one is 3d8."

addResult(string, rollDice)
//Returns something like this: "The first dice roll is 2d6 [7] and the second one is 3d8 [17]."

Here we've created a function that takes a string and another function to roll dice. Inside the function, we call .replace() to find dice roll expressions in the string and execute our dice roller for each match, so that the resulting new string includes the result of the roll after the expression.

In this case, our regular expression has two captured groups, each of them consisting of one or more digits ((\d+)). When we call our callback function, we name the first group dice and the second sides. The whole matching expression will be captured in the argument roll. We don't need any of the other arguments that it can take, so we simply omit them.

When it gets really fun

But we can even use .replace() without actually replacing anything in the string. Consider the following example.

const addResult = (string, roller) => {
  const regEx = /(\d+)d(\d+)/g
  const newArr = []
  string.replace(regEx, (roll, dice, sides) => newArr.push({
      roll: roll, 
      result: roller(dice, sides) 
    }))
  return newArr
}

This time, our addResult() returns an array of objects where it has stored all the dice roll expressions and their calculated result without actually doing anything to the original string. This technique is useful when we want to gather data from a string, but we aren't particularly interested in changing it.

Conclusion

Because it can take a function as a parameter, .replace() can be a very useful method when we need to manipulate a text in complex ways or we simply want to gather information from it.

If you want to learn more about it, you can always check MDN web docs. Personally, I also found the chapter dedicated to regular expressions in Secrets of the JavaScript Ninja very enlightening.

Top comments (0)