Task
In a factory a printer prints labels for boxes. For one kind of boxes
the printer has to use colors which, for the sake of simplicity, are
named with letters from a to m.
The colors used by the printer are recorded in a control string. For
example a "good" control string would be aaabbbbhaijjjm meaning that
the printer used three times color a, four times color b, one time
color h then one time color a...
Sometimes there are problems: lack of colors, technical malfunction
and a "bad" control string is produced e.g. aaaxbbbbyyhwawiwjjjwwm
with letters not from a to m.
You have to write a function printer_error which given a string will
output the error rate of the printer as a string representing a
rational whose numerator is the number of errors and the denominator
the length of the control string. Don't reduce this fraction to a
simpler expression.
The string has a length greater or equal to one and contains only
letters from ato z.
Examples:
s="aaabbbbhaijjjm"
error_printer(s) => "0/14"
s="aaaxbbbbyyhwawiwjjjwwm"
error_printer(s) => "8/22"
My Attempt
Ok I am super sorry for the long code (it could probably be a lot shorter):
function printerError(s){
let regex = /[a-m]/g
let winCount = 0;
let totalCount = 0;
s.split('').map((item)=>{
totalCount++;
if(regex.test(item)){
winCount++;
}
})
let a = winCount / totalCount;
let b = (decimal) => {
for(var denominator = 1; (decimal * denominator) % 1 !== 0; denominator++);
return {numerator: decimal * denominator, denominator: denominator};
}
let c = b(a);
let d = Object.values(c);
let e = d.toString();
let regex2 = /,/;
let f = e.replace(regex2, '/');
return f;
};
console.log(printerError('adfsdgdsrwe'));
My Question
The engineer is broken. Please help me find the error that is causing the output to be way off!
Like already mentioned in the comments, you misunderstood the task and let yourself be confused by the denominator stuff. That way it's hard to 'fix' your code. You only need to identify the number of wrong letters and the length of the string. The idea using a regex was nice. I would suggest you do it the other way around and count the number of mismatching letters.
let error_printer = (s) => {
// Count all matches, which match the regexp which is looking for letters,
// which are NOT within a-m. If no are found, just use 0.
// Then append / with the length of the string.
return (s.match(/[^a-m]/g)?.length || 0) + "/" + s.length;
}
let s="aaabbbbhaijjjm"
console.log(error_printer(s)) // => "0/14"
s="aaaxbbbbyyhwawiwjjjwwm"
console.log(error_printer(s)); // => "8/22"
EDIT: I've just seen that #Aks Jacoves did a quite similar approach in the comments using codepen.
My purpose is to punch multiple strings into a single (shortest) string that will contain all the character of each string in a forward direction. The question is not specific to any language, but more into the algorithm part. (probably will implement it in a node server, so tagging nodejs/javascript).
So, to explain the problem:
Let's consider I have few strings
["jack", "apple", "maven", "hold", "solid", "mark", "moon", "poor", "spark", "live"]
The Resultant string should be something like:
"sjmachppoalidveonrk"
jack: sjmachppoalidveonrk
apple: sjmachppoalidveonrk
solid: sjmachppoalidveonrk
====================================>>>> all in the forward direction
These all are manual evaluation and the output may not 100% perfect in the example.
So, the point is all the letters of each string have to exist in the output in
FORWARD DIRECTION (here the actual problem belongs), and possibly the server will send the final strings and numbers like 27594 will be generated and passed to extract the token, in the required end. If I have to punch it in a minimal possible string it would have much easier (That case only unique chars are enough). But in this case there are some points:
Letters can be present multiple time, though I have to reuse any
letter if possible, eg: for solid and hold o > l > d can be
reused as forward direction but for apple (a > p) and spark
(p > a) we have to repeat a as in one case it appears before p
for apple, and after p for sparks so either we need to repeat
a or p. Even, we cannot do p > a > p as it will not cover both the case
because we need two p after a for apple
We directly have no option to place a single p and use the same
index twice in a time of extract, we need multiple p with no option
left as the input string contains that
I am (not) sure, that there is multiple outputs possible for a set of
strings. but the concern is it should be minimal in length,
the combination doesn't matter if its cover all the tokens in a forward direction. all (or one ) outputs of minimal possible length
need to trace.
Adding this point as an EDIT to this post. After reading the comments and knowing that it's already an existing
problem is known as shortest common supersequence problem we can
define that the resultant string will be the shortest possible
string from which we can re generate any input string by simply
removing some (0 to N) chars, this is same as all inputs can be found in a forward direction in the resultant string.
I have tried, by starting with an arbitrary string, and then made an analysis of next string and splitting all the letters, and place them accordingly, but after some times, it seems that current string letters can be placed in a better way, If the last string's (or a previous string's) letters were placed according to the current string. But again that string was analysed and placed based on something (multiple) what was processed, and placing something in the favor of something that is not processed seems difficult because to that we need to process that. Or might me maintaining a tree of all processed/unprocessed tree will help, building the building the final string? Any better way than it, it seems a brute force?
Note: I know there are a lot of other transformation possible, please try not to suggest anything else to use, we are doing a bit research on it.
I came up with a somewhat brute force method. This way finds the optimal way to combine 2 words then does it for each element in the array.
This strategy works by trying finding the best possible way to combine 2 words together. It is considered the best by having the fewest letters. Each word is fed into an ever growing "merged" word. Each time a new word is added the existing word is searched for a matching character which exists in the word to be merged. Once one is found both are split into 2 sets and attempted to be joined (using the rules at hand, no need 2 add if letter already exists ect..). The strategy generally yields good results.
The join_word method takes 2 words you wish to join, the first parameter is considered to be the word you wish to place the other into. It then searches for the best way to split into and word into 2 separate parts to merge together, it does this by looking for any shared common characters. This is where the splits_on_letter method comes in.
The splits_on_letter method takes a word and a letter which you wish to split on, then returns a 2d array of all the possible left and right sides of splitting on that character. For example splits_on_letter('boom', 'o') would return [["b","oom"],["bo","om"],["boo","m"]], this is all the combinations of how we could use the letter o as a split point.
The sort() at the beginning is to attempt to place like elements together. The order in which you merge the elements generally effects the results length. One approach I tried was to sort them based upon how many common letters they used (with their peers), however the results were varying. However in all my tests I had maybe 5 or 6 different word sets to test with, its possible with a larger, more varying word arrays you might find different results.
Output is
spmjhooarckpplivden
var words = ["jack", "apple", "maven", "hold", "solid", "mark", "moon", "poor", "spark", "live"];
var result = minify_words(words);
document.write(result);
function minify_words(words) {
// Theres a good sorting method somewhere which can place this in an optimal order for combining them,
// hoever after quite a few attempts i couldnt get better than just a regular sort... so just use that
words = words.sort();
/*
Joins 2 words together ensuring each word has all its letters in the result left to right
*/
function join_word(into, word) {
var best = null;
// straight brute force each word down. Try to run a split on each letter and
for(var i=0;i<word.length;i++) {
var letter = word[i];
// split our 2 words into 2 segments on that pivot letter
var intoPartsArr = splits_on_letter(into, letter);
var wordPartsArr = splits_on_letter(word, letter);
for(var p1=0;p1<intoPartsArr.length;p1++) {
for(var p2=0;p2<wordPartsArr.length;p2++) {
var intoParts = intoPartsArr[p1], wordParts = wordPartsArr[p2];
// merge left and right and push them together
var result = add_letters(intoParts[0], wordParts[0]) + add_letters(intoParts[1], wordParts[1]);
if(!best || result.length <= best.length) {
best = result;
}
}
}
}
// its possible that there is no best, just tack the words together at that point
return best || (into + word);
}
/*
Splits a word at the index of the provided letter
*/
function splits_on_letter(word, letter) {
var ix, result = [], offset = 0;;
while((ix = word.indexOf(letter, offset)) !== -1) {
result.push([word.substring(0, ix), word.substring(ix, word.length)]);
offset = ix+1;
}
result.push([word.substring(0, offset), word.substring(offset, word.length)]);
return result;
}
/*
Adds letters to the word given our set of rules. Adds them starting left to right, will only add if the letter isnt found
*/
function add_letters(word, addl) {
var rIx = 0;
for (var i = 0; i < addl.length; i++) {
var foundIndex = word.indexOf(addl[i], rIx);
if (foundIndex == -1) {
word = word.substring(0, rIx) + addl[i] + word.substring(rIx, word.length);
rIx += addl[i].length;
} else {
rIx = foundIndex + addl[i].length;
}
}
return word;
}
// For each of our words, merge them together
var joinedWords = words[0];
for (var i = 1; i < words.length; i++) {
joinedWords = join_word(joinedWords, words[i]);
}
return joinedWords;
}
A first try, not really optimized (183% shorter):
function getShort(arr){
var perfect="";
//iterate the array
arr.forEach(function(string){
//iterate over the characters in the array
string.split("").reduce(function(pos,char){
var n=perfect.indexOf(char,pos+1);//check if theres already a possible char
if(n<0){
//if its not existing, simply add it behind the current
perfect=perfect.substr(0,pos+1)+char+perfect.substr(pos+1);
return pos+1;
}
return n;//continue with that char
},-1);
})
return perfect;
}
In action
This can be improved trough simply running the upper code with some variants of the array (200% improvement):
var s=["jack",...];
var perfect=null;
for(var i=0;i<s.length;i++){
//shift
s.push(s.shift());
var result=getShort(s);
if(!perfect || result.length<perfect.length) perfect=result;
}
In action
Thats quite close to the minimum number of characters ive estimated ( 244% minimization might be possible in the best case)
Ive also wrote a function to get the minimal number of chars and one to check if a certain word fails, you can find them here
I have used the idea of Dynamic programming to first generate the shortest possible string in forward direction as stated in OP. Then I have combined the result obtained in the previous step to send as a parameter along with the next String in the list. Below is the working code in java. Hope this would help to reach the most optimal solution, in case my solution is identified to be non optimal. Please feel free to report any countercases for the below code:
public String shortestPossibleString(String a, String b){
int[][] dp = new int[a.length()+1][b.length()+1];
//form the dynamic table consisting of
//length of shortest substring till that points
for(int i=0;i<=a.length();i++){
for(int j=0;j<=b.length();j++){
if(i == 0)
dp[i][j] = j;
else if(j == 0)
dp[i][j] = i;
else if(a.charAt(i-1) == b.charAt(j-1))
dp[i][j] = 1+dp[i-1][j-1];
else
dp[i][j] = 1+Math.min(dp[i-1][j],dp[i][j-1]);
}
}
//Backtrack from here to find the shortest substring
char[] sQ = new char[dp[a.length()][b.length()]];
int s = dp[a.length()][b.length()]-1;
int i=a.length(), j=b.length();
while(i!=0 && j!=0){
// If current character in a and b are same, then
// current character is part of shortest supersequence
if(a.charAt(i-1) == b.charAt(j-1)){
sQ[s] = a.charAt(i-1);
i--;
j--;
s--;
}
else {
// If current character in a and b are different
if(dp[i-1][j] > dp[i][j-1]){
sQ[s] = b.charAt(j-1);
j--;
s--;
}
else{
sQ[s] = a.charAt(i-1);
i--;
s--;
}
}
}
// If b reaches its end, put remaining characters
// of a in the result string
while(i!=0){
sQ[s] = a.charAt(i-1);
i--;
s--;
}
// If a reaches its end, put remaining characters
// of b in the result string
while(j!=0){
sQ[s] = b.charAt(j-1);
j--;
s--;
}
return String.valueOf(sQ);
}
public void getCombinedString(String... values){
String sSQ = shortestPossibleString(values[0],values[1]);
for(int i=2;i<values.length;i++){
sSQ = shortestPossibleString(values[i],sSQ);
}
System.out.println(sSQ);
}
Driver program:
e.getCombinedString("jack", "apple", "maven", "hold",
"solid", "mark", "moon", "poor", "spark", "live");
Output:
jmapphsolivecparkonidr
Worst case time complexity of the above solution would be O(product of length of all input strings) when all strings have all characters distinct and not even a single character matches between any pair of strings.
Here is an optimal solution based on dynamic programming in JavaScript, but it can only get through solid on my computer before it runs out of memory. It differs from #CodeHunter's solution in that it keeps the entire set of optimal solutions after each added string, not just one of them. You can see that the number of optimal solutions grows exponentially; even after solid there are already 518,640 optimal solutions.
const STRINGS = ["jack", "apple", "maven", "hold", "solid", "mark", "moon", "poor", "spark", "live"]
function map(set, f) {
const result = new Set
for (const o of set) result.add(f(o))
return result
}
function addAll(set, other) {
for (const o of other) set.add(o)
return set
}
function shortest(set) { //set is assumed non-empty
let minLength
let minMatching
for (const s of set) {
if (!minLength || s.length < minLength) {
minLength = s.length
minMatching = new Set([s])
}
else if (s.length === minLength) minMatching.add(s)
}
return minMatching
}
class ZipCache {
constructor() {
this.cache = new Map
}
get(str1, str2) {
const cached1 = this.cache.get(str1)
if (!cached1) return undefined
return cached1.get(str2)
}
set(str1, str2, zipped) {
let cached1 = this.cache.get(str1)
if (!cached1) {
cached1 = new Map
this.cache.set(str1, cached1)
}
cached1.set(str2, zipped)
}
}
const zipCache = new ZipCache
function zip(str1, str2) {
const cached = zipCache.get(str1, str2)
if (cached) return cached
if (!str1) { //str1 is empty, so only choice is str2
const result = new Set([str2])
zipCache.set(str1, str2, result)
return result
}
if (!str2) { //str2 is empty, so only choice is str1
const result = new Set([str1])
zipCache.set(str1, str2, result)
return result
}
//Both strings start with same letter
//so optimal solution must start with this letter
if (str1[0] === str2[0]) {
const zipped = zip(str1.substring(1), str2.substring(1))
const result = map(zipped, s => str1[0] + s)
zipCache.set(str1, str2, result)
return result
}
//Either do str1[0] + zip(str1[1:], str2)
//or str2[0] + zip(str1, str2[1:])
const zip1 = zip(str1.substring(1), str2)
const zip2 = zip(str1, str2.substring(1))
const test1 = map(zip1, s => str1[0] + s)
const test2 = map(zip2, s => str2[0] + s)
const result = shortest(addAll(test1, test2))
zipCache.set(str1, str2, result)
return result
}
let cumulative = new Set([''])
for (const string of STRINGS) {
console.log(string)
const newCumulative = new Set
for (const test of cumulative) {
addAll(newCumulative, zip(test, string))
}
cumulative = shortest(newCumulative)
console.log(cumulative.size)
}
console.log(cumulative) //never reached
I want to grab the user input from an input tag including everything after the # symbol and up to a space if the space exists. For example:
If the user input is "hello#yourname"
I want to grab "yourname"
If the user input is "hello#yourname hisname"
I want to grab "yourname" because it is after the # symbol and ends at the space.
I have some code written that attempts to grab the user input based on these rules, but there is a bug present that I can't figure out how to fix. Right now if I type "hello#yourname hisname"
My code will return "yourname hisn"
I don't know why the space and four characters "hisn" are being returned. Please help me figure out where the bug is.
Here is my function which performs the user input extraction.
handleSearch(event) {
let rawName, nameToSearch;
rawName = event.target.value.toLowerCase();
if (rawName.indexOf('#') >= 0 && rawName.indexOf(' ') >= 0) {
nameToSearch = rawName.substr(rawName.indexOf('#') + 1, rawName.indexOf(' ') - 1);
} else if (rawName.indexOf('#') >= 0 && rawName.indexOf(' ') < 0) {
nameToSearch = rawName.substr(rawName.indexOf('#') + 1);
} else {
nameToSearch = '';
}
return nameToSearch;
}
Working example:
handleSearch(event) {
let rawName = event.target.value.toLowerCase();
if (rawName.indexOf("#") === -1) {
return '';
}
return (rawName.split("#")[1].split(" "))[0];
}
You have to handle a lack of "#", but you don't need to handle the case where there is a space or not after the "#". The split function will still behave correctly in either of those scenarios.
Edit: The specific reason why OP's code doesn't work is because the substr method's second argument is not the end index, but the number of characters to return after the start index. You can use the similar SUBSTRING method instead of SUBSTR to make this easier. Change the line after the first if statement as follows:
nameToSearch = rawName.substring(rawName.indexOf('#') + 1, rawName.indexOf(' '));
const testCases = [
"hello#yourname",
"hello#yourname hisname"
];
for (let test of testCases) {
let re = /#(.*?)(?:\s|$)/g;
let result = re.exec(test);
console.log(result[1]);
}
Use regex instead if you know how the string will be created.
You could do something like this--
var string = "me#somename yourname";
var parts = string.split("#");
var parts2 = parts[1];
var yourPart = parts2.split(" ");
console.log(yourPart[0]);
NOTE:
I am suggesting it just because you know your string structure.
Suggestion
For your Piece of code I think you have some white space after hisn that is why it is returning this output. Try to replace all the white spaces with some character see if you are getting any white space after hisn.
I'm not sure of the language your code is in (there are several it 'could be', probably Javascript), but in most languages (including Javascript) a substring function 'starts at' the position of the first parameter, and then 'ends at' that position plus the second parameter. So when your second parameter is 'the position of the first space - 1', you can substitute 'the position of the first space - 1' with the number 13. Thus, you're saying 'get a substring by starting one after the position of the first # character i.e. position 6 in a zero-based system. Then return me the next 13 characters.'
In other words, you seem to be trying to say 'give me the characters between position 6 and position 12 (inclusive)', but you're really saying 'give me the characters between position 6 and position 18 (inclusive)'.
This is
y o u r n a m e h i s n
1 2 3 4 5 6 7 8 9 10 11 12 13
(For some reason I can't get my spaces and newlines to get preserved in this answer; but if you count the letters in 'yourname hisn' it should make sense :) )
This is why you could use Neophyte's code so long as you can presume what the string would be. To expand on Neophyte's answer, here's the code I would use (in the true branch of the conditional - you could also probably rename the variables based on this logic, etc.):
nameToSearch = rawName.substr(rawName.indexOf('#') + 1;
var nameFromNameToSearch = nameToSearch.substr(nameToSearch.indexof(' ') - 1;
nameFromNameToSearch would contain the string you're looking for. I haven't completely tested this code, but I hope it 'conceptually' gives you the answer you're looking for. Also, 'conceptually', it should work whether there are more than one '#' sign, etc.
P.S. In that first 'rawName.substr' I'm not giving a second parameter, which in Javascript et al. effectively says 'start at the first position and give me every character up to the end of the string'.
I am creating a class to convert an integer to a sentence in a natural language. I've got some basic checks going on to ensure that the number given is between -9999 and 9999. I feel like this works for the most part.
However, once the program reaches "this.convertSentence" - past the try/catch block and error checking, I'm wondering what the best practice is now to decompose the problem into the various function calls it will need to run through to get the job done.
What I'm planning on doing with this.convertSentence is doing some checking for number size, etc...and then sending the number off to separate functions to do more work and having them propagate a sentence to return. I'm not sure if I want a variable just within my class to work with or whether I should be passing a variable around for the sentence to build. Things like this I am wondering about.
/**
* A class for converting an integer to a natrual language sentence in Spanish.
* Accepts integers from -9999 to 9999
*
*/
function NumberToWord () {
this.getSentence = function(number) {
// Check for erroneous input. Accepts only -9999 thru 9999 integers
try
{
if(number === parseInt(number) && number > -10000 && number < 10000) {
return this.convertSentence(number);
}
else {
throw new Error("Argument is not an integer between -9999 and 9999");
}
}
catch(e){
console.log(e.name + " " + e.message);
}
};
this.convertSentence = function(number) {
return "This is where I'll start the logic for the sentence";
};
}
var numberToWord = new NumberToWord();
// Tests
console.log(numberToWord.getSentence(9999));
console.log(numberToWord.getSentence(-9999));
console.log(numberToWord.getSentence(10000));
console.log(numberToWord.getSentence(-10000));
console.log(numberToWord.getSentence(0));
console.log(numberToWord.getSentence(1.1));
console.log(numberToWord.getSentence(-9999.1));
console.log(numberToWord.getSentence(10001));
console.log(numberToWord.getSentence(-10001));
console.log(numberToWord.getSentence(5.5));
console.log(numberToWord.getSentence());
There are a few things I found amiss in your code:
You don't need a class. You simply want to convert a number to a sentence. Use a function.
Why are both getSentence and convertSentence public? Only getSentence should be public.
Since your class will (in all probability) only be instatiated once, use the singleton pattern.
Things I would do:
Because you want to make your code modular, I would use the module pattern.
You can delegate specific tasks to different functions, but keep them in a private namespace.
Here's the code:
Number.prototype.toWord = function () {
return function (lang) {
var number = this.valueOf();
if (parseInt(number) === number) {
if (number < 10000 && number > 10000) {
switch (lang) {
case "es":
return toSpanish(number);
case "en":
default:
return toEnglish(number);
}
} else throw new RangeError("Expected an integer between ±10000.");
} else throw new TypeError("Expected an integer.");
};
function toSpanish(number) {
// convert the number to Spanish
}
function toEnglish(number) {
// convert the number to English
}
}();
Then you can use it like this:
var number = 1337;
alert(number.toWord("es"));
Edit: I wrote a simple function which will do what you want. However it's in English. I don't know Spanish so you'll have to implement that yourself. Here's the demo: http://jsfiddle.net/XKYhx/2/
My thinking would be to check how many parts you are going to have to the sentence and build an array to match with the substrings. for example, in English anyway (I don't speak Spanish!)
as natural language you would say (minus) xxx thousand and xxx
since your number has a max / min of ~10000 / ~-10000,
in pseudocode:
var sign = ""
var wholeparts = new Array()
var mantissaparts = new Array()
if number < 0,
sign = "minus"
number = math.abs(number) // turn the number into a positive number now we have the sign
var whole = math.floor(number) //get whole number
var mantissa = number - whole //get the after decimal part if exists
if whole > 1000
wholeparts.push(math.floor(whole/1000)) //get the thousands part
wholeparts.push(whole - parts[0]*1000) // add the hundreds
else
parts.push(whole)
if mantissa.length > 0
do something similar for the mantissa to the mantissaparts array.
At this point you would have the sentence structure broken down then:
string sentance:
foreach (var part in wholeparts)
stringify and check each number, converting to human words depending on index, ie "seven" or "seventy", add each to the string.
if wholeparts.length > 1 : sentence = sentence + " thousand and"
then if you had a mantissa, sentence = sentence + "point" .. then and the mantissa as natural language.
Best breakdown I can think of would be:
method to convert a number (whole or mantissa) to an array,
method to convert the array to natural language, with a parameter saying if it is whole or mantissa for the different wording that would be used.
method that accepts a number in string form and returns the natural language equivalent
Hope that helps .. was thinking on the fly.
Is there a way to resolve mathematical expressions in strings in javascript? For example, suppose I want to produce the string "Tom has 2 apples, Lucy has 3 apples. Together they have 5 apples" but I want to be able to substitute in the variables. I can do this with a string replacement:
string = "Tom has X apples, Lucy has Y apples. Together they have Z apples";
string2 = string.replace(/X/, '2').replace(/Y/, '3').replace(/Z/, '5');
However, it would be better if, instead of having a variable Z, I could use X+Y. Now, I could also do a string replace for X+Y and replace it with the correct value, but that would become messy when trying to deal with all the possible in-string calculations I might want to do. I suppose I'm looking for a way to achieve this:
string = "Something [X], something [Y]. Something [(X+Y^2)/(5*X)]";
And for the [___] parts to be understood as expressions to be resolved before substituting back into the string.
Thanks for your help.
There's no direct, built-in way (well, okay, perhaps there is — see below), but if you use the callback feature of the replace function, where the replacement can be a function rather than a string (the return value is what's substituted in), you can implement this fairly easily.
For instance, suppose you use the Ruby notation #{xyz} for your placeholders. This code loops through those:
var mappings, str;
str = "One #{X} three #{Y} five";
mappings = {
"X": 2,
"Y": 4
};
str = str.replace(/\#\{([^#]+)\}/g, function(match, key) {
var result;
result = mappings[key];
/* ...processing here */
return result;
});
The resulting string is One 2 three 4 five, because #{X} and #{Y} have been replaced via lookup. You can look at the key and see whether it's an expression and needs to be evaluated rather than simply looked up. That evaluation is where your real work comes in.
Now, you could use with and eval to achieve expression support; change the result = mapping[key]; line above to this:
with (mappings) {
result = eval(key);
}
If you feed the string "One #{X} three #{Y} five #{X + Y * 2}" into that, the result is One 2 three 4 five 10 — because 2 + 4 * 2 = 10.
That works because with sticks the given object on top of the scope chain, so it's the first thing checked when resolving an unqualified reference (like X), and eval executes Javascript code — and so can evaluate expressions — and magically does so within the scope in which it's called. But beware; as Eric pointed out, not all operators are the same in various forms of expression, and in particular Javascript interprets ^ to mean "bitwise XOR", not "to the power of". (It doesn't have an exponent operator; you have to use Math.pow.)
But you need to be very careful about that sort of thing, both with and eval (each in their own way) can be problematic. But the main issues with with are that it's hard to tell where something comes from or where it will go if you do an assignment, which you're not; and the main issues with eval come from using it to interpret strings you don't control. As long as you keep safeguards in place and are aware of the issues...
Boiling that down into a function:
function evaluate(str, mappings) {
return str.replace(/\#\{([^#]+)\}/g, function(match, key) {
var result;
with (mappings) {
result = eval(key);
}
return result;
});
}
alert(evaluate(
"The expression '(#{X} + #{Y}) * 2' equals '#{(X + Y) * 2}'",
{"X": 2, "Y": 4}
)); // alerts "The expression '(2 + 4) * 2' equals '12'"
alert(evaluate(
"The expression '(#{X} + #{Y}) * 2' equals '#{(X + Y) * 2}'",
{"X": 6, "Y": 3}
)); // alerts "The expression '(6 + 3) * 2' equals '18'"
The only way I can think of to achieve this would be a templating engine such as jTemplates. Also see the answers to this SO question.
Nice question:
function substitutestring(str,vals)
{
var regex = /\[[^\]]*\]/gi;
var matches = str.match(regex);
var processed = [];
for(var i = 0; i<matches.length; i++)
{
var match = matches[i];
processed[match] = match.slice(1,-1);
for(j in vals)
{
processed[match] = processed[match].replace(j,vals[j]);
}
processed[match] = eval("("+processed[match]+")");
}
for(var original in processed)
{
str = str.replace(original,processed[original]);
}
return str;
}
document.write(
substitutestring(
"[x] + [y] = [x+y]",
{"x": 1, "y": 2}
)
);
In ES6 you can now use template strings:
var X = 2, Y = 3;
string = Tom has ${X} apples, Lucy has ${Y} apples. Together they have ${X+Y} apples;