1

In the code that I develop and maintain I have ran into an issue.

I have a function that takes a query (type string) and replaces substrings of that string with a different string. As an example if a user types in the string I have a cat it would replace it with I have a dog.

My code works but the problem is I have hundreds of such substrings that need to be replaced with a different one. It also looks really bad aesthetically.

var myString;
myString = myString.replace('cat','dog')
                   .replace('elephant','zebra')
                   .replace('bird','fish')
                   // Goes on for hundreds of lines

This is all inside a function in which everytime it's called it goes through hundreds of replace calls.

One thing I could try doing is creating an array of objects and iterating through it. The code would look something like this.

var animalsArray = [
                       {'a':'cat','b':'dog'},
                       {'a':'elephant','b':'zebra'},
                       {'a':'bird','b':'fish'}
                   ];

And then in my function

function stringReplace(string) {
    for (var i = 0; i < animalsArray.length; i++) {
        if (string.indexOf(animalsArray[i]['a']) > -1) {
            sting = string.replace(animalsArray[i]['a'],animalsArray[i]['b']);
        }
    }
}

But I'm not sure whether that would improve on my current practice of chaining together hundreds of replace calls.

I'm basically looking to optimize my current code. What is the best practice?

3 Answers3

1

You can make a regaulr expression with a bunch of or statements. (dog|elephant|bird|....) and that will allow you to run one check. The replace gives you the matched text which you can use to look up the word to replace.

So make an object of the strings to replace and their value is the word to replace. You can just look up the replacement by the matched key.

const animals = {
  cat: 'dog',
  elephant: 'zebra',
  bird: 'fish',
}

// build the or sting with a simple join
const keysString = Object.keys(animals).join("|")
// generate the regular expression to match the words
var animalRE = new RegExp(`\\b(${keysString})\\b`,'g');

// a sample string to match
const myString = "I like to pet a cat that looks like an elephant that moves like a bird."

// the replacement step. The function takes the matched text (aka key) and looks it up in the object
const updated = myString.replace(animalRE, key => animals[key] || key)

// display the new string
console.log(updated)
epascarello
  • 204,599
  • 20
  • 195
  • 236
  • Nice solution! As long as the words (or phrases) doesn't contain any special regex characters it will work fine. I did something similar a couple of months ago, and did some profiling comparing Object and Map, and a map was much faster (at least in Chrome and Node, the only environments I tested) – some Mar 10 '20 at 23:44
0

I'd consider using an object instead, whose keys are the substrings to be replaced, and whose values are the replacements. Make a regular expression by alternating all of the object's keys, then use a replacer function to look up the associated value on the object:

const replacements = {
  cat: 'dog',
  elephant: 'zebra',
  bird: 'fish'
};
const pattern = new RegExp(Object.keys(replacements).join('|'), 'g');
console.log('Foo cat bar bird'.replace(pattern, match => replacements[match]));

Using object syntax makes it easy to add/remove items. If you want to make it even easier to modify, you could consider putting the replacement information into a string instead, then parsing it into an object:

const replacementsStr = `
cat        dog
elephant   zebra
bird       fish
`;

const replacements = Object.fromEntries(
  replacementsStr
    .trim()
    .split('\n')
    .map(line => line.split(/\s+/))
);
const pattern = new RegExp(Object.keys(replacements).join('|'), 'g');
console.log('Foo cat bar bird'.replace(pattern, match => replacements[match]));
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320
  • Obviously `RegExp` has been highly optimized in a browser but I wonder how a solution with `indexOf` and `substring` might perform against it albeit with more code. See also https://stackoverflow.com/questions/5296268/fastest-way-to-check-a-string-contain-another-substring-in-javascript – Jay Mar 10 '20 at 23:02
  • Using `indexOf` would require calling `indexOf` for *every* possible replacement, and for *every* possible character index combination for each of those possible replacements (eg, for 'cat', indicies 0-2, then indicies 1-3, then indicies 2-4, etc. If a match is found, do the same thing again, until no more matches are found. Then do the same thing for the next replacement, of which there are hundreds). Probably less efficient and more messy. – CertainPerformance Mar 10 '20 at 23:05
0

I would do something like:

function WordSwapper(sentence){
  const swapper = sentence;
  this.swaps = [];
  this.add = (word, replacer)=>{
    this.swaps.push([word, replacer]);
    return this;
  }
  this.swap = (sentence = null)=>{
    let s = sentence === null ? swapper : sentence;
    this.swaps.forEach(a=>{
      s = s.replace(new RegExp(a[0], 'gi'), a[1]);
    });
    return s;
  }
}
const ws = new WordSwapper('The cat plays. Elephants live in the Serengeti. Have you ever seen a bird fly?');
ws.add('cat', 'dog').add('elephant', 'zebra').add('bird', 'fish');
console.log(ws.swap());

Of course, you may want to do something with plural and uppercase situations.

StackSlave
  • 10,613
  • 2
  • 18
  • 35
  • I really appreciate the response. Upper Case and plurals would be pretty easy but that's outside the scope of this question. – Funny Mouse Mar 10 '20 at 23:58