1

Having the following array of objects:

const variables = [
  { name: '%NAME%', value: 'joe' },
  { name: '%EMAIL%', value: '%NAME%@mail.com' },
  { name: '%HOBBY%', value: 'tennis' }
];

And the input string:

const inputString = `Hi, my name is %NAME%, I like %HOBBY%, you can contact me at %EMAIL%`;

The function should take as arguments variables and inputString and return the following sting:

'Hi, my name is joe, I like tennis, you can contact me at [email protected]'

Here is the function so far:

function doMagic(variables, inputString) {
  let output = inputString;
  for (let i = 0; i < variables.length; i++) {
     output = inputString.replace(variables[i].name, variables[i].value);
  }
  return output;
}

Unfortunatelly, this only finds one occurrence, in case there are more, and it doesn't go into nested variables, like %EMAIL%.

Any ideas to improve?

2
  • You got to assign the replaced string back to the original variable, like inputString = inputString.replaceAll(variables[i].name, variables[i].value); and then return inputString; Commented Feb 10, 2022 at 7:50
  • You may want to take a look at tagged templates. Commented Feb 10, 2022 at 7:50

7 Answers 7

1

Looping backwards through the array, and using replaceAll() should do the trick.

const variables = [
  { name: '%NAME%', value: 'joe' },
  { name: '%EMAIL%', value: '%NAME%@mail.com' },
  { name: '%HOBBY%', value: 'tennis' },
];

const inputString = `Hi, my name is %NAME%', I like %HOBBY%, you can contact me at %EMAIL%`;

function doMagic(variables, inputString) {
  let output = inputString;
  variables.slice().reverse().forEach(variable => {
    output = output.replaceAll(variable.name, variable.value);
  })
  
  return output;  
}

console.log(doMagic(variables, inputString));

Using regex and while(regex.test.output()) also does the trick.

const variables = [
  { name: '%NAME%', value: 'joe' },
  { name: '%EMAIL%', value: '%NAME%@mail.com' },
  { name: '%HOBBY%', value: 'tennis' },
];

const inputString = `Hi, my name is %NAME%', I like %HOBBY%, you can contact me at %EMAIL%`;

function doMagic(variables, inputString) {
  const regex = new RegExp("(" + variables.map(x => x.name).join("|") + ")", "g");
  let output = inputString;
  while (regex.test(output)) {
    output = output.replace(regex, m => {
      return variables.filter(x => x.name == m)[0].value;
    })
  }    
  
  return output;  
}

console.log(doMagic(variables, inputString));

Sign up to request clarification or add additional context in comments.

2 Comments

Repeated calls to replaceAll() may be lead to suboptimal performance. In addition, this does replace calls for everything in the input map, when in fact the input may not contain all entries.
Okay. Thanks for the feedback. I also added another solution, inspired by your regex approach.
1

You could do a recursive call until there are no variables left in the string

const variables = [
  { name: '%NAME%', value: 'joe' },
  { name: '%EMAIL%', value: '%NAME%@mail.com' },
  { name: '%HOBBY%', value: 'tennis' },
];

const inputString = `Hi, my name is %NAME%', I like %HOBBY%, you can contact me at %EMAIL%`;

function doMagic(variables, inputString) {
  let output = inputString;
  for (let i = 0; i < variables.length; i++) {
    output = output.replace(variables[i].name, variables[i].value);
  }
  for (let i = 0; i < variables.length; i++) {
    if (output.includes(variables[i].name)) {
      output = doMagic(variables, output);
    }
  }
  return output;
}

console.log(doMagic(variables, inputString));

Comments

1

We can do a regex replacement with the help of a callback function:

var variables = [
                   {name: '%NAME%', value: 'joe'},
                   {name: '%EMAIL%', value: '[email protected]'},
                   {name: '%HOBBY%', value: 'tennis'}
                  ];
var regex = new RegExp("(" + variables.map(x => x.name).join("|") + ")", "g");
var inputString = "Hi, my name is %NAME%, I like %HOBBY%, you can contact me at %EMAIL%";
var output = inputString.replace(regex, (m) => {
    return variables.filter(x => x.name == m)[0].value;
});
console.log(output);

The strategy here is to first build a regex alternation of names from the map. We do a global regex search for these names in the input string. For each match, the callback function replaces the name with the value from the map.

1 Comment

This only works because the value of %EMAIL% is edited in the variables array.
0

Issues.

  • You have not updated inputString after string replace.

I have made use of below logic.

  • Loop through variables first.
  • Replace the values if any of the value consist of node from variable array itself (in our case replace %NAME%@mail.com as [email protected])
  • Use this replaced array to perform replacement of string values from inputString.
  • Use replaceAll instead of replace to handle multiple ovvurance of same variable.

const variables = [
  { name: '%NAME%', value: 'joe' },
  { name: '%EMAIL%', value: '%NAME%@mail.com' },
  { name: '%HOBBY%', value: 'tennis' }
];
const inputString = `Hi, my name is %NAME%', I like %HOBBY%, you can contact me at %EMAIL%`;
function doMagic(variables, inputString) {
  let output = inputString;
  // First replace variables
  for (let i = 0; i < variables.length; i++) {
    const node = variables.find(item => item.value.includes(variables[i].name));
    if (node) {
      node.value = node.value.replaceAll(variables[i].name, variables[i].value);
    }
  }
  for (let i = 0; i < variables.length; i++) {
    output = output.replaceAll(variables[i].name, variables[i].value);
  }
  return output;
}
console.log(doMagic(variables, inputString))

Comments

0

You can use replaceAll method to replace all the occurrence and loop twice.

function doMagic(variables, inputString) {
  let output = inputString;
  for (let i = 0; i < variables.length; i++) {
     output = output.replaceAll(variables[i].name, variables[i].value);
  }
  for (let i = 0; i < variables.length; i++) {
     output = output.replaceAll(variables[i].name, variables[i].value);
  }
  return output;
}

3 Comments

it still doesn't replace %EMAIL%
Do you need to change '%NAME%@mail.com' again?
No need for replaceAll() with this approach. replace() would be sufficient since you're looping twice
0

My solution with use "reduce"

const variables = [
  { name: "%NAME%", value: "joe" },
  { name: "%EMAIL%", value: "%NAME%@mail.com" },
  { name: "%HOBBY%", value: "tennis" }
];
const inputString =
  "Hi, my name is %NAME%, I like %HOBBY%, you can contact me at %EMAIL%";

function doMagic(string, variables) {
  const replaceVar = (s, v) =>
    v.reduce((prev = "", { name, value }) => prev.replace(name, value), s);
  const fixedVariables = variables.map((v) => ({
    ...v,
    value: replaceVar(v.value, variables)
  }));

  return replaceVar(string, fixedVariables);
}

console.log(doMagic(inputString, variables));

Also if you want, you can modify my code to prototype, like this:

const inputString =
  "Hi, my name is %NAME%, I like %HOBBY%, you can contact me at %EMAIL%";

String.prototype.doMagic = function (variables) {
  const replaceVar = (s, v) =>
    v.reduce((prev = "", { name, value }) => prev.replace(name, value), s);
  const fixedVariables = variables.map(({ value, ...rest }) => ({
    ...rest,
    value: replaceVar(value, variables)
  }));

  return replaceVar(this, fixedVariables);
};


console.log(
  inputString.doMagic([
    { name: "%NAME%", value: "joe" },
    { name: "%EMAIL%", value: "%NAME%@mail.com" },
    { name: "%HOBBY%", value: "tennis" }
  ])
)

Comments

0

Bit lengthy version, But if you need to do in one iteration, iterate over each character and when you get second % (means ending) then look for replacement of string.

const variables = [
  { name: "%NAME%", value: "joe" },
  { name: "%EMAIL%", value: "%NAME%@mail.com" },
  { name: "%HOBBY%", value: "tennis" },
];

const inputString = `Hi, my name is %NAME%, I like %HOBBY%, you can contact me at %EMAIL%`;

const cache = variables.reduce((acc, { name, value }) => ({
  ...acc,
  [name]: value,
}), {});

const myReplace = (str) => {
  let newString = "";
  let count = 0;
  let last = 0;
  let i;
  
  for (i = 0; i < str.length; i++) {
    if (str[i] === "%") {
      if (count === 0) {
        newString = `${newString}${str.slice(last, i)}`;
        count += 1;
        last = i;
      } else {
        newString = `${newString}${myReplace(cache[str.slice(last, i + 1)])}`;
        count = 0;
        last = i + 1;
      }
    }
  }
  newString = `${newString}${str.slice(last, i)}`;
  return newString;
};

console.log(myReplace(inputString))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.