Javascript: Convert string to computable - javascript

please someone help me about my javascript code. I want to convert string to be computable.
Example:
var string = "34.5 + 30";
the javascript function or code will automatically compute my string value. So the result will be 64.5 with a float or decimal data type.
hope someone can answer my query.

Use eval function.
Reference
The eval() function evaluates JavaScript code represented as a string.
But there is a security issue in this.
Executing JavaScript from a string is an enormous security risk. It is
far too easy for a bad actor to run arbitrary code when you use
eval(). See Never use eval()!, below.
var myString = "34.5 + 30";
console.log(eval(myString));
Since eval has some security issues, its always adviced not to use it. You could either make use of some custom libraries or implement your own parsing logic.
Please find a small paring logic from my side.
Please note, this evaluates the mathematical expressions linearly. This doesnot works with BODMAS rule or will not evaluate any complex expression. This is to evaluate a mathematical expression that contains only numbers, and basic operators such as +, -, * and /. If you wish to have custom validations you could build on top of this or can implement a solution of your own.
I have added the description as code comment
const myString = "34.5 + 30";
// Regex to split the expression on +, -, *, / and spaces
const isNumeric = /(?=[-+*\/(\s+)])/;
// Valid operators
const operators = ['+', '-', '*', '/'];
// Split the string into array
const expressionArray = myString.split(isNumeric);
// Object to hold the result and the operator while parsing the array generated
const result = { result: 0, operator: '' };
// loop though each node in the array
// If an operator is found, set it to result.operator
// If not an operator, it will be a number
// Check an operator is already existing in result.operator
// If then perform the operation with the current node and result and clear the result.operator
expressionArray.forEach((node) => {
const trimmedNode = node.trim();
if (trimmedNode) {
if (operators.indexOf(trimmedNode) === -1) {
if (result.operator) {
switch (result.operator) {
case '+':
result.result += Number(node);
break;
case '-':
result.result -= Number(node);
break;
case '*':
result.result *= Number(node);
break;
case '/':
result.result /= Number(node);
break;
result.operator = '';
}
} else {
result.result += Number(node);
}
} else {
result.operator = trimmedNode;
}
}
});
console.log(result.result);

Related

Sum of all Object Keys returns a String, not a Number as expected

I'm trying to add all the values of an object into one variable for the total, but I seem to be concatenating them instead?
binance.depth("BNBBTC", (error, depth, symbol) => {
a = 0;
for (value in depth.bids){
a += value;
};
console.log(a);
});
This is the output:
00.001081000.001080900.001080800.001080200.001080000.001079700.001079600.001079100
Any help would be much appreciated
The unexpected behavior of your code is a side effect of adding a String and a Number together. Unlike other languages that complain about adding a Number to a String or vice versa, JavaScript will implicitly convert a Number to a String and not complain at all.
This is discussed in Section 2 of the 2ality Blog Post, JavaScript quirk 1: implicit conversion of values. See below excerpt:
2. Implicit conversion of strings
In web development, you often get values as strings that are actually numbers or booleans. For example, when users enter this kind of data in a form. If you forget to explicitly convert these strings then JavaScript will surprise you negatively in two ways: First, there will be no warning. Second, the values will be converted automatically, but wrongly. The plus operator (+), for instance, is problematic, because it concatenates strings as soon as one of its operands is a string. During the following interaction with JavaScript, the assumption is that we are adding 1 to 5. Instead, we are concatenating the strings '5' and '1'.
> var x = '5'; // wrong assumption: x is a number
> x + 1
> '51'
Incorporating the solution of casting value to a Number, the above code can be rewritten more concisely with the help of ES6. Using Array#reduce() the sum can be modularized a bit. I've also included Error-First Callbacks for Error Handling.
const sumArray = values => values.reduce((sum, value) => (sum + Number(value)), 0)
const getDepth = (data, cb) => {
binance.depth(data, (err, {bids}) => {
if (err) {
return cb(err)
}
return cb(null, sumArray(Object.keys(bids)))
})
}
getDepth('BNBBTC', (err, depth) => {
if (err) {
console.log(err)
} else {
console.log(depth)
}
})
thanks guys
i needed to convert to value to a number first
a += Number(value)
maybe converting value to int such as
var v = parseInt(value);
a += value;
Can not post a comment as my reputation is below 50 :(.
Try converting value to a number before adding: a += Number(value)
All need to do is add a + in front of the string
let a = "10"
let b = "20"
let c = +a + +b //c = 30
In your case:
a += +value

JavaScript switch case using enum

I have an "enum" declared like so:
var PlaceType = {
PASSABLE_TERRAIN: 1,
IMPASSABLE_TERRAIN: 0,
SOMEWHAT_PASSABLE_TERRAIN: 2,
PATH: 3
};
and a function declared like this:
setPlaceType(placeType) {
this.clear = false;
this.placeType = placeType;
alert("before switch "+(PlaceType.SOMEWHAT_PASSABLE_TERRAIN==this.placeType));
switch(this.placeType) {
case PlaceType.PASSABLE_TERRAIN: {
alert("Case PASSABLE");
break;
}
case PlaceType.IMPASSABLE_TERRAIN: {
alert("Case IMPASSABLE");
break;
}
case PlaceType.SOMEWHAT_PASSABLE_TERRAIN: {
alert("Case SOMEWHAT_PASSABLE");
break;
}
case PlaceType.PATH: {
alert("Case PATH");
break;
}
default: {
alert("case default");
}
}
}
if I call it like this:
setPlaceType(1);
I get the following alerts: "before switch true", "case default"
if I call it like this:
setPlaceType(2);
I get the following alerts: "before switch false", "case default"
In other words, the function is called with the proper argument, which, when doing (what it seems to me to be) the same comparison as the switch but via "==" I get correct behavior, but the switch never matches the values to the appropriate case. Does anybody know why?
The comparison operator will cast both operands to strings if either operator is a string. If you pass in a string, you are comparing string == number which will cast the number to a string and, in the case of passing the string '2', it will be true.
switch case comparison uses the identity operator === and will fail if the operands are not the same type.
long story short, make sure you are always passing a number if your cases are comparing against numbers, you can double check like this:
setPlaceType(placeType) {
if (typeof placeType !== 'number') {
throw new Error('You must pass a number to setPlaceType!');
}
...
}
also, you should be calling your function like this:
setPlaceType(PlaceType.PASSABLE_TERRAIN);
otherwise there's not really any point to using the "enumeration" (i use that term loosely) object.
Refering to this => Switch-Case for strings in Javascript not working as expected
Switch do a ===, while if do a ==.
Hope this help! have a nice day
When you are doing the comparison using == js is using type-coercion to cast the 2 operands to an intermediate type, string in this case, and thus compare them successfully.
So to get the effect to work with your switch statement, you will need to cast as such
this.placeType = parseint(placeType);
What you also got to learn here is that it is not really an ideal practice to compare 2 values in javascript using the == operator, instead use the === operator which also checks for the types to be the same. So in your case,
alert("before switch "+(PlaceType.SOMEWHAT_PASSABLE_TERRAIN==this.placeType));
would have failed if you would have used === as you are comparing an int and string
Working demo here: http://jsfiddle.net/pratik136/ATx8c/
The matching case is determined using the === identity operator, not the == equality operator. The expressions must match without any type conversion. It would fail if you are passing in a string and not an integer.
setPlaceType(1); //"Case PASSABLE"
setPlaceType("1"); //"case default"
Example running your code with the above lines: jsFiddle
So if you are saying it is failing, you are probably comparing a string to a number. Use parseInt.
this.placeType = parseint(placeType,10);
Another way to use enum for switch case:
const PlaceType = Object.freeze({
PASSABLE_TERRAIN: 1,
IMPASSABLE_TERRAIN: 0,
SOMEWHAT_PASSABLE_TERRAIN: 2,
PATH: 3
});
function setPlaceType(placeType){
return({
0:"Case IMPASSABLE_TERRAIN",
1:"Case PASSABLE_TERRAIN",
2:"Case SOMEWHAT_PASSABLE_TERRAIN",
3:"PATH"
}[placeType]);
}

Replace with the regular expression

What regular expression I need to use to correct
if (text.indexOf("+") != -1) {
action = "+";
} else if (text.indexOf("-") != -1) {
action = "-";
} else if (text.indexOf("*") != -1) {
action = "*";
} else if (text.indexOf("/") != -1) {
action = "/";
}
this code
?
EDIT:
and how can I improve this code:
switch (action) {
case "+":
result = parseInt(array[0]) + parseInt(array[1]);
break;
case "-":
result = parseInt(array[0]) - parseInt(array[1]);
break;
case "*":
result = parseInt(array[0]) * parseInt(array[1]);
break;
case "/":
result = parseInt(array[0]) / parseInt(array[1]);
break;
default:
break;
}
Sorry for dull questions I am new in js.
You can use either of these:
var action = text.replace(/.*([+*/-]).*/,'$1');
var match = /[*+/-]/.exec(text);
var action = match && match[0];
If there's the possibility of newlines in your text then change the first to:
var action = text.replace(/[\d\D]*([+*/-])[\d\D]*/,'$1');
Edit: You can improve your switch statement by using, e.g.
// allow floating point values, and also
// don't accidentally interpret "011" as octal
array[0]*1 + array[1]*1;
For more details on why parseInt is probably bad, see this answer.
You can also remove the superfluous default section of your case statement. However, I suspect that your desired "improvement" was making fewer lines. Given that =-*/ are operators in JavaScript (and not methods), I cannot think of any way to avoid having four calculations (i.e. a switch or if/else if).
Whereas in Ruby you could, for example, use array[0].send(action,array[1]) to cover all four cases ;)

Convert String with Dot or Comma as decimal separator to number in JavaScript

An input element contains numbers a where comma or dot is used as decimal separator and space may be used to group thousands like this:
'1,2'
'110 000,23'
'100 1.23'
How would one convert them to a float number in the browser using JavaScript?
jQuery and jQuery UI are used. Number(string) returns NaN and parseFloat() stops on first space or comma.
Do a replace first:
parseFloat(str.replace(',','.').replace(' ',''))
I realise I'm late to the party, but I wanted a solution for this that properly handled digit grouping as well as different decimal separators for currencies. As none of these fully covered my use case I wrote my own solution which may be useful to others:
function parsePotentiallyGroupedFloat(stringValue) {
stringValue = stringValue.trim();
var result = stringValue.replace(/[^0-9]/g, '');
if (/[,\.]\d{2}$/.test(stringValue)) {
result = result.replace(/(\d{2})$/, '.$1');
}
return parseFloat(result);
}
This should strip out any non-digits and then check whether there was a decimal point (or comma) followed by two digits and insert the decimal point if needed.
It's worth noting that I aimed this specifically for currency and as such it assumes either no decimal places or exactly two. It's pretty hard to be sure about whether the first potential decimal point encountered is a decimal point or a digit grouping character (e.g., 1.542 could be 1542) unless you know the specifics of the current locale, but it should be easy enough to tailor this to your specific use case by changing \d{2}$ to something that will appropriately match what you expect to be after the decimal point.
The perfect solution
accounting.js is a tiny JavaScript library for number, money and currency formatting.
Check this for ref
You could replace all spaces by an empty string, all comas by dots and then parse it.
var str = "110 000,23";
var num = parseFloat(str.replace(/\s/g, "").replace(",", "."));
console.log(num);
I used a regex in the first one to be able to match all spaces, not just the first one.
This is the best solution
http://numeraljs.com/
numeral().unformat('0.02'); = 0.02
What about:
parseFloat(str.replace(' ', '').replace('.', '').replace(',', '.'));
All the other solutions require you to know the format in advance. I needed to detect(!) the format in every case and this is what I end up with.
function detectFloat(source) {
let float = accounting.unformat(source);
let posComma = source.indexOf(',');
if (posComma > -1) {
let posDot = source.indexOf('.');
if (posDot > -1 && posComma > posDot) {
let germanFloat = accounting.unformat(source, ',');
if (Math.abs(germanFloat) > Math.abs(float)) {
float = germanFloat;
}
} else {
// source = source.replace(/,/g, '.');
float = accounting.unformat(source, ',');
}
}
return float;
}
This was tested with the following cases:
const cases = {
"0": 0,
"10.12": 10.12,
"222.20": 222.20,
"-222.20": -222.20,
"+222,20": 222.20,
"-222,20": -222.20,
"-2.222,20": -2222.20,
"-11.111,20": -11111.20,
};
Suggestions welcome.
Here's a self-sufficient JS function that solves this (and other) problems for most European/US locales (primarily between US/German/Swedish number chunking and formatting ... as in the OP). I think it's an improvement on (and inspired by) Slawa's solution, and has no dependencies.
function realParseFloat(s)
{
s = s.replace(/[^\d,.-]/g, ''); // strip everything except numbers, dots, commas and negative sign
if (navigator.language.substring(0, 2) !== "de" && /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(s)) // if not in German locale and matches #,###.######
{
s = s.replace(/,/g, ''); // strip out commas
return parseFloat(s); // convert to number
}
else if (/^-?(?:\d+|\d{1,3}(?:\.\d{3})+)(?:,\d+)?$/.test(s)) // either in German locale or not match #,###.###### and now matches #.###,########
{
s = s.replace(/\./g, ''); // strip out dots
s = s.replace(/,/g, '.'); // replace comma with dot
return parseFloat(s);
}
else // try #,###.###### anyway
{
s = s.replace(/,/g, ''); // strip out commas
return parseFloat(s); // convert to number
}
}
Here is my solution that doesn't have any dependencies:
return value
.replace(/[^\d\-.,]/g, "") // Basic sanitization. Allows '-' for negative numbers
.replace(/,/g, ".") // Change all commas to periods
.replace(/\.(?=.*\.)/g, ""); // Remove all periods except the last one
(I left out the conversion to a number - that's probably just a parseFloat call if you don't care about JavaScript's precision problems with floats.)
The code assumes that:
Only commas and periods are used as decimal separators. (I'm not sure if locales exist that use other ones.)
The decimal part of the string does not use any separators.
try this...
var withComma = "23,3";
var withFloat = "23.3";
var compareValue = function(str){
var fixed = parseFloat(str.replace(',','.'))
if(fixed > 0){
console.log(true)
}else{
console.log(false);
}
}
compareValue(withComma);
compareValue(withFloat);
This answer accepts some edge cases that others don't:
Only thousand separator: 1.000.000 => 1000000
Exponentials: 1.000e3 => 1000e3 (1 million)
Run the code snippet to see all the test suite.
const REGEX_UNWANTED_CHARACTERS = /[^\d\-.,]/g
const REGEX_DASHES_EXEPT_BEGINNING = /(?!^)-/g
const REGEX_PERIODS_EXEPT_LAST = /\.(?=.*\.)/g
export function formatNumber(number) {
// Handle exponentials
if ((number.match(/e/g) ?? []).length === 1) {
const numberParts = number.split('e')
return `${formatNumber(numberParts[0])}e${formatNumber(numberParts[1])}`
}
const sanitizedNumber = number
.replace(REGEX_UNWANTED_CHARACTERS, '')
.replace(REGEX_DASHES_EXEPT_BEGINING, '')
// Handle only thousands separator
if (
((sanitizedNumber.match(/,/g) ?? []).length >= 2 && !sanitizedNumber.includes('.')) ||
((sanitizedNumber.match(/\./g) ?? []).length >= 2 && !sanitizedNumber.includes(','))
) {
return sanitizedNumber.replace(/[.,]/g, '')
}
return sanitizedNumber.replace(/,/g, '.').replace(REGEX_PERIODS_EXEPT_LAST, '')
}
function formatNumberToNumber(number) {
return Number(formatNumber(number))
}
const REGEX_UNWANTED_CHARACTERS = /[^\d\-.,]/g
const REGEX_DASHES_EXEPT_BEGINING = /(?!^)-/g
const REGEX_PERIODS_EXEPT_LAST = /\.(?=.*\.)/g
function formatNumber(number) {
if ((number.match(/e/g) ?? []).length === 1) {
const numberParts = number.split('e')
return `${formatNumber(numberParts[0])}e${formatNumber(numberParts[1])}`
}
const sanitizedNumber = number
.replace(REGEX_UNWANTED_CHARACTERS, '')
.replace(REGEX_DASHES_EXEPT_BEGINING, '')
if (
((sanitizedNumber.match(/,/g) ?? []).length >= 2 && !sanitizedNumber.includes('.')) ||
((sanitizedNumber.match(/\./g) ?? []).length >= 2 && !sanitizedNumber.includes(','))
) {
return sanitizedNumber.replace(/[.,]/g, '')
}
return sanitizedNumber.replace(/,/g, '.').replace(REGEX_PERIODS_EXEPT_LAST, '')
}
const testCases = [
'1',
'1.',
'1,',
'1.5',
'1,5',
'1,000.5',
'1.000,5',
'1,000,000.5',
'1.000.000,5',
'1,000,000',
'1.000.000',
'-1',
'-1.',
'-1,',
'-1.5',
'-1,5',
'-1,000.5',
'-1.000,5',
'-1,000,000.5',
'-1.000.000,5',
'-1,000,000',
'-1.000.000',
'1e3',
'1e-3',
'1e',
'-1e',
'1.000e3',
'1,000e-3',
'1.000,5e3',
'1,000.5e-3',
'1.000,5e1.000,5',
'1,000.5e-1,000.5',
'',
'a',
'a1',
'a-1',
'1a',
'-1a',
'1a1',
'1a-1',
'1-',
'-',
'1-1'
]
document.getElementById('tbody').innerHTML = testCases.reduce((total, input) => {
return `${total}<tr><td>${input}</td><td>${formatNumber(input)}</td></tr>`
}, '')
<table>
<thead><tr><th>Input</th><th>Output</th></tr></thead>
<tbody id="tbody"></tbody>
</table>
From number to currency string is easy through Number.prototype.toLocaleString. However the reverse seems to be a common problem. The thousands separator and decimal point may not be obtained in the JS standard.
In this particular question the thousands separator is a white space " " but in many cases it can be a period "." and decimal point can be a comma ",". Such as in 1 000 000,00 or 1.000.000,00. Then this is how i convert it into a proper floating point number.
var price = "1 000.000,99",
value = +price.replace(/(\.|\s)|(\,)/g,(m,p1,p2) => p1 ? "" : ".");
console.log(value);
So the replacer callback takes "1.000.000,00" and converts it into "1000000.00". After that + in the front of the resulting string coerces it into a number.
This function is actually quite handy. For instance if you replace the p1 = "" part with p1 = "," in the callback function, an input of 1.000.000,00 would result 1,000,000.00

How can I modify my Shunting-Yard Algorithm so it accepts unary operators?

I've been working on implementing the Shunting-Yard Algorithm in JavaScript for class.
Here is my work so far:
var userInput = prompt("Enter in a mathematical expression:");
var postFix = InfixToPostfix(userInput);
var result = EvaluateExpression(postFix);
document.write("Infix: " + userInput + "<br/>");
document.write("Postfix (RPN): " + postFix + "<br/>");
document.write("Result: " + result + "<br/>");
function EvaluateExpression(expression)
{
var tokens = expression.split(/([0-9]+|[*+-\/()])/);
var evalStack = [];
while (tokens.length != 0)
{
var currentToken = tokens.shift();
if (isNumber(currentToken))
{
evalStack.push(currentToken);
}
else if (isOperator(currentToken))
{
var operand1 = evalStack.pop();
var operand2 = evalStack.pop();
var result = PerformOperation(parseInt(operand1), parseInt(operand2), currentToken);
evalStack.push(result);
}
}
return evalStack.pop();
}
function PerformOperation(operand1, operand2, operator)
{
switch(operator)
{
case '+':
return operand1 + operand2;
case '-':
return operand1 - operand2;
case '*':
return operand1 * operand2;
case '/':
return operand1 / operand2;
default:
return;
}
}
function InfixToPostfix(expression)
{
var tokens = expression.split(/([0-9]+|[*+-\/()])/);
var outputQueue = [];
var operatorStack = [];
while (tokens.length != 0)
{
var currentToken = tokens.shift();
if (isNumber(currentToken))
{
outputQueue.push(currentToken);
}
else if (isOperator(currentToken))
{
while ((getAssociativity(currentToken) == 'left' &&
getPrecedence(currentToken) <= getPrecedence(operatorStack[operatorStack.length-1])) ||
(getAssociativity(currentToken) == 'right' &&
getPrecedence(currentToken) < getPrecedence(operatorStack[operatorStack.length-1])))
{
outputQueue.push(operatorStack.pop())
}
operatorStack.push(currentToken);
}
else if (currentToken == '(')
{
operatorStack.push(currentToken);
}
else if (currentToken == ')')
{
while (operatorStack[operatorStack.length-1] != '(')
{
if (operatorStack.length == 0)
throw("Parenthesis balancing error! Shame on you!");
outputQueue.push(operatorStack.pop());
}
operatorStack.pop();
}
}
while (operatorStack.length != 0)
{
if (!operatorStack[operatorStack.length-1].match(/([()])/))
outputQueue.push(operatorStack.pop());
else
throw("Parenthesis balancing error! Shame on you!");
}
return outputQueue.join(" ");
}
function isOperator(token)
{
if (!token.match(/([*+-\/])/))
return false;
else
return true;
}
function isNumber(token)
{
if (!token.match(/([0-9]+)/))
return false;
else
return true;
}
function getPrecedence(token)
{
switch (token)
{
case '^':
return 9;
case '*':
case '/':
case '%':
return 8;
case '+':
case '-':
return 6;
default:
return -1;
}
}
function getAssociativity(token)
{
switch(token)
{
case '+':
case '-':
case '*':
case '/':
return 'left';
case '^':
return 'right';
}
}
It works fine so far. If I give it:
((5+3) * 8)
It will output:
Infix: ((5+3) * 8)
Postfix (RPN): 5 3 + 8 *
Result: 64
However, I'm struggling with implementing the unary operators so I could do something like:
((-5+3) * 8)
What would be the best way to implement unary operators (negation, etc)? Also, does anyone have any suggestions for handling floating point numbers as well?
One last thing, if anyone sees me doing anything weird in JavaScript let me know. This is my first JavaScript program and I'm not used to it yet.
The easiest thing would be to make isNumber match /-?[0-9]+(\.[0-9]+)?/, handling both floating points and negative numbers.
If you really need to handle unary operators (say, unary negation of parenthesized expressions), then you have to:
Make them right-associative.
Make them higher precedence than any of the infix operators.
Handle them separately in EvaluateExpression (make a separate PerformUnaryExpression function which only takes one operand).
Distinguish between unary and binary minus in InfixToPostfix by keeping track of some kind of state. See how '-' is turned into '-u' in this Python example.
I wrote up a more thorough explanation of handling unary minus on another SO question.
my suggestion is this. don't handle the '-' as an arithmetic operator. treat it as a 'sign' operator. or treat it as if it's a part of the whole operand (i.e. its sign). what i mean is that everytime you encounter '-', you just have to multiply the operand after it by -1, then proceed to read the next token. :) i hope that helps. just a simple thought...
I could solve this problem by modifying unary operators('+' and '-') to distinguish them from the binary ones.
For example, I called the unary minus 'm' and unary plus 'p', making them right-assocative and their precedence equal to the exponent operator('^').
To detect if the operator is unary I simply had to check if the token before the operator was an operator or an opening bracket.
This is my implementation in C++:
if (isOperator(*token))
{
if (!isdigit(*(token - 1)) && *(token - 1) != ')') // Unary check
{
if (*token == '+')
*token = 'p'; // To distinguish from the binary ones
else if (*token == '-')
*token = 'm';
else
throw;
}
short prec = precedence(*token);
bool rightAssociative = (*token == '^' || *token == 'm' || *token == 'p');
if (!operators.empty())
{
while (prec < precedence(operators.top()) || (prec == precedence(operators.top()) && !rightAssociative))
{
rpn += operators.top();
operators.pop();
if (operators.empty())
break;
}
}
operators.push(*token);
}
Here operators is a stack and token is an iterator to the infix expression string
(This just the operator handling part)
When I needed to support this, I did this in an intermediate stage. I started by generating a list of all expression lexemes, then used helper functions to extract operators and operands and the "get operand" function simply consumed two lexemes whenever it saw a unary operator.
It really helps if you use another character to signify "unary minus", though.
In my Java implementation I did it in next way:
expression = expression.replace(" ", "").replace("(-", "(0-")
.replace(",-", ",0-");
if (expression.charAt(0) == '-') {
expression = "0" + expression;
}
To handle floating point numbers you can change your (number part of) regex to:
/([0-9]+\.?[0-9]*)/
so the final regex would be:
/([0-9]+\.?[0-9]*|[*+-\/()])/
And for handling unary minus operator, you can change it to another character like 'u'.
(As it is explained here by -TGO)
The javascript code that i wrote for handling unary minus operator based on the link given is:
// expr is the given infix expression from which, all the white spaces has been
// removed.(trailing and leading and in between white space characters)
const operators = ['+', '*', '-', '/', '^'];
const openingBrackets = ['(', '[', '{'];
let exprArr = Array.from(expr);
// Since strings are immutable in js, I am converting it to an array for changing
// unary minus to 'u'
for (let i = 0; i < expr.length; i++) {
if (expr[i] === '-') {
if (i === 0) {
exprArr[i] = 'u';
} else if (operators.includes(expr[i - 1])) {
exprArr[i] = 'u';
} else if (openingBrackets.includes(expr[i - 1])) {
exprArr[i] = 'u';
} else {
// '-' is not a unary operator
// it is a binary operator or we have the wrong expression, so...
if (!openingBrackets.includes(expr[i + 1]) && !/[0-9]/.test(expr[i + 1])) {
throw new Error("Invalid Expression...");
}
}
}
}
// And finally converting back to a string.
let expr2 = exprArr.join('');
This isn't in Javascript, but here is a library I wrote to specifically solve this problem after searching and not finding any clear answers.
This does all you want and more:
https://marginalhacks.com/Hacks/libExpr.rb/
It is a ruby library (as well as a testbench to check it) that runs a modified shunting yard algorithm that also supports unary ('-a') and ternary ('a?b:c') ops. It also does RPN, Prefix and AST (abstract syntax trees) - your choice, and can evaluate the expression, including the ability to yield to a block (a lambda of sorts) that can handle any variable evaluation. Only AST does the full set of operations, including the ability to handle short-circuit operations (such as '||' and '?:' and so on), but RPN does support unary. It also has a flexible precedence model that includes presets for precedence as done by C expressions or by Ruby expressions (not the same). The testbench itself is interesting as it can create random expressions which it can then eval() and also run through libExpr to compare results.
It's fairly documented/commented, so it shouldn't be too hard to convert the ideas to Javascript or some other language.
The basic idea as far as unary operators is that you can recognize them based on the previous token. If the previous token is either an operator or a left-paren, then the "unary-possible" operators (+ and -) are just unary and can be pushed with only one operand. It's important that your RPN stack distinguishes between the unary operator and the binary operator so it knows what to do on evaluation.

Categories

Resources