Replace multiple strings with multiple other strings

368,083

Solution 1

As an answer to:

looking for an up-to-date answer

If you are using "words" as in your current example, you might extend the answer of Ben McCormick using a non capture group and add word boundaries \b at the left and at the right to prevent partial matches.

\b(?:cathy|cat|catch)\b
  • \b A word boundary to prevent a partial match
  • (?: Non capture group
    • cathy|cat|catch match one of the alternatives
  • ) Close non capture group
  • \b A word boundary to prevent a partial match

Example for the original question:

let str = "I have a cat, a dog, and a goat.";
const mapObj = {
  cat: "dog",
  dog: "goat",
  goat: "cat"
};
str = str.replace(/\b(?:cat|dog|goat)\b/gi, matched => mapObj[matched]);
console.log(str);

Example for the example in the comments that not seems to be working well:

let str = "I have a cat, a catch, and a cathy.";
const mapObj = {
  cathy: "cat",
  cat: "catch",
  catch: "cathy"

};
str = str.replace(/\b(?:cathy|cat|catch)\b/gi, matched => mapObj[matched]);
console.log(str);

Solution 2

Specific Solution

You can use a function to replace each one.

var str = "I have a cat, a dog, and a goat.";
var mapObj = {
   cat:"dog",
   dog:"goat",
   goat:"cat"
};
str = str.replace(/cat|dog|goat/gi, function(matched){
  return mapObj[matched];
});

jsfiddle example

Generalizing it

If you want to dynamically maintain the regex and just add future exchanges to the map, you can do this

new RegExp(Object.keys(mapObj).join("|"),"gi"); 

to generate the regex. So then it would look like this

var mapObj = {cat:"dog",dog:"goat",goat:"cat"};

var re = new RegExp(Object.keys(mapObj).join("|"),"gi");
str = str.replace(re, function(matched){
  return mapObj[matched];
});

And to add or change any more replacements you could just edit the map. 

fiddle with dynamic regex

Making it Reusable

If you want this to be a general pattern you could pull this out to a function like this

function replaceAll(str,mapObj){
    var re = new RegExp(Object.keys(mapObj).join("|"),"gi");

    return str.replace(re, function(matched){
        return mapObj[matched.toLowerCase()];
    });
}

So then you could just pass the str and a map of the replacements you want to the function and it would return the transformed string.

fiddle with function

To ensure Object.keys works in older browsers, add a polyfill eg from MDN or Es5.

Solution 3

Use numbered items to prevent replacing again. eg

let str = "I have a %1, a %2, and a %3";
let pets = ["dog","cat", "goat"];

then

str.replace(/%(\d+)/g, (_, n) => pets[+n-1])

How it works:- %\d+ finds the numbers which come after a %. The brackets capture the number.

This number (as a string) is the 2nd parameter, n, to the lambda function.

The +n-1 converts the string to the number then 1 is subtracted to index the pets array.

The %number is then replaced with the string at the array index.

The /g causes the lambda function to be called repeatedly with each number which is then replaced with a string from the array.

In modern JavaScript:-

replace_n=(str,...ns)=>str.replace(/%(\d+)/g,(_,n)=>ns[n-1])

Solution 4

This may not meet your exact need in this instance, but I've found this a useful way to replace multiple parameters in strings, as a general solution. It will replace all instances of the parameters, no matter how many times they are referenced:

String.prototype.fmt = function (hash) {
        var string = this, key; for (key in hash) string = string.replace(new RegExp('\\{' + key + '\\}', 'gm'), hash[key]); return string
}

You would invoke it as follows:

var person = '{title} {first} {last}'.fmt({ title: 'Agent', first: 'Jack', last: 'Bauer' });
// person = 'Agent Jack Bauer'

Solution 5

using Array.prototype.reduce():

const arrayOfObjects = [
  { plants: 'men' },
  { smart:'dumb' },
  { peace: 'war' }
]
const sentence = 'plants are smart'

arrayOfObjects.reduce(
  (f, s) => `${f}`.replace(Object.keys(s)[0], s[Object.keys(s)[0]]), sentence
)

// as a reusable function
const replaceManyStr = (obj, sentence) => obj.reduce((f, s) => `${f}`.replace(Object.keys(s)[0], s[Object.keys(s)[0]]), sentence)

const result = replaceManyStr(arrayOfObjects , sentence1)

Example

// /////////////    1. replacing using reduce and objects

// arrayOfObjects.reduce((f, s) => `${f}`.replace(Object.keys(s)[0], s[Object.keys(s)[0]]), sentence)

// replaces the key in object with its value if found in the sentence
// doesn't break if words aren't found

// Example

const arrayOfObjects = [
  { plants: 'men' },
  { smart:'dumb' },
  { peace: 'war' }
]
const sentence1 = 'plants are smart'
const result1 = arrayOfObjects.reduce((f, s) => `${f}`.replace(Object.keys(s)[0], s[Object.keys(s)[0]]), sentence1)

console.log(result1)

// result1: 
// men are dumb


// Extra: string insertion python style with an array of words and indexes

// usage

// arrayOfWords.reduce((f, s, i) => `${f}`.replace(`{${i}}`, s), sentence)

// where arrayOfWords has words you want to insert in sentence

// Example

// replaces as many words in the sentence as are defined in the arrayOfWords
// use python type {0}, {1} etc notation

// five to replace
const sentence2 = '{0} is {1} and {2} are {3} every {5}'

// but four in array? doesn't break
const words2 = ['man','dumb','plants','smart']

// what happens ?
const result2 = words2.reduce((f, s, i) => `${f}`.replace(`{${i}}`, s), sentence2)

console.log(result2)

// result2: 
// man is dumb and plants are smart every {5}

// replaces as many words as are defined in the array
// three to replace
const sentence3 = '{0} is {1} and {2}'

// but five in array
const words3 = ['man','dumb','plant','smart']

// what happens ? doesn't break
const result3 = words3.reduce((f, s, i) => `${f}`.replace(`{${i}}`, s), sentence3)

console.log(result3)

// result3: 
// man is dumb and plants
Share:
368,083

Related videos on Youtube

Anderson Green
Author by

Anderson Green

I write source-to-source compilers in JavaScript using Peggyjs. I also write compilers in Prolog. For reference, I also have a list of source-to-source compilers on GitHub.

Updated on December 15, 2021

Comments

  • Anderson Green
    Anderson Green over 2 years

    I'm trying to replace multiple words in a string with multiple other words. The string is "I have a cat, a dog, and a goat."

    However, this does not produce "I have a dog, a goat, and a cat", but instead it produces "I have a cat, a cat, and a cat". Is it possible to replace multiple strings with multiple other strings at the same time in JavaScript, so that the correct result will be produced?

    var str = "I have a cat, a dog, and a goat.";
    str = str.replace(/cat/gi, "dog");
    str = str.replace(/dog/gi, "goat");
    str = str.replace(/goat/gi, "cat");
    
    //this produces "I have a cat, a cat, and a cat"
    //but I wanted to produce the string "I have a dog, a goat, and a cat".
    
    • Anderson Green
      Anderson Green about 11 years
      I want to replace multiple words in a string with multiple other words, without replacing words that have already been replaced.
    • Prasanna
      Prasanna over 5 years
      i've some different query, what if i dnt know user is going to enter cat or dog or goat(this is randomly coming) but whenever this kinda word will come i need to replace with let's say 'animal'. how to get this scenario
    • Anderson Green
      Anderson Green almost 3 years
      The top-voted answer to this question seems to be incorrect: it sometimes replaces the strings in the wrong order.
    • Guerric P
      Guerric P almost 3 years
      @AndersonGreen in your example why cat should not match the cat part of catch? You should precise the match criteria.
    • Anderson Green
      Anderson Green almost 3 years
      @GuerricP I need to match and replace every string when possible. In this case, the word "catch" doesn't get matched at all, since the word "cat" appears first in the regex.
    • Guerric P
      Guerric P almost 3 years
      In this case just surround every word with \b in the regex: jsfiddle.net/rkc52a0u
  • Anderson Green
    Anderson Green almost 11 years
    I'm not sure if I could use this function to replace all types of strings, since the characters that are allowed in JavaScript strings are not the same as the characters that are allowed in JavaScript identifiers (such as the keys that are used here).
  • Ben McCormick
    Ben McCormick almost 11 years
    you can use an arbitrary string in as a javascript property. Shouldn't matter. You just can't use the . notation with all such properties. Brackets notation works with any string.
  • Sygmoral
    Sygmoral over 10 years
    It indeed works great. I am succesfully using this solution (the 'specific' one) to change English number notations into European ones (24,973.56 to 24.973,56), using map={'.': ',', ',': '.'} and regex /\.|,/g.
  • Robert Brisita
    Robert Brisita over 10 years
    Just wanted to add the functionality to replace with empty strings: return mapObj[matched] ? mapObj[matched] : '';
  • bonbon.langes
    bonbon.langes over 10 years
    i think there's a bug related to it since it only does the replacement once. check out this fiddle. jsfiddle.net/L6LB2/33
  • Ben McCormick
    Ben McCormick over 10 years
    @bonbon.langes it works for multiple selections, it does fail for uppercase words though. Fixed here: jsfiddle.net/L6LB2/35
  • Michal Moravcik
    Michal Moravcik over 9 years
    I love this solution but I had to replace return mapObj[matched.toLowerCase()]; to just return mapObj[matched]; since I use case sensitive keys in mapObj.
  • pliski
    pliski about 9 years
    Smart! I moved out the regexp definition cause in my case the map it doesn't change. Here is my implementation: fiddle
  • Kyle
    Kyle over 8 years
    Oh that's awesome, using an object to solve the problems makes everything easier, I wasn't expecting for that +1
  • MiniScalope
    MiniScalope almost 7 years
    according to jsperf this solution is a lot slower than chaining .replace() calls
  • fregante
    fregante almost 7 years
  • Ben McCormick
    Ben McCormick over 6 years
    @MiniScalope Chaining replace calls can cause bugs if you're swapping 2 words, since if you made all dogs cats then made all cats dogs, every word that was originally dogs or cats would end up as dogs
  • Devon
    Devon over 6 years
    // worthless: replaceAll("I have a cat, a dog, and a goat.", { cat:"dog", dog:"goat", goat:"cat" }) // produces: "I have a cat, a cat, and a cat."
  • Anderson Green
    Anderson Green over 6 years
    Does this solution require JQuery?
  • Billu
    Billu over 6 years
    javascript tag added in question, and jquery is a libarary of javascript.
  • Traxo
    Traxo about 6 years
    @Super javascript tag added in question, and jquery is a libarary of javascript. Hmmm that logic is off, it should be other way round, and also just a heads up - from javascript tag info: "Unless another tag for a framework/library is also included, a pure JavaScript answer is expected."
  • Billu
    Billu about 6 years
    @Anderson Green, yes jquery needed for above script.
  • Eric Hepperle - CodeSlayer2010
    Eric Hepperle - CodeSlayer2010 almost 6 years
    Interesting. Can you explain the logic in the replace function?
  • 牛さん
    牛さん over 5 years
    Not working if the input is dog,doge. Only the first one in the RegExp will match. That is to say dog|doge will match dog while doge|dog will fail dog
  • klodoma
    klodoma about 5 years
    this is a BUG: return mapObj[matched.toLowerCase()]; >> .toLowerCase() not required
  • LittleBobbyTables - Au Revoir
    LittleBobbyTables - Au Revoir almost 5 years
    The OP asked "Is it possible to replace multiple strings with multiple other strings at the same time". This is three separate steps.
  • Billu
    Billu almost 5 years
    @ Traxo, in most web applications we are using framework (bootstrap/google material). Jquery includes in all modern frameworks. So, jquery is necessary item for web applications.
  • Billu
    Billu almost 5 years
    @AndersonGreen yes jquery required
  • Roemer
    Roemer almost 5 years
    Doesn't work if the string contains regex characters.
  • robsch
    robsch almost 5 years
    You may want to escape the keys for the regular expression: Object.keys(mapObj).map(key => key.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')).join('|'). Inspired by this answer
  • David Spector
    David Spector over 4 years
    About "don't extend...": I extended my String to compare two strings for equality, case-insensitive. This functionality is not provided by String, but it might be someday, which could cause my apps to break. Is there a way to "subclass" or "extend" String to include such a function safely, or should I simply define a new two-argument function as part of my app library?
  • David Spector
    David Spector over 4 years
    Best answer. But is there a reason to use ${f} instead of f for the accumulating value?
  • David Spector
    David Spector over 4 years
    If you want ALL of the given strings to be replaced, not just the first, add the g flag: "const result1 = arrayOfObjects.reduce((f, s) => ${f}.replace(new RegExp(Object.keys(s)[0],'g'), s[Object.keys(s)[0]]), sentence1)"
  • Art3mix
    Art3mix over 4 years
    @SuperModel There is nothing wrong with providing jQuery solution because of how often it is still used, but saying that: jquery is necessary item for web applications is just wrong.
  • KARTHIKEYAN.A
    KARTHIKEYAN.A over 4 years
    If you not aware skip, but don't say it is the wrong answer. My case "{"a":1,"b":2}" like that is there I used to replace the above way. If it helps others if they want for other the answer is not for you only. @Carr
  • Carr
    Carr over 4 years
    Again, you just provided a meaningless answer, what you do is the asker already can do in the question, this answer will mislead people that they may think new and utilize the RegExp object could solve the problem
  • Carr
    Carr over 4 years
    In this case, you still have the same problem as asker's question when you do var i = new RegExp('}','g'), j = new RegExp('{','g'), k = data.replace(i,'{').replace(j,'}');
  • Billu
    Billu over 4 years
    @ Art3mix ok, but I think, Jquery plays a key role in big modern web application to save your time. Using jquery, you can achieve: Write less, do more.
  • Vishnu Prassad
    Vishnu Prassad about 4 years
    This package is amazing :) Works exactly as i expected
  • Slavik Meltser
    Slavik Meltser almost 3 years
    This will not work correctly for the text I have a %11, a %21, and a %31, once we need the final result I have a dog1, a cat1, and a goat1.
  • Slavik Meltser
    Slavik Meltser almost 3 years
    This works only if your keys not include any special regex characters. Otherwise, it will return error or worse, an unexpected behavior. E.g: 'abc'.fmt({'a(b':1})
  • Anderson Green
    Anderson Green almost 3 years
    What is "dynamically provided mapping?"
  • Anderson Green
    Anderson Green almost 3 years
    This solution won't always work if the replaced strings overlap: see here, for example.
  • Anderson Green
    Anderson Green almost 3 years
    This is a different problem. I wanted to replace the strings without adding delimiters.
  • Slavik Meltser
    Slavik Meltser almost 3 years
    "dynamically provided mapping" is an unknown content of keys and value and in unknown quantity during the development process. Such information can be known mostly during runtime. E.g. user input.
  • Anderson Green
    Anderson Green almost 3 years
    Unfortunately, this package can still replace the strings in the wrong order.
  • Anderson Green
    Anderson Green almost 3 years
    This doesn't solve my problem: it only replaces several strings with one other string.
  • Anderson Green
    Anderson Green almost 3 years
    Instead of adding word boundaries, you could also sort the strings in the regex by length, so that the longest strings would be matched first.
  • Casimir et Hippolyte
    Casimir et Hippolyte almost 3 years
    I think your answer is the way to go, but if I were you, I will provide a more general way to do that with /\w+/g as pattern, and with a function that returns the match when the property doesn't exist in your object. Also, instead of a standard object, I think a Map is more appropriate: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… . To finish, the help of toLowerCase() or toUpperCase() can be usefull.
  • Leftium
    Leftium almost 3 years
    @AndersonGreen Sorting the strings will give slightly different results. For example, "cats" will be changed to "catchs." Word boundaries will leave "cats" intact. It depends on the behavior desired.
  • Anderson Green
    Anderson Green almost 3 years
    This mostly works, except in some cases where the keys overlap. It might work better if the keys in the regex were sorted by length (from longest to shortest).
  • Nearoo
    Nearoo almost 3 years
    @AndersonGreen What do you expect this to do? If you're looking to replace "strings" in general, your question applied to your jsfiddle is ambiguous: It's not clear if cathy should turn into cat or catchthy. Do you want to replace the longest string matching, out of all matches you have? Or perhaps precise matches of words, delimited by spaces, comma etc.?
  • Anderson Green
    Anderson Green almost 3 years
    This won't work in cases where the input string contains special regex characters: see here, for example. You also need to escape these characters.
  • Ben McCormick
    Ben McCormick almost 3 years
    @AndersonGreen when you have substrings you just need to be a bit more careful about the ordering. For instance I think this will likely produce the result you're expecting: str = str.replace(/cathy|catch|cat/gi, function(matched){...})
  • Steve Chambers
    Steve Chambers almost 3 years
    Yes, you'd need to escape special characters in the regex if they need to be replaced - like this if the objective is to replace cat) - not sure I understand why this is an issue?
  • Kodie Grantham
    Kodie Grantham almost 3 years
    Thanks for bringing that to my attention @AndersonGreen, I'll get that fixed.
  • Slavik Meltser
    Slavik Meltser almost 3 years
    @AndersonGreen True, you can sort it by size. But it really depends on your use case. Some may prefer to look for shortest first, though. In your case is can be easily solved with Object.keys(mapObj).sort((a,b) => b.length - a.length)
  • Kavvya Ramarathnam
    Kavvya Ramarathnam almost 3 years
    Not necessary since we are trying to replace the string only. splitting is a unnecessary step.
  • Anderson Green
    Anderson Green over 2 years
    To make this solution reusable, you can define a function to replace the strings using the map object.
  • Sophie
    Sophie about 2 years
    Hey, nice solution! Is it possible to adapt to tags? example: (find: "<p><figure><img" and replace for "<figure><img" and another's like this
  • Sophie
    Sophie about 2 years
    Hey, nice solution! Is it possible to adapt to tags? example: (find: "<p><figure><img" and replace for "<figure><img" and another's like this
  • Slavik Meltser
    Slavik Meltser about 2 years
    @Sophie sure, everything is possible, it’s programming :) You can try and support also regex as a replacer in the mapObj - which may help you achieve your goal.
  • Naresh Kumar
    Naresh Kumar almost 2 years
    @AndersonGreen is that case relpace the var find = '{' + para[0] + '}' to var find = para[0]