Unusual question here and maybe I'm approaching this issue incorrectly -
I'd like to do a comparison between some numeric values in Javascript, but I have the comparators stored in a database (in a VARCHAR field), where specific criteria are stored. (Comparators being <=, <, ==, >, >=, etc).
Is there a way to evaluate the string returned as a comparator in JS?
Thanks
Yes, you could use an object with the comparators as key and return a function for making the comparison.
var comparison = {
'<=': function (a, b) { return a <= b; },
'<': function (a, b) { return a < b; },
'==': function (a, b) { return a == b; },
'>': function (a, b) { return a > b; },
'>=': function (a, b) { return a >= b; },
default: function () { return false; }
}
Usage:
(comparison[comp] || comparison.default)(value1, value2);
There is, but unless you're using it with completely trusted data, don't use it.
Instead, implement the comparisons:
function compare(operator, operand1, operand2) {
switch (operator) {
case "<=":
return operand1 <= operand2;
case ">=":
return operand1 >= operand2;
// ...and so on...
}
}
But yes, if you fully trust the data, you can use eval:
function compare(operator, operand1, operand2) {
return eval("operand1 " + operator + " operand2");
}
(That looks like pseudo-code, but it isn't; eval evaluates the code string you give it in the context where you call it, so it has access to the operand1 and operand2 arguments.)
The "trust" part there is really, really important, because eval allows executing any script code. If you're allowing Bob to provide data that will eventually be evaluated in a browser by Alice, don't use eval. It's a major security risk to Alice.
You can use eval function
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
var a = 10;
var b = 20;
var comparator = '<=';
var result = eval(a + comparator + b)
// true
Better approach would be to write your own compare method and keep results/codes on DB something like
function compare(a, b):
if( a < b ) return -2;
else if (a <= b) return -1;
else if ( a == b) return 0;
//...
and then keep values of 0, -1, -2 on db
Now, if you want to apply some comparator from your DB you just call compare on your string and check whether the result is the same as code from DB or not (and it will give you True or False)
The only problem here is that you need to fire all comparisions on dataset but it is completely safe and also will handle some inappropriate operators (that cannot be applied for a strings) - the only requirement is that code on DB must be a key
You can use jquery ajax call to retrieve the string operator from server side code and then evaluate the string for comparison in javascript code.
<script>
$(document).ready(function(){
$.ajax({url: "retrieveOperator.php", success: function(result){
switch(String(result)) {
case "==":
code block for == operator
break;
case ">=":
code block for >= operator
break;
default:
default code block
}
});
});
</script>
Related
I'm attempting to allow user defined logic criteria. Since the user definitions are basically strings, I'm trying to make the code efficient by avoiding string comparison. For example, a simple test like a == 10 would be represented by an array
var userDef = ['a', '==', 10]
To make the logic evaluation efficient, I'd like to create an object like the ffg:
var binaryOperator = {'==': 0, '>': 1, '<': 2}
.. so when I mine the array, I could do e.g.
if(binaryOperator[userdef[1]] == 0)
{
return (obj[userDef[0]] == userDef[2])
}
... where of course obj.a = something. The above code, on the face of it, avoids string comparison.
But does it really? Is the reference binaryOperator[userdef[1]] really fast or does it also involve string comparison somewhere?
In other words is there a performance difference between thisObj.a and thisObj['a']?
You could omit the check for the operator and take a function as value, like
var binaryOperator = {
'==': function (a, b) { return a === b; },
'<': function (a, b) { return a < b; },
'>': function (a, b) { return a > b; }
};
Objects in Javascript have an access of O(1).
Further readings:
Performance of key lookup in JavaScript object, JS engine desings
JavaScript object structure: speed matters, by using a fully defined object instead of adding properties at run time.
I have an array of strings I need to sort in JavaScript, but in a case-insensitive way. How to perform this?
In (almost :) a one-liner
["Foo", "bar"].sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
Which results in
[ 'bar', 'Foo' ]
While
["Foo", "bar"].sort();
results in
[ 'Foo', 'bar' ]
It is time to revisit this old question.
You should not use solutions relying on toLowerCase. They are inefficient and simply don't work in some languages (Turkish for instance). Prefer this:
['Foo', 'bar'].sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'}))
Check the documentation for browser compatibility and all there is to know about the sensitivity option.
myArray.sort(
function(a, b) {
if (a.toLowerCase() < b.toLowerCase()) return -1;
if (a.toLowerCase() > b.toLowerCase()) return 1;
return 0;
}
);
EDIT:
Please note that I originally wrote this to illustrate the technique rather than having performance in mind. Please also refer to answer #Ivan Krechetov for a more compact solution.
ES6 version:
["Foo", "bar"].sort(Intl.Collator().compare)
Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Collator/compare
arr.sort(function(a,b) {
a = a.toLowerCase();
b = b.toLowerCase();
if (a == b) return 0;
if (a > b) return 1;
return -1;
});
You can also use the new Intl.Collator().compare, per MDN it's more efficient when sorting arrays. The downside is that it's not supported by older browsers. MDN states that it's not supported at all in Safari. Need to verify it, since it states that Intl.Collator is supported.
When comparing large numbers of strings, such as in sorting large arrays, it is better to create an Intl.Collator object and use the function provided by its compare property
["Foo", "bar"].sort(Intl.Collator().compare); //["bar", "Foo"]
If you want to guarantee the same order regardless of the order of elements in the input array, here is a stable sorting:
myArray.sort(function(a, b) {
/* Storing case insensitive comparison */
var comparison = a.toLowerCase().localeCompare(b.toLowerCase());
/* If strings are equal in case insensitive comparison */
if (comparison === 0) {
/* Return case sensitive comparison instead */
return a.localeCompare(b);
}
/* Otherwise return result */
return comparison;
});
Normalize the case in the .sort() with .toLowerCase().
You can also use the Elvis operator:
arr = ['Bob', 'charley', 'fudge', 'Fudge', 'biscuit'];
arr.sort(function(s1, s2){
var l=s1.toLowerCase(), m=s2.toLowerCase();
return l===m?0:l>m?1:-1;
});
console.log(arr);
Gives:
biscuit,Bob,charley,fudge,Fudge
The localeCompare method is probably fine though...
Note: The Elvis operator is a short form 'ternary operator' for if then else, usually with assignment.
If you look at the ?: sideways, it looks like Elvis...
i.e. instead of:
if (y) {
x = 1;
} else {
x = 2;
}
you can use:
x = y?1:2;
i.e. when y is true, then return 1 (for assignment to x), otherwise return 2 (for assignment to x).
The other answers assume that the array contains strings. My method is better, because it will work even if the array contains null, undefined, or other non-strings.
var notdefined;
var myarray = ['a', 'c', null, notdefined, 'nulk', 'BYE', 'nulm'];
myarray.sort(ignoreCase);
alert(JSON.stringify(myarray)); // show the result
function ignoreCase(a,b) {
return (''+a).toUpperCase() < (''+b).toUpperCase() ? -1 : 1;
}
The null will be sorted between 'nulk' and 'nulm'. But the undefined will be always sorted last.
arr.sort(function(a,b) {
a = a.toLowerCase();
b = b.toLowerCase();
if( a == b) return 0;
if( a > b) return 1;
return -1;
});
In above function, if we just compare when lower case two value a and b, we will not have the pretty result.
Example, if array is [A, a, B, b, c, C, D, d, e, E] and we use the above function, we have exactly that array. It's not changed anything.
To have the result is [A, a, B, b, C, c, D, d, E, e], we should compare again when two lower case value is equal:
function caseInsensitiveComparator(valueA, valueB) {
var valueALowerCase = valueA.toLowerCase();
var valueBLowerCase = valueB.toLowerCase();
if (valueALowerCase < valueBLowerCase) {
return -1;
} else if (valueALowerCase > valueBLowerCase) {
return 1;
} else { //valueALowerCase === valueBLowerCase
if (valueA < valueB) {
return -1;
} else if (valueA > valueB) {
return 1;
} else {
return 0;
}
}
}
In support of the accepted answer I would like to add that the function below seems to change the values in the original array to be sorted so that not only will it sort lower case but upper case values will also be changed to lower case. This is a problem for me because even though I wish to see Mary next to mary, I do not wish that the case of the first value Mary be changed to lower case.
myArray.sort(
function(a, b) {
if (a.toLowerCase() < b.toLowerCase()) return -1;
if (a.toLowerCase() > b.toLowerCase()) return 1;
return 0;
}
);
In my experiments, the following function from the accepted answer sorts correctly but does not change the values.
["Foo", "bar"].sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
This may help if you have struggled to understand:
var array = ["sort", "Me", "alphabetically", "But", "Ignore", "case"];
console.log('Unordered array ---', array, '------------');
array.sort(function(a,b) {
a = a.toLowerCase();
b = b.toLowerCase();
console.log("Compare '" + a + "' and '" + b + "'");
if( a == b) {
console.log('Comparison result, 0 --- leave as is ');
return 0;
}
if( a > b) {
console.log('Comparison result, 1 --- move '+b+' to before '+a+' ');
return 1;
}
console.log('Comparison result, -1 --- move '+a+' to before '+b+' ');
return -1;
});
console.log('Ordered array ---', array, '------------');
// return logic
/***
If compareFunction(a, b) is less than 0, sort a to a lower index than b, i.e. a comes first.
If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. Note: the ECMAscript standard does not guarantee this behaviour, and thus not all browsers (e.g. Mozilla versions dating back to at least 2003) respect this.
If compareFunction(a, b) is greater than 0, sort b to a lower index than a.
***/
http://jsfiddle.net/ianjamieson/wmxn2ram/1/
I wrapped the top answer in a polyfill so I can call .sortIgnoreCase() on string arrays
// Array.sortIgnoreCase() polyfill
if (!Array.prototype.sortIgnoreCase) {
Array.prototype.sortIgnoreCase = function () {
return this.sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
};
}
Wrap your strings in / /i. This is an easy way to use regex to ignore casing
I'm trying to create a dynamic function in JavaScript where I can compare one object to another, passing in the comparison operator as a string value to the function.
E.g two objects like this:
{value: 1, name: "banana"}
{value: 2, name: "apples"}
I want to compare banana to apple, is there a way I can pass a string representation of a comparison operator and then use it as an actual comparison operator in a function?
function compare (first, second, comparator) {
return first.id (comparator) second.id;
}
e.g compare(apple,banana,"<=");
//return true
compare(apple,banana,"===");
//return false
etc
Granted I could implement with a switch or if statement on the comparator string i.e.
if (comparator === "<=")
return first.id <= second.id
if (comparator === "===")
return first.id === second.id
but I wonder if there is any better more efficient way to do it that avoids the need for such a switch/if statement.
While this might be possible in some languages, JavaScript is not one of them.
Personally I think it's a bad idea, as it comes dangerously close to eval territory. I think you should whitelist the operators and define their behaviour:
switch(comparator) {
case "<=": return first.id <= second.id;
case "===": return first.id === second.id;
// ...
// you can have synonyms:
case ">=":
case "gte": return first.id >= second.id;
// or even nonexistant operators
case "<=>": // spaceship!
if( first.id === second.id) return 0;
if( first.id < second.id) return -1;
return 1;
// and a catch-all:
default:
throw new Error("Invalid operator.");
}
I have the following response I get from my server:
[
{"key":{"name":"1","kind":"a"},
{"key":{"name":"1","kind":"ap"},
{"key":{"name":"5","kind":"ap"}
]
The responses come in a different order every time as the server does not guarantee order. Thus I need to sort the response like this after I receive it:
First sort so the smallest KIND is first so 'a' should come before 'b'. I then need to make it so name of the username is the first ordered within the 'a'.
var username = '5';
var response = [
{"key":{"name":"1","kind":"a"},
{"key":{"name":"1","kind":"ap"},
{"key":{"name":"5","kind":"ap"}
];
response = entities.sort(function (a, b) {
if (a.key.kind != b.key.kind){ return a.key.kind < b.key.kind}
else if(a.key.name == username){ return a.key.name < b.key.name }
else return a.key.name > b.key.name;
});
This is the code I use to sort, but it does not work. It sorts the KIND correctly, but then when it needs to sort by NAME (username should come before other names) it does not work.
The actual result I get is equal to this:
[
{"key":{"name":"1","kind":"ap"},
{"key":{"name":"5","kind":"ap"},
{"key":{"name":"1","kind":"a"}
]
But the result I want is this:
[
{"key":{"name":"5","kind":"ap"},
{"key":{"name":"1","kind":"ap"},
{"key":{"name":"1","kind":"a"}
]
As you can see my username is equal to 5, so {"key":{"name":"5","kind":"ap"} should come before {"key":{"name":"1","kind":"ap"} .
return a.key.name < b.key.name is not saying that a < b.
They actually will be compared.
Try to replace it with return -1; to say to comparator:
a is lesser than b in that case.
When you use .sort() methods, you have to pass a compare function which will say who should comes first between two elements.
When you are comparing numbers, you can simply do :
function compare(a, b) {
return a - b;
}
this will sort an array of numbers ascending.
However, when you are comparing string, you have to define some ordering criterion, in order to tell which element should be comes first.
If compare(a, b) is less than 0, sort a to a lower index than b, so a comes first.
If compare(a, b) is greater than 0, sort b to a lower index than a, so b comes first.
If compare(a, b) is equal to 0, there is no change
So will get something like :
function compare(a, b) {
if (a is lower than b by your criteria) {
return -1;
}
if (a is greater than b by your criteria) {
return 1;
}
return 0;
}
In your case, you can write function generator that takes the property of the object to sort, and a custom sort function. This is useful when the function needs to be applied in more than one situation.
const username = '5';
const response = [
{"key":{"name":"1","kind":"a"}},
{"key":{"name":"1","kind":"ap"}},
{"key":{"name":"5","kind":"ap"}}
];
//Retrieve the value at 'props' into 'obj'
function retrieve(props, obj) {
return props.reduce((result, current) => {
result = result[current];
return result;
}, obj);
}
//Custom sort function for Kind properties
function sortKind(a, b) {
return a < b ? -1 : (a > b) ? 1 : 0;
}
//Custom sort function for name properties
function sortName(a, b) {
return a === username ? -1 : 1;
}
//Generic sort function
function sortByProp(...props) {
const callback = props.pop();
return function(a, b) {
const v1 = retrieve(props, a);
const v2 = retrieve(props, b);
return callback(v1, v2);
}
}
//Then you just have to call your sort function, and you can chain them
const res = response
.sort(sortByProp('key', 'kind', sortKind))
.sort(sortByProp('key', 'name', sortName));
console.log(res);
You can see here a Working plunker
You missed "return" in this line
else if(a.key.name == username){ a.key.name < b.key.name }
For example, can I create a method which can return me an expression that can be evaluated by if?
function getCondition(variable, value, operator)//not sure what params to pass
{
var condition = false; //initialized to false
//generate condition based on parameter passed
return condition;
}
and then use it directly
if ( getCondition( a, 5, "<" ) ){ console.log("correct") }
Yes.
In your example, which probably is not your actual use-case, you'd simply have to map your operator:
function getCondition( x, y, op ) {
switch ( op ) {
case '<':
return x < y
case '>':
return x > y
default:
throw new Error( 'operator not understood' )
}
}
if ( getCondition( 1, 5, '<' ) ) {
...
}
You might see this pattern commonly in something like a physics simulation, where you need operators that do not exist natively, such as dot or cross products. I've never seen a use-case where you'd want to pass that operator explicitly to a function though, rather, just create the functions you need for each operator.
You could pass the expression as a parameter
var a = 3.5;
function getCondition(bool) {
var condition = false;
return bool || condition
}
if (getCondition(a < 5)) {
console.log("correct")
}
You probably want to evaluate arguments when you apply the condition, not when you define it. Here's one possibility:
var operator = {};
operator.greaterThan = function(val) {
return function(x) {
return x > val;
}
};
operator.lessThan = function(val) {
return function(x) {
return x < val;
}
};
isLessThan5 = operator.lessThan(5);
a = 4;
if(isLessThan5(a)) console.log('ok'); else console.log('not ok');
b = 10;
if(isLessThan5(b)) console.log('ok'); else console.log('not ok');
For complex conditions you can also add boolean operators:
operator.and = function() {
var fns = [].slice.call(arguments);
return function(x) {
return fns.every(f => f(x));
}
};
operator.or = function() {
var fns = [].slice.call(arguments);
return function(x) {
return fns.some(f => f(x));
}
};
isBetween5and10 = operator.and(
operator.greaterThan(5),
operator.lessThan(10));
if(isBetween5and10(8)) console.log('ok')
if(isBetween5and10(15)) console.log('ok')
Yes, but you have to define in the function what the operator means. So your function needs to contain some code along the lines of:
if (operator === '>') {
condition = (value1 > value2);
}
You could also use string concatenation and eval, but I wouldn't recommend it:
condition = eval(value1 + operator + value2);
Yes, you can use the return value of a method if it can be evaluated to either true or false.
The sample code you provided should work as you expect it.
The return value of the method can also be evaluated from an int or a string to a boolean value. Read more about that here: JS Type Coercion
It is possible to pass a function or expression to an if. Like you're saying yourself, an if accepts an expression... that evaluates to either true or false. So you can create any function or method that returns a boolean value (not entirely true in PHP and other weak typed languages).
Clearly, since PHP isn't strongly typed, no function guarantees that it returns a boolean value, so you need to implement this properly yourself, as doing this makes you prone to getting errors.