What regular expression to use to replace symbols in a string?

Asked

Viewed 150 times

9

I got a string, just like "1x2,4÷4" and I want to replace the x for *; to , for . and the ÷ for / using the replace javascript.

Which regular expression I would need to use to make this replacement?

For example:

let expression = "5,5x8÷7";

expression = expression.replace('???', '???');
// Deveria estar assim "5.5*8/7"

resolveExpression(expression);

function resolveExpression(exp) {
  // Resolve a expressão
}

4 answers

11


No need for regex, and even if it still needed you would have to use a loop, as it is or while to solve other things.

Create a simple object to map what you want to replace and use String.prototype.replaceAll with string already resolves:

let expression = "5,5x8÷7";
const expressoes = { ',': '.', 'x': '*', '÷': '/' };

for (const [exp, value] of Object.entries(expressoes)) {
  expression = expression.replaceAll(exp, value);
}

console.log(expression);

Of course if the goal is to use this with eval(), it will probably be something "messed up", although it works there are several problems that can occur due to data entry that you can not fully control, the best would be to create something own or use an existing library that already does it, as https://www.npmjs.com/package/mathjs (must have smaller libs that do only the part you want, this does much more, then edit if you find one that really is simple and efficient).


Version ES5:

Like const [...] and replaceAll are more modern, if you want something for old browsers you will need regex yet (you could do with String.prototype.split() also):

var expression = "5,5x8÷7";
var expressoes = { ',': '.', 'x': '*', '÷': '/' };

for (var exp in expressoes) {
  expression = expression.replace(new RegExp(exp, 'g'), expressoes[exp]);
}

console.log(expression);

With split may even work and look faster, but it will depend a little on the size of the input value, in general it was still 3% faster, which is something tiny:

var expression = "5,5x8÷7";
var expressoes = { ',': '.', 'x': '*', '÷': '/' };

for (var exp in expressoes) {
  expression = expression.split(exp).join(expressoes[exp]);
}

console.log(expression);

However the quickest was the suggestion of the other answer, working char to char as you can see in the benchmark: https://jsbench.me/usko4noh4h/5, being on average 55% faster than the others, which to treat several expressions will be quite advantageous in a matter of time:

let output = '';

for (const char of expression) {
  output += char in expressoes ? expressoes[char] : char;
}

An equivalent for ES5 would be (using for "normal"):

var expression = "5,5x8÷7";
var expressoes = { ',': '.', 'x': '*', '÷': '/' };

var output = '';

for (var i = 0, j = expression.length; i < j; i++) {
  var char = expression[i];

  output += char in expressoes ? expressoes[char] : char;
}

console.log(output);

The fastest was with "normal":

for increment apresentou o resultado mais rápido

  • It is worth remembering that the String.prototype.replace, when used with string in the first argument, it does not replace of all occurrences; only the first one. In this type of situation, I would find it preferable to use the String.prototype.replaceAll.

  • @Luizfelipe yes, my fault, missed the "global", adjusted.

  • Then if you want to do the benchmark with the another answer, I’ll be glad to know.

  • 1

    @Luizfelipe very likely to use char will be much more efficient than anything, since it operates within its own value, in any engine, but I already add.

  • I also put an answer - just to serve as an example of what not to do :-) - but I think you don’t even need to put in the benchmark, it’s almost certain that it will be slower... cc @Luizfelipe

  • 1

    @hkotsubo and Luiz, put the "ES5 version" of the code (using "normal") and it was the fastest, being at least 50% more efficient than the others.

Show 1 more comment

7

Only as an alternative to another answer, can solve this problem with just a scan of the string instead of a scan for each token mapped as a result of the use of String.prototype.replaceAll.

As each token has a single character, this task becomes trivial:

const input = '5,5x8÷7';

const tokenMap = {
  ',': '.',
  'x': '*',
  '÷': '/'
};

let output = '';
for (const char of input) {
  output += char in tokenMap ? tokenMap[char] : char;
}

console.log(output);

And for the further analysis of this type of expression, I keep the observation of the colleague warning of the misuse of eval (learn more). Perhaps it is the case to build a small parser to create a syntax tree from the string. From the tree, you can evaluate it by following the order of precedence of the operators.

There are a number of articles about this on the internet. I won’t go into too much detail, but it’s a good exercise.

5

Complementing the other responses - and reinforcing for nay use regex, since solutions proposed by others are more suitable than regular expressions - see how it would be unnecessarily more complicated with regex:

const input = '5,5x8÷7';
const tokenMap = {
  ',': '.',
  'x': '*',
  '÷': '/'
};

const operadores = Object.keys(tokenMap).map(s => "\\" + s);
const r = RegExp(`[${operadores.join('')}]`, 'g');

let output = input.replace(r, function(m) { return tokenMap[m] });
console.log(output);

That is, I build a regex with the operators I want to replace. Since each operator has only one character, I can use one character class, bounded by square brackets.

In the above case, the regex would be [\,\x\÷], ie, is an expression that picks up any of these characters. I added the \ for precaution, in case you have any character that needs escape (none of these need, but if you have any that need, you will be properly escaped).

And in the replace I pick up the operator that was found and exchange for the respective character of the tokenMap. That is, in addition to using a regex - which already has a certain cost, probably higher than the solutions of the other answers - still has the calls of callback being made for each operator found.

In addition to unnecessarily more complicated, it will probably be even slower. But it stays here as a record of what not to do...

4

Still complementing the other answers it is also possible to get the desired output using principles of functional programming.

const input = '5,5x8÷7';
const tokenMap = {
  ',': '.',
  'x': '*',
  '÷': '/'
};
output = [...input].map((c) => (c in tokenMap) ? tokenMap[c] : c).join("");
console.log(output);

The expression [...input].map((c) => (c in tokenMap) ? tokenMap[c] : c).join("") may be interpreted as follows::

  • [...input]: breaks the input in a character array.
  • map((c) => (c in tokenMap) ? tokenMap[c] : c): for each character c an anonymous function is applied which checks whether this character is a key in tokenMap:
    • If yes, do the mapping and return it.
    • If not, just return the character.
  • .join(""): joins the result in a string.

References:

Browser other questions tagged

You are not signed in. Login or sign up in order to post.