Replace multiple strings with multiple other strings
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];
});
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.
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.
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
Related videos on Youtube
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, 2021Comments
-
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 about 11 yearsI want to replace multiple words in a string with multiple other words, without replacing words that have already been replaced.
-
Prasanna over 5 yearsi'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 almost 3 yearsThe top-voted answer to this question seems to be incorrect: it sometimes replaces the strings in the wrong order.
-
Guerric P almost 3 years@AndersonGreen in your example why
cat
should not match thecat
part ofcatch
? You should precise the match criteria. -
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 almost 3 yearsIn this case just surround every word with
\b
in the regex: jsfiddle.net/rkc52a0u
-
-
Anderson Green almost 11 yearsI'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 almost 11 yearsyou 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 over 10 yearsIt 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 over 10 yearsJust wanted to add the functionality to replace with empty strings: return mapObj[matched] ? mapObj[matched] : '';
-
bonbon.langes over 10 yearsi 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 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 over 9 yearsI love this solution but I had to replace
return mapObj[matched.toLowerCase()];
to justreturn mapObj[matched];
since I use case sensitive keys inmapObj
. -
pliski about 9 yearsSmart! I moved out the regexp definition cause in my case the map it doesn't change. Here is my implementation: fiddle
-
Kyle over 8 yearsOh that's awesome, using an object to solve the problems makes everything easier, I wasn't expecting for that +1
-
MiniScalope almost 7 yearsaccording to jsperf this solution is a lot slower than chaining .replace() calls
-
fregante almost 7 years
-
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 allcats
dogs
, every word that was originallydogs
orcats
would end up asdogs
-
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 over 6 yearsDoes this solution require JQuery?
-
Billu over 6 yearsjavascript tag added in question, and jquery is a libarary of javascript.
-
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 about 6 years@Anderson Green, yes jquery needed for above script.
-
Eric Hepperle - CodeSlayer2010 almost 6 yearsInteresting. Can you explain the logic in the replace function?
-
牛さん over 5 yearsNot working if the input is
dog
,doge
. Only the first one in the RegExp will match. That is to saydog|doge
will matchdog
whiledoge|dog
will faildog
-
klodoma about 5 yearsthis is a BUG: return mapObj[matched.toLowerCase()]; >> .toLowerCase() not required
-
LittleBobbyTables - Au Revoir almost 5 yearsThe OP asked "Is it possible to replace multiple strings with multiple other strings at the same time". This is three separate steps.
-
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 almost 5 years@AndersonGreen yes jquery required
-
Roemer almost 5 yearsDoesn't work if the string contains regex characters.
-
robsch almost 5 yearsYou 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 over 4 yearsAbout "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 over 4 yearsBest answer. But is there a reason to use
${f}
instead of f for the accumulating value? -
David Spector over 4 yearsIf 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 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 over 4 yearsIf 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 over 4 yearsAgain, 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 over 4 yearsIn 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 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 about 4 yearsThis package is amazing :) Works exactly as i expected
-
Slavik Meltser almost 3 yearsThis will not work correctly for the text
I have a %11, a %21, and a %31
, once we need the final resultI have a dog1, a cat1, and a goat1
. -
Slavik Meltser almost 3 yearsThis 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 almost 3 yearsWhat is "dynamically provided mapping?"
-
Anderson Green almost 3 yearsThis solution won't always work if the replaced strings overlap: see here, for example.
-
Anderson Green almost 3 yearsThis is a different problem. I wanted to replace the strings without adding delimiters.
-
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 almost 3 yearsUnfortunately, this package can still replace the strings in the wrong order.
-
Anderson Green almost 3 yearsThis doesn't solve my problem: it only replaces several strings with one other string.
-
Anderson Green almost 3 yearsInstead 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 almost 3 yearsI 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 oftoLowerCase()
ortoUpperCase()
can be usefull. -
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 almost 3 yearsThis 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 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 intocat
orcatchthy
. 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 almost 3 yearsThis 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 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 almost 3 yearsYes, 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 almost 3 yearsThanks for bringing that to my attention @AndersonGreen, I'll get that fixed.
-
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 almost 3 yearsNot necessary since we are trying to replace the string only. splitting is a unnecessary step.
-
Anderson Green over 2 yearsTo make this solution reusable, you can define a function to replace the strings using the map object.
-
Sophie about 2 yearsHey, 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 about 2 yearsHey, 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 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 almost 2 years@AndersonGreen is that case relpace the var find = '{' + para[0] + '}' to var find = para[0]