JavaScript -- write a function that can solve a math expression (without eval) - javascript

Ultimately I want to take this:
2x + 3 = 5
and solve for x, by first subtract 3 from both sides so 2x = 2, then divide both sides by 2 so x = 1. I was thinking a lot how one should go about making a function like this in JavaScript that can return an array of the steps done in order, including the result. Obviously "eval" wouldn't do anything for this, so seemingly one has to re-create equations.
I initially thought to first of all, ignore X, and just try to make a function that can solve simple equations, without eval or any built-in function.
I figured that the first step is to break up the terms using .split, but I was having some trouble with this, as I need to split for multiple symbols. For example, say I have the simple expression to evaluate: 3 - 6 * 3 / 9 + 5. So before we even get into order of operations, just splitting up each term (and categorizing them) is the hard part, which is the main concrete-question I have at this point.
I started simply splitting one after the other, but I was having some problems, and especially considering the order.
function solve(eq) {
var minuses = eq.split("-"),
pluses = minuses.map(x=> x.split("+")),
timeses = pluses.map(x=>x.map(y=>y.split("*"))),
dividers = timeses.map(x=>x.map(y=>y.map(z=>z.split("/"))));
console.log(minuses, pluses, timeses, dividers);
}
solve("3 - 6 * 3 / 9 + 5");
As you can see, for each successive operator I need to map through each of he elements of the previous one to split it, and then I am left with an array of arrays etc...
So 1) how can I split up these terms more efficiently, without making a new variable for each one, and manually recursively mapping through each one? Seemingly I should just have some kind of dictionary of array keeping track of orders of operations (not considering parenthesis or exponents now): ["*","/","+","-"] -- and given that array, generate something similar to the last array in the above example ("dividers") which contains only constants, and somehow keep track of the which elements each of the stored arrays follows...
and 2) How can I solve the expression given the arrays of values?
I was just a little confused with the logic, I guess I need to work up from the last array and solve the constants one at a time, keeping track of which operator is the current one, but I'm not sure how exactly.

While your problem doesn't require to construct, binary expression tree is a good way to brainstorm the logic to solve a math query.
So for the query 3 - 6 * 3 / 9 + 5, the representative binary expression tree is:
plus
|_minus
| |_3
| |_divide
| |_times
| | |_3
| | |_6
| |_9
|_5
to solve above tree, you recursively solve from the leaf level up to the root.
Again, you don't need to construct a tree. It just helps us to see the logic of parsing here:
Get the last minus or plus expression in query and solve left and right child of that expression.
If no plus/minus, get the last times/division expression and solve left and right child
If meet a number, return that number value.
Given above logic, here is an implementation:
function solve(str) {
var expressionIndex = Math.max(str.lastIndexOf("-"), str.lastIndexOf("+"));
if (expressionIndex === -1) {
expressionIndex = Math.max(str.lastIndexOf("*"), str.lastIndexOf("/"));
}
if (expressionIndex === -1) {
var num = Number.parseInt(str.trim());
if (isNaN(num)) {
throw Exception("not a valid number");
} else {
return num;
}
} else {
var leftVal = solve(str.substring(0, expressionIndex).trim());
var rightVal = solve(str.substring(expressionIndex + 1).trim());
switch (str[expressionIndex]) {
case "+":
return leftVal + rightVal;
case "-":
return leftVal - rightVal;
case "*":
return leftVal * rightVal;
case "/":
return leftVal / rightVal;
}
}
}
function parse(str) {
var expressionIndex = Math.max(str.lastIndexOf("-"), str.lastIndexOf("+"));
if (expressionIndex === -1) {
expressionIndex = Math.max(str.lastIndexOf("*"), str.lastIndexOf("/"));
}
if (expressionIndex === -1) {
var num = Number.parseInt(str.trim());
if (isNaN(num)) {
throw Exception("not a valid number");
} else {
return { type: "number", value: num };
}
} else {
var leftNode = parse(str.substring(0, expressionIndex).trim());
var rightNode = parse(str.substring(expressionIndex + 1).trim());
return {
type: "expression",
value: str[expressionIndex],
left: leftNode,
right: rightNode
};
}
}
console.log(solve("3 - 6 * 3 / 9 + 5"));
console.log(parse("3 - 6 * 3 / 9 + 5"));
Above is a solution for very simple query with only +, -, *, / (no parenthesis, e.g.). For solving a equation like your first example requires a lot more of work.
EDIT: add a parse function to return the tree.

You can do that in following steps:
First of all use split() and split by the + and - which will occur after multiplication and division.
Then use map() on array and split() it again by * and /.
Now we have a function which will which will evaluate an array of numbers with operators to single number.
Pass the nested array to complete multiplication and division.
Then pass that result again to sovleSingle and perform addition and subtraction.
The function works same as eval as long as there are no brackets ().
Note: This doesnot matters the which occurs first among + and - or which occurs first among * and /. But *,/ should occur before +,-
function solveSingle(arr){
arr = arr.slice();
while(arr.length-1){
if(arr[1] === '*') arr[0] = arr[0] * arr[2]
if(arr[1] === '-') arr[0] = arr[0] - arr[2]
if(arr[1] === '+') arr[0] = +arr[0] + (+arr[2])
if(arr[1] === '/') arr[0] = arr[0] / arr[2]
arr.splice(1,1);
arr.splice(1,1);
}
return arr[0];
}
function solve(eq) {
let res = eq.split(/(\+|-)/g).map(x => x.trim().split(/(\*|\/)/g).map(a => a.trim()));
res = res.map(x => solveSingle(x)); //evaluating nested * and / operations.
return solveSingle(res) //at last evaluating + and -
}
console.log(solve("3 - 6 * 3 / 9 + 5")); //6
console.log(eval("3 - 6 * 3 / 9 + 5")) //6

Related

I don't know javascript algorithm problem! (codewars problem)

I have a simple question about JavaScript algorithm
https://www.codewars.com/kata/56747fd5cb988479af000028/train/javascript
I'm solving this problem. The explanation of this problem is to extract two letters from the middle of the odd-numbered character
What I'm curious about is
function getMiddle(s) {
//Code goes here!
let answer = "";
if (s.length % 2 !== 0) {
answer += s[Math.floor(s.length / 2)];
} } else {
answer += s.slice(
(Math.floor(s.length / 2 - 1), Math.floor(s.length / 2 + 1))
);
}
return answer;
}
console.log(getMiddle("test"));
console.log(
"test".slice(
Math.floor("test".length / 2 - 1),
Math.floor("test".length / 2 + 1)
)
);
Is the return value from the getMiddle function different from the console.log('test'.slice~') in the end?
The difference is that one is the return value of the function and the other is just taken directly from the console, but I don't know why it's the same code, but the value is differentcrying
please help me
The problem is that in the function you have wrapped the pair of indices in parentheses:
answer += s.slice(
(Math.floor(s.length / 2 - 1), Math.floor(s.length / 2 + 1))
// ^ ^
);
Those parentheses will trigger an evaluation of what is between them, and that means the comma is then interpreted as a comma-operator. The result of that operation is then a single number that is passed as argument to s.slice. So you are actually calling slice with just one argument instead of two, and due to the comma operator, it is the second index that is used by slice. It is equivalent to s.slice(3)
In the second console.log you don't have those extra parentheses, and so there it works as it is intended.
NB: I assume the extra closing brace just before else is a typo.

Recursive function to find binary not returning elements in single array. (Not pushing to the same )

Problem statement: I'm trying to get string > binary without using the inbuilt method in javascript.
This is a piece of program where a string input (like "ABC") is accepted, then it is translated to an array of equivalent code value ([65,66,67]).
Function binary() will change a number to binary. But I'm unable to join them together to loop through all the contents. Please help. (I'm a noob, please forgive my bad code and bad explanation)
var temp3 = [65,66,67];
var temp2 = [];
var r;
for(i=0;i<temp3.length;i++) {
var r = temp3[i];
temp2.push(binary(r));
}
function binary(r) {
if (r === 0) return;
temp2.unshift(r % 2);
binary(Math.floor(r / 2));
return temp2;
}
console.log(temp2);
I think this is a cleaner version of this function. It should work for any non-negative integers, and would be easy enough to extend to the negatives. If we have a single binary digit (0 or 1) and hence are less than 2, we just return the number converted to a string. Otherwise we call recursively on the floor of half the number (as yours does) and append the final digit.
const binary = (n) =>
n < 2
? String (n)
: binary (Math.floor (n / 2)) + (n % 2)
console.log (binary(22)) //=> '10110'
console.log ([65, 66, 67] .map (binary)) //=> ['1000001', '1000010', '1000011']
In your function you have this code
var r = temp3[i];
I don't see any temp3 variable anywhere in your code above so I'd imagine that could be causing some issues.

Building Javascript Calculator

I am trying to build a calculator in javascript but I am stuck and don't know how to proceed. every time someone click on 1 + 1 / 2, for exemple I am creating an array that pushes everything that was typed so in the case above the array would be
[1, "+", 1, "/", 2];
However, I can't figure it out how to transform this array into an actual mathematical value.
I had an idea of looping through all elements
like this:
for(var i=0; i<arrayCharacters.length ;i++){
if(arrayCharacters[i] != "*" || arrayCharacters[i] != "/" || arrayCharacters[i] != "+" || arrayCharacters[i] != "*"){
arrayNumbers.push(arrayCharacters.slice(0, i));
console.log(arrayNumbers);
}
}
It's very incomplete because I got stuck. Can anyone help me?
var result=eval(arrayCharacters.join(""));
You could also parse the expression manually, however this requires building up a tree, as math isnt evaluated left to right.
If you really want to parse it on your own (which is far better then eval), you could use a math notation that really goes from left to right , so its much easier to parse ( As #Scott suggested). An implementation:
var stack=[];
var arrayCharacters=[1,2,"/",1,"+"];
for(var i=0;i<arrayCharacters.length;i++){
var char=arrayCharacters[i];
if(typeof char==="number"){
stack.push(char);
continue;
}
var op2=stack.pop()||0;
var op1=stack.pop()||0;
var res;
if(char=="+"){
res=op1+op2;
}
if(char=="-"){
res=op1-op2;
}
if(char=="*"){
res=op1*op2;
}
if(char=="/"){
res=op1/op2;
}
stack.push(res);
}
var result=stack.pop();
Math Syntax (RPN (1)):
1+2 => 1 2 +
1/2+3 => 1 2 / 3 +
1+2/3 => 1 2 3 / +
(1+2)/3 => 1 2 + 3 /
1/2/3+4/5/6 => 1 2 / 3 / 4 5 / 6 / +
http://jsbin.com/zofeqitiba/edit?console
"eval" function is a very good choice in your case. Also you can use the math.js library, which comes with a powerful expression parser.
http://mathjs.org

How to compare two strings in this way?

I'm working with JavaScript, and I have two strings like this :
var week1="1.345.7", // each digit refers to one day of the week
week2="123..6.";
Now I want to return a value from 1 to 7 which refers to the number of days in common.
In the previous example I should return 2, because we have both weeks have Monday and Wednesday (1 and 3).
How can I achieve the above ?
Each character is either . or its index, so you can represent it with a bit.
"0b" + "1.345.7".replace(/./g, c=>c==='.'?0:1); // "0b1011101"
"0b" + "123..6.".replace(/./g, c=>c==='.'?0:1); // "0b1110010"
Then, you can use the bitwise operator AND &:
"0b1011101"
& "0b1110010";
// 0b1010000
Finally, you only need to convert it back to string and count the number of 1:
0b1010000.toString(2).split('1').length-1; // 2
Probably I wouldn't do it like this, but just for fun :)
In fact, to waste less memory, you could store the data as numbers instead of strings
0b1011101; // 93 - only needs 64 bits!
0b1110010; // 114 - only needs 64 bits!
And to retrieve the data
0b1011101 >> 6 & 1; // 1 - 1st bit
0b1011101 >> 5 & 1; // 0 - 2nd bit
0b1011101 >> 4 & 1; // 1 - 3rd bit
0b1011101 >> 3 & 1; // 1 - 4th bit
0b1011101 >> 2 & 1; // 1 - 5th bit
0b1011101 >> 1 & 1; // 0 - 6th bit
0b1011101 >> 0 & 1; // 1 - 7th bit
Get a list of digits from the first string using a quick regexp, then filter it to keep only the ones that are in the other one, then see how many there are with length.
(week1.match(/\d/g) || []) . filter(n => week2.includes(n)) . length
In a "code golf" spirit, you could write this as a generator, taking advantage of the ability of for...of to loop across characters in a string:
function *common(a, b) {
for (c of a) if (c !== '.' && b.includes(c)) yield c;
}
console.log(...common(a, b))
Just to throw another option out there, if you split a string with an empty string argument, you get an array of one-character strings. This makes them easy to iterate, but also if you're targeting browsers that support ECMAscript 5.1 (most notably IE 9+) you can use the reduce function. It's generally a good fit when you're passing in an array that you want to iterate and return a single value. This could be more concise, but I think it's easier to follow this way.
var week1="1.345.7";
var week2="123..6.";
function weekDaysInCommon(w1, w2) {
//split to convert w1 to an array.
//"1.345.7" becomes ["1", ".", "3", "4", "5", ".", "7"]
w1 = w1.split('');
//countCharactersAtSameIndex(w2) returns the function to use as the callback, with w2 accessible to it via closure
//the second arg, 0, is the initial value.
return w1.reduce(countCharactersAtSameIndex(w2), 0);
}
function countCharactersAtSameIndex(comparisonWeek) {
comparisonWeek = comparisonWeek.split('');
return function(total, day, index) {
if(comparisonWeek[index] === day) {
return total + 1;
} else {
return total;
}
}
}
document.write(weekDaysInCommon(week1, week2) + ' days in common');
Further reading:
MDN has a good doc on the reduce function - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

How do I create a javascript elite planet name generator? [closed]

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
This question appears to be off-topic because it lacks sufficient information to diagnose the problem. Describe your problem in more detail or include a minimal example in the question itself.
Closed 8 years ago.
Improve this question
I try to convert Tricky's script (that generates a name from elite) to javascript here:
https://github.com/rubo77/eliteNameGen/blob/master/elite.js
But I get stuck at this LPC-code by Tricky:
digrams=ABOUSEITILETSTONLONUTHNO..LEXEGEZACEBISOUSESARMAINDIREAERATENBERALAVETIEDORQUANTEISRION
...
pairs = digrams[24..<1];
...
names[0..<2]
I couldn't find a manual to LPC that would explain this syntax.
In the End I want to create a javascript, that creates a random planet name from the old C64 game Elite.
I also found a python version, (but that seems a bit more complicated to me)
Ok I managed to port the code over, but I had to tweak a bit of the algorithm. The one that is provide by Tricky, for some reason, produces non-unique names. I used the tweakseed function to tweak the seeds to generate a list of random names.
Answer
To answer the question above, #MattBurland is correct. You would replace the following code:
pairs = digrams[24..<1];
with
pairs = digrams.substring(24);
The following code, however, is actually printing out the list of names. So you're indexing an array - in which case:
names[0..<2]
becomes
for (var i = 0; i < (names.length - 2); i++) {
names[i]
}
Analysis
Just to give this some more depth. I've analyzed the code and realized that rotatel, twist, tweakseed, and next were just used to create random numbers. I don't know enough about LPC, but I think that at the time it probably didn't have a pseudo-random number generator.
A lot of this code can be removed and just replaced with Math.random. The key part of this entire program is the variable digram. This sequence of characters produces Alien-like names. I figure it probably has something to do with alternation of consonants and vowels. Grabbing them in pairs randomly will almost always produce some sort of consonant + vowel pairing. There is the odd time where you'll end up with a name like 'Rmrirqeg', but in most cases, the names appear Alien-like.
Port
Below is a direct port of the code. You can use this jsFiddle to see it in action, but it uses AngularJS to print out the names instead of printing out a list like the code provided. genNames will produce an array of names, which you can use for whatever reason you want.
Note this port only works on IE9+, since it uses map, reduce, and forEach. Replace these with loops if you plan on using this on IE8 or below.
You can tweak this to produce names longer or shorter. However, the length of the names is dependent on the pairs array. Either use Math.random or something to make it completely wild.
var digrams = "ABOUSEITILETSTONLONUTHNO" +
"..LEXEGEZACEBISOUSESARMAINDIREA.ERATENBERALAVETIEDORQUANTEISRION";
function rotatel(x) {
var tmp = (x & 255) * 2;
if (tmp > 255) tmp -= 255;
return tmp;
}
function twist(x) {
return (256 * rotatel(x / 256)) + rotatel(x & 255);
}
function next(seeds) {
return seeds.map(function(seed) {
return twist(seed);
});
}
function tweakseed(seeds) {
var tmp;
tmp = seeds.reduce(function(total, seed) {
return total += seed;
}, 0);
return seeds.map( function ( seed, index, arr ) {
return arr[index + 1] || (tmp & 65535)
});
};
function makename(pairs, seeds)
{
var name = [];
/* Modify pair if you want to have names shorter or longer than 8 chars */
/* I'll leave that as an exercise for you. */
var pair = [0, 0, 0, 0];
var longname = seeds[0] & 64;
pair = pair.map(function() {
seeds = tweakseed(seeds);
return 2 * ((seeds[2] / 256) & 31);
});
pair.forEach(function(value, index, arr) {
if (longname || ( index < (arr.length - 1))) {
name.push(pairs[value]);
name.push(pairs[value + 1]);
}
});
return name.join('').toLowerCase()
.replace(/^\w/, function(letter) {
return letter.toUpperCase();
});
}
function genNames()
{
var names = [];
var pairs;
var num = 256;
var seeds = [23114, 584, 46931];
pairs = digrams.substring(24);
while (--num) {
names.push( makename(pairs, seeds) );
seeds = tweakseed(next(seeds));
}
return names;
}
For the range operator in LPC, this link helps:
http://www.unitopia.de/doc/LPC/operators.html
expr1[expr2..expr3] Extracts a
piece from an array or string.
expr2 or expr3 may be omitted, default is the begin
or end of expr1.
Negative numbers for expr2 or expr3
mean ``count from before the beginning'', i.e.
foo[-2..-1] is an empty array or string.
foo[<2..<1] gives the 2nd and last element of
the array resp. chars of the string.
So I'm guessing that:
pairs = digrams[24..<1];
Means get the substring starting at index 24 to the end of the string?

Categories