I am currently writing a JS rules engine which at one point needs to evaluate boolean expressions using the eval() function.
Firstly I construct an equation as such:
var equation = "relation.relatedTrigger.previousValue" + " " + relation.operator +
" " + "relation.value";
relation.relatedTrigger.previousValue is the value I want to compare.
relation.operator is the operator (either "==", "!=", <=, "<", ">", >=").
relation.value is the value I want to compare with.
I then simply pass this string to the eval function and it returns true or false as such:
return eval(equation);
This works absolutely fine (with words and numbers) or all of the operators except for >= and <=. E.g. When evaluating the equation:
relation.relatedTrigger.previousValue <= 100
It returns true when previousValue = 0,1,10,100 & all negative numbers but false for everything in between.
I would greatly appreciate the help of anyone to either answer my question or to help me find an alternative solution.
Regards,
Augier.
P.S. I don't need a speech on the insecurities of the eval() function. Any value given to relation.relatedTrigger.previousValue is predefined.
edit: Here is the full function:
function evaluateRelation(relation)
{
console.log("Evaluating relation")
var currentValue;
//if multiple values
if(relation.value.indexOf(";") != -1)
{
var values = relation.value.split(";");
for (x in values)
{
var equation = "relation.relatedTrigger.previousValue" + " " + relation.operator +
" " + "values[x]";
currentValue = eval(equation);
if (currentValue)
return true;
}
return false;
}
//if single value
else
{
//Evaluate the relation and get boolean
var equation = "relation.relatedTrigger.previousValue" + " " + relation.operator +
" " + "relation.value";
console.log("relation.relatedTrigger.previousValue " + relation.relatedTrigger.previousValue);
console.log(equation);
return eval(equation);
}
}
Answer: Provided by KennyTM below. A string comparison doesn't work. Converting to a numerical was needed.
You didn't show how relation.relatedTrigger.previousValue is obtained, but I guess the type of this variable is still a string. In this case, the right hand side will be treated as a string instead. A string comparison matches all characteristics you mentioned:
>>> '-213' <= '100'
true
>>> '0' <= '100'
true
>>> '1' <= '100'
true
>>> '2' <= '100'
false
>>> '10' <= '100'
true
>>> '13' <= '100'
false
You need to make sure relation.relatedTrigger.previousValue is a number. One solution is use the unary + operator in the comparison, e.g.
+relation.relatedTrigger.previousValue <= 100
This has nothing to do with eval. The problem is the overly liberal implicit conversion in Javascript.
Edit: By the way, instead of eval, you could use a dictionary of functions instead. This is faster and also safer. See http://jsperf.com/eval-vs-function-map.
var fmap = {
'>=': function(a, b) { return a >= b; },
...
};
fmap[relation.operator](+relation.relatedTrigger.previousValue,
+relation.value);
It is comparing strings not numbers.
Make sure that relation.relatedTrigger.previousValue and relation.value are numbers.
"11" > "100": Because 11 comes after 100 in alphabetical order.
11 < 100 in numeric order.
var relation = {'relatedTrigger':{'previousValue':"7"}, 'operator':'<=', 'value': "100"};
var equation = "parseFloat(relation.relatedTrigger.previousValue)" + " " + relation.operator +
" " + "parseFloat(relation.value)";
alert(equation + ", " + eval(equation));
This is effectively what you end up with and the extra step to ensure numeric value, not strings are passed seems to work.
Related
I have a assignment where I write a number in Expanded Form and display it as a string.
For example:
expandedForm(7020); // should return '7000 + 20';
expandedForm(7402); // '7000 + 400 + 2';
I haven't figured out all of it yet, but my current code looks like:
function expandedForm(num) {
let numArr = Array.from(num.toString()).map(Number),
counter = numArr.length,
answer = "";
for(let i = 0; i < numArr.length; i++) {
(numArr[i] === 0) ? counter-- : --counter;
console.log(numArr[i].toString(), counter);
}
}
expandedForm(702); // returns '7' 2 and '2' 0
I skip 0 since it's supposed to be ignored, go through each integer and check the amount of units it has with the counter. This is helpful but now I'm trying to somehow add '00' to the '7' based on counter value. So if counter is 2 I need to add two 0 as a string '00'.
There is a good chance I'm not doing this with best practices so if something should be done differently with what I already have please suggest. Thank you.
"0".repeat(counter)
thats it... then just concatenate all those to strings and you are done :)
Also note that:
(numArr[i] === 0) ? counter-- : --counter;
basically equals counter--;.
"best practice" is quite opinion based, heres how I'd do that:
const chars = [...("" + input)];
return chars
.map((char, i) => char + "0".repeat(chars.length - i - 1))
.filter(it => it[0] !== "0")
.join(" + ");
If you are interested in code golfing (it's fun, but not always a "best practice"), you can use:
[...`${input}`].map((c,i,a)=>c.padEnd(a.length-i,'0')).filter(c=>+c).join(' + ')
Here it is in action:
> input = 2
> [...`${input}`].map((c,i,a)=>c.padEnd(a.length-i,'0')).filter(c=>+c).join(' + ')
'2'
> input = 200
> [...`${input}`].map((c,i,a)=>c.padEnd(a.length-i,'0')).filter(c=>+c).join(' + ')
'200'
> input = 209777800001
> [...`${input}`].map((c,i,a)=>c.padEnd(a.length-i,'0')).filter(c=>+c).join(' + ')
'200000000000 + 9000000000 + 700000000 + 70000000 + 7000000 + 800000 + 1'
Here is how it works:
[...`${input}`]
makes an array out of the digits in your number. For example, 7503 becomes ['7', '5', '0', '3']
.map((c,i,a)=>c.padEnd(a.length-i,'0'))
maps each digit to its padding with the correct number of zeros. The callback to map takes the digit, the array index, and then the array itself. This gives us, for the running example, ['7000', '500', '00', '3']
.filter(c=>+c)
removes any string of zeros such as "0", "0000", etc. from the array. Now we have ['7000', '500', '3']
.join(' + ')
does what it looks like it does.
Here's what this solution does not do:
Handle the value 0 (because you get an empty string)
Handle negative numbers
Handle non integers
The first one can be fixed by doing
[...`${input}`].map((c,i,a)=>c.padEnd(a.length-i,'0')).filter(c=>+c).join(' + ') || '0'
Since this is an assignment, I'll leave the other two cases to you.
Also have fun explaining this to the person that assigned you the problem. ;-)
As mentioned in another answer, best practice is subjective. You can be more explicit by introducing intermediate variables:
const chars = Array.from(`${input}`);
const places = chars.map((c, i, a) => c.padEnd(a.length - i, '0'));
const nonZeros = places.filter(c => Number(c) !== 0);
const result = nonZeros.join(' + ') || '0';
Again, best practices would also require you do some error handling. What if your input is NaN or a string, array, or object? What if it is infinity? Floating point?
var bedTime = $("bedtime").value;
var wakeUp = $("wakeup").value;
var Value = bedTime < wakeUp;
alert("is " + bedTime + " less than " + wakeUp + '? ' + Value);
My goal here to take user input through an html form (numeric only). This is what I wrote when I started running into the issue. Anything less than 10 except 1 will return the correct value, but anything over is incorrect. For example if bedTime = 10 and wakeUp = 9 it returns true. So just plugging in a few numbers leads me to believe that when comparing variables this way it only compares the first digit? Any workarounds? Thanks guys.
Try using parseInt() or parseFloat() to make sure you are comparing the same type of data.
var Value = parseInt(bedTime) < parseInt(wakeUp);
Comparing strings is different, for example '10' < '9' => true.
I am currently learning JS and when I do some practice, I find some issues I am unclear on data type in Javascript. I understand that JS do NOT require specific type indication, it will automatically do the type conversion whenever possible. However, I suffer one problem when I do NOT do type conversion which is as follows:
var sum = 0;
function totalSum (a) {
if (a == 0) {
return sum;
}
else {
sum += a;
return totalSum(--a);
}
}
var i = prompt("Give me an integer");
// var num = parseInt(i);
alert("Total sum from 1 to " + i + " = " + totalSum(i));
// alert("Total sum from 1 to " + i + " = " + totalSum(num));
I notice that the code works perfectly if I change the data type from string to int using parseInt function, just as the comment in the code does. BUT when I do NOT do the type conversion, things are getting strange, and I get a final result of 054321, if I input the prompt value as 5. AND in a similar way, input of 3, gets 0321 and so on.
Why is it the case? Can someone explain to me why the totalSum will be such a number? Isn't javascript will automatically helps me to turn it into integer, in order for it to work in the function, totalSum?
The sample code can also be viewed in http://jsfiddle.net/hphchan/66ghktd2/.
Thanks.
I will try to decompose what's happening in the totalSum method.
First the method totalSum is called with a string as parameter, like doing totalSum("5");
Then sum += a; (sum = 0 + "5" : sum = "05") (note that sum become a string now)
then return totalSum(--a);, --a is converting the value of a to a number and decrement it's value. so like calling return totalSum(4);
Then sum += a (sum = "05" + 4 : sum = "054") ...
See the documentation of window.prompt: (emphasis mine)
result is a string containing the text entered by the user, or the value null.
Here's an easy one for you true believers: You can use + to convert a string to a number,
var thing = "12"
alert(thing);
alert(typeof thing); // string
thing = +thing;
alert(typeof thing); // number
if (thing == 112) alert("!"); // number
Can someone explain:
What is the name of this process?
How does + convert a string to a number?
Javascript uses a dynamic type system. For me it's a 'cast' operation.
The operator + could be a String operator ('a' + 'b') or an Number operator (1+2). It could be used also between Strings and numbers (remembering that 0 + '12' = 12 and '0'+'12' = '012')
By default, i think that the JS interpreter considered +thing as 0 + things so it casts this variable to a number
Please look at the following code. I can't get my values to add up. The digit just adds itself to the back of the string. Wonder any way to go about it.
$("a[name='older_post']").click(function(){
$("div.main_ads_div a[name='older_post']").remove().fadeOut();
var last_td_id=parseInt($("table.main_ads_table:last").find("td.load_ads:last").attr("id"),10);
alert(last_td_id); //OUTPUTS 38
$("div.main_ads_div").append('<table class="main_ads_table" col="7" row="7"><tr><td class="load_ads" id="'+last_td_id+1+'"></td><td class="load_ads" id="'+last_td_id+2+'"></td><td class="load_ads" id="'+last_td_id+3+'"></td><td class="load_ads priority" id="'+last_td_id+4+'"></td><td class="load_ads priority" id="'+last_td_id+5+'"></td><td class="load_ads" id="'+last_td_id+6+'"></td><td class="load_ads" id="'+last_td_id+7+'"></td><td class="load_ads" id="'+last_td_id+8+'"></td></tr></table>');
});
So what I'm trying to get here is for the each td that appends, I'm trying to get 39, 40, 41, 42... But I'm getting values such as 381, 382, 383,... etc etc.
Any help here appreciated.
Wrap the addition in parentheses:
... + (last_td_id + 7) + ...
You are concating string with number, enclose the addition in parentheses to perform arithmatic operation on it.
Change
+last_td_id+1+
To
+(last_td_id+1)+
The association of + is left to right and in the statement '....class="load_ads" id="'+last_td_id first concatenates the left hand string with number (last_td_id) and gives a string which again concatenates the incremental number like (2 or 3 ..) to previous string. Putting the parenthesis around the number makes its precedence high and the calucation is performed first and result is concatenated in the string.
The plus operator only performs mathematical addition only if both operands are numbers. If one of them is a string, it will perform string concatenation (and cast the 1 to "1").
Yet it is left associative, and you are not using parenthesis. So your expression is evaluated as
(((…('<…' + id) + 1) + '"…') + id) + 2) + …
and every single step yields a string. You will need to enforce the addition to be executed first by wrapping it in parenthesis, as others have already mentioned:
'<…' + (id + 1) + '"…' + (id + 2) + …
// evaluated as
(((…('<…' + (id + 1)) + '"…') + (id + 2)) + …
Using '+' in javascript always appends the variables/strings. Try something like this:
var c = (16 * 24) + 12;
d = c.toString();
Only then the var 'd' will give you the mathematical output
In your case, it could be
(last_td_id+4).toString(); and so on