Profiling performance-sensitive code paths - javascript

I have a function that receives 2 arguments and 1 additional, optional argument. The function must return true if the first argument is bigger than the second one, false if not, except if the third argument is true (the third argument can be only true or false, false by default), in which case the function should return true if the first argument is either bigger or equal (strict comparison) to the second argument.
The function isn't guaranteed to receive arguments of the same type, or even arguments that make any sense (the function could be called with null, undefined). Anyways, the function must obey javascript behavior to compare the received arguments.
I have two functions, and I believe the second one should be faster, but neither my own benchmarks nor jsperf results say so. In fact, the first function is ~30-35% faster, which is quite a lot.
How can I track down the slow code paths inside each function? How can I know why the second function is slower?
This is my benchmark:
var microtime = require('microtime');
/* Helper functions */
function maybeBool() {
if(Math.round(Math.random() * 1)) {
return true;
} else {
return false;
}
}
function maybeNullUndef() {
if(Math.round(Math.random() * 1)) {
return null;
} else {
return undefined;
}
}
function randomString() {
return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5);
}
function randomDate() {
var y = Math.round(Math.random() * 100);
return new Date(y);
}
function something() {
var x = Math.round(Math.random()*3);
switch(x) {
case 0:
return maybeBool();
break;
case 1:
return maybeNullUndef();
break;
case 2:
return randomString();
break;
case 3:
return randomDate();
break;
}
}
var things_to_compare = [];
for(i = 0; i < 500000; i++) {
var a = something();
var b = something();
things_to_compare.push([a, b]);
}
/* First function */
function gtHelper(prop1, prop2, equal) {
// 'falsy' and Boolean handling
if (!prop1 || !prop2 || prop1 === true || prop2 === true) {
if ((prop1 === true || prop1 === false) && (prop2 === true || prop2 === false)) {
if (equal) {
return prop1 === prop2;
} else {
if (prop1) {
return !prop2;
} else {
return false;
}
}
}
if (prop1 === undefined || prop1 === null || prop1 === false || prop2 === true) {
return !!equal || false;
}
if (prop2 === undefined || prop2 === null || prop1 === true || prop2 === false) {
return true;
}
if (prop1 > prop2) {
return true;
}
if (prop1 < prop2) {
return false;
}
// not lt and and not gt so equality assumed-- this ordering of tests is date compatible
return equal;
}
if (prop1 > prop2) {
return true;
}
if (prop1 < prop2) {
return false;
}
// not lt and and not gt so equality assumed-- this ordering of tests is date compatible
return equal;
}
/* Second function */
function gtHelper2 (prop1, prop2, equal) {
equal = !!equal;
//If 'prop1' is any of those, the result will be always 'false',
//unless 'equal' is true.
switch (prop1) {
case "":
case null:
case false:
case undefined:
return (prop1 === prop2 && equal);
}
//If 'prop2' is any of those, the result will be always 'true'
switch (prop2) {
case "":
case null:
case false:
case undefined:
return true;
}
if (prop1 > prop2 || (prop1 === prop2 && equal)) {
return true;
} else if (prop1 < prop2) {
return false;
} else {
return equal;
}
}
/* Benchmark */
var res1 = 0;
for(n = 0; n < 30; n++) {
var now = microtime.now();
for(i = 0; i < 500000; i++) {
gtHelper(things_to_compare[i][0], things_to_compare[i][1]);
}
var now1 = microtime.now();
res1 += now1 - now;
}
var res2 = 0;
for(n = 0; n < 30; n++) {
var now = microtime.now();
for(i = 0; i < 500000; i++) {
gtHelper2(things_to_compare[i][0], things_to_compare[i][1]);
}
var now1 = microtime.now();
res2 += now1 - now;
}
console.log("gtHelper:", res1/30);
console.log("gtHelper2:", res2/30);
Edit:
I have been further working on the second function, I achieved make it a little bit faster, but it keep lagging behind the first function.
This is how it looks now:
function gtHelper2 (prop1, prop2, equal) {
//If 'prop1' is any of those, the result will be always 'false',
//unless 'equal' is true.
if (!prop1) {
return (prop1 === prop2 && !!equal);
}
//If 'prop2' is any of those, the result will be always 'true'
if (!prop2) {
return true;
}
if (prop1 > prop2) {
return true;
} else if (prop1 < prop2) {
return false;
} else if (prop1 === prop2 && !!equal) {
return true;
} else {
return !!equal;
}
}

Your two functions do not return the same thing.
gtHelper(true, 'string') // true
gtHelper2(true, 'string') // false
gtHelper('string', new Date()) // undefined
gtHelper2('string', new Date()) // false
gtHelper(new Date(), 'string') // undefined
gtHelper2(new Date(), 'string') // false
If you can get these functions behaving the same, I am sure you will see more meaningful results.
You should be aware that, in the browser at least, you should not expect switch to perform the same on all platforms. You should read the ECMA spec on switch to see why optimising this should be so difficult. I know that Firefox did spend a good deal of time on making their switch implementation perform well. I've heard nothing about anything similar on V8.

Related

Check if string exists in another string (not exactly equal)

I have this 2 strings:
var test = 'BN123';
var behaviour = 'BN***,TA****';
I need to check if behaviour contains a string with the same format as test.
On the behaviour, the BN and TA as to be equal, and the * means it can be any char. (behaviour comes from an API, so I never know what it has, this is just a test.)
In this case it should return true. Right now I'm only comparing is case behaviour as a single string, but I need to modify that:
isValidInput(behaviour, test) {
if (behaviour.length != test.length) {
return false;
}
for (var i = 0; i < behaviour.length; i++) {
if (behaviour.charAt(i) == '*') {
continue;
}
if (behaviour.charAt(i) != test.charAt(i)) {
return false;
}
}
return true;
}
You can use .some() of Array.prototype.
like below
function isValidInput(behaviour, string1) {
if (behaviour.length != string1.length) {
return false;
}
for (var i = 0; i < behaviour.length; i++) {
if (behaviour.charAt(i) == '*') {
continue;
}
if (behaviour.charAt(i) != string1.charAt(i)) {
return false;
}
}
return true;
}
var test = 'BN123';
var behaviour = 'BN***,TA****';
console.log(behaviour.split(',').some(x => isValidInput(x,test)));
console.log(behaviour.split(',').some(x => isValidInput(x,"test")));
The only issue I see with your implementation is that you're not allowing for the fact behaviour contains possible strings separated with a comma (or at least, that's how it looks to me). So you need to check each of them:
// Check one behaviour string from the list
function isOneValidInput(behaviour, string) {
if (behaviour.length != string.length) {
return false;
}
for (var i = 0; i < behaviour.length; i++) {
// Note we can easily combine those conditions, and use []
// with strings
if (behaviour[i] != '*' && behaviour[i] != string[i]) {
return false;
}
}
return true;
}
// Check all behaviour strings in a comma-separated one
function isValidInput(behaviours, string) {
return behaviours.split(",").some(function(behaviour) {
return isOneValidInput(behaviour, string);
});
}
var string = 'BN123';
var behaviour = 'BN***,TA****';
console.log(isValidInput(behaviour, string));
(I stuck to ES5 there because you seemed to be doing so.)
Is this what you want?
var test = 'BN123';
var behaviour = 'BN***,TA****';
var behaviours = behaviour.split(',');
var result = behaviours.map(b => {
if (b.length != test.length)
return false;
var pattern = b.split('*')[0];
return pattern === test.substring(0,pattern.length);
}).find(r => r === true) > -1;
console.log(result)
You can use the new .includes() method to see if a string is contained within another. Note that this is case sensitive. I have included two dodgied up behaviours to check the string against.
var string = 'BN123';
var behaviour1 = 'BN123,TA1234';
var behaviour2 = 'BN120,TA1230';
function testStr(behaviour,string) {
return behaviour.includes(string);
}
console.log(testStr(behaviour1,string)) // gives true
console.log(testStr(behaviour2,string)) // gives false
isValidInput(behaviour, string) {
var array = behaviour.split(",");
var flag = 0;
for(var i = 0;i< array.length;i++){
var now = array[i];
var _flag = 1;
if (now.length == string.length) {
for (var j = 0; j < now.length; j++) {
if (now.charAt(j) == '*') {
continue;
}
if (now.charAt(j) != string.charAt(j)) {
_flag = 0;
}
}
flag |= _flag;
}
}
return flag;
}
Try modify your behaviour to RegExp:
function checkFn(testStr) {
var behaviour = '(BN...)|(TA....)'
var r = new RegExp('^(' + behaviour + ')$')
return r.test(testStr)
}
checkFn('BN123') // true
checkFn('BN12') // false
checkFn('BN1234') // false
checkFn('TA1234') // true
checkFn('TA123') // false
checkFn('TA12345') // false
Or use this fn:
function checkFn(testStr) {
var behaviour = 'BN***,TA****'
behaviour = behaviour
.split(',')
.reduce((r, el) => {
r.push('(' + el.replace(/\*/g, '.') + ')')
return r
}, [])
.join('|')
var r = new RegExp('^('+behaviour+')$')
return r.test(testStr)
}

Cannot read property length null error when used with regular expressions

I'm a javascript beginner doing some CodeWars.com questions. I came across this question and I'm stuck due to a "cannot read property length null" error. I've tried to look up that error and can't find what the problem is in my program.
The assignment is:
"Check to see if a string has the same amount of 'x's and 'o's. The method must return a boolean and be case insensitive. The string can contains any char."
And this is what I've written so far:
function XO(str) {
var x = "x";
var o = "o";
var numX = str.match(/x/gi).length;
var numO = str.match(/o/gi).length;
while(str.indexOf(x) > -1 || str.indexOf(o) > -1) {
if(numX == numO){
return true;
}
}
if (numX === -1 && numO === -1){
return true;
}
}
XO("xoxo");
The assignment also says that if there is neither an X or an O then the program should return true.
This will not give you that error. When there are no matches, the match function returns null and you cannot get the length of null. A few extra lines solves this issue.
function XO(str) {
var x = "x";
var o = "o";
var numX = 0;
var numO = 0;
var xMatch = str.match(/x/gi);
var oMatch = str.match(/o/gi);
if (xMatch) {
numX = xMatch.length;
}
if (oMatch) {
numO = oMatch.length;
}
while(str.indexOf(x) > -1 || str.indexOf(o) > -1) {
if(numX == numO){
return true;
} else {
return false;
}
}
if (numX === -1 && numO === -1){
return true;
} else {
return false;
}
}
console.log(XO("ddd"));
I think you are making this problem more complex than it has to be.
All you need to do is make the string lowercase(to account for case insensitive), traverse the string, and when it finds an x, add 1 to a counter, and when you find and o, decrease 1 from the counter.
If it ends at 0, you return true, else you return false. There's no need for regexes
function XO(str){
var count = 0;
str = str.toLowerCase();
for(var i = 0; i < str.length; i++){
if(str[i] === 'x') count++;
if(str[i] === 'o') count--;
}
return count === 0 ? true : false;
}
Yes you have to check the return value of match is not null before checking the length property. However
while(str.indexOf(x) > -1 || str.indexOf(o) > -1) {
if(numX == numO){
return true;
}
}
looks like an infinite loop if either string contains lower case 'x' or 'o' and there are a different number of each.
More simply:
function XO(str)
{ var matchX = str.match(/x/gi);
var matchY = str.match(/o/gi);
return (matchX && matchY) ? matchX.length == matchY.length : !matchX && !matchY;
}

Why indexOf in javascript not working?

I'm not sure what I'm doing wrong here. The first instance that I use indexOf it works perfectly fine, but when I use it the second time it's not returning the result that I'm expecting.
function mutation(arr) {
//return arr;
res = "";
for (var x=0; x<arr[1].split("").length; x++) {
if (arr[0].indexOf(arr[1].split("")[x]) !== -1) {
res += "t";
} else {
res += "f";
}
}
// res = ttt
if (res.indexOf("f") !== -1) {
return true;
} else {
return false;
}
}
mutation(["hello", "hey"]);
// this returns true instead of false
mutation(["floor", "loo"]);
// returns false instead of true
mutation should return false if an element from arr[1] is not present in arr[0] else return true.
your code isn't working because when you say:
res.indexOf("f") != -1
this means: "I found an f", but you're treating it as if it means "I did not find an f".
In your case that you want to return false if you find an 'f', but you're returning true. Flip your true and false cases:
if (res.indexOf("f") != -1) {
return false;
} else {
return true;
}
ALSO your for loop is wrong because x starts at 0, so you need to go to < length not <= length of your string.
for (var x=0; x < arr[1].split("").length; x++) {
and now your code works as you wanted it to.
Just edited your code. Click on the <p> to check:
function mutation(arr) {
//return arr;
res = "";
for (var x=0; x< arr[1].split("").length; x++) {
res += arr[0].indexOf(arr[1].split("")[x]) > -1 ? 't' : 'f';
}
return res.indexOf('f') > -1;
}
$('p').click(function(){
alert(mutation(["hello", "hey"]));
alert(mutation(["floor", "loo"]));
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<p>Click me</p>
If you simplify the logic a bit, that's easier to check:
function mutation(arr) {
return arr[1].split('').reduce(function(res, x) {
return arr[0].indexOf(x) >= 0;
}, true);
}
Thanks Leon for the correction.
I tried to not chance your logic, the mistake are:
You're trying to compare with all characters on the array[0], not only the first.
If you find a character equals on the first character on array[0] you should return true.
Correct code:
function mutation(arr) {
res = "";
for (var x=0; x<=arr[1].split("").length; x++) {
if (arr[0].split("")[0].indexOf(arr[1].split("")[x]) !== -1) {
return true;
}
}
return false;
}

JavaScript: Check to see if a variable or object exists

I have a function to return if a variable/object is set or not:
function isset() {
var a = arguments, l = a.length;
if (l === 0) { console.log("Error: isset() is empty"); }
for (var i=0; i<l; i++) {
try {
if (typeof a[i] === "object") {
var j=0;
for (var obj in a[i]) { j++; }
if (j>0) { return true; }
else { return false; }
}
else if (a[i] === undefined || a[i] === null) { return false; }
}
catch(e) {
if (e.name === "ReferenceError") { return false; }
}
}
return true;
}
For example, this works:
var foo;
isset(foo); // Returns false
foo = "bar";
isset(foo); // Returns true
foo = {};
isset(foo); // Returns false
isset(foo.bar); // Returns false
foo = { bar: "test" };
isset(foo); // Returns true
isset(foo.bar); // Returns true
Here is the problem... if foo is never set to begin with, this happens:
// foo has not been defined yet
isset(foo); // Returns "ReferenceError: foo is not defined"
I thought I could use try/catch/finally to return false if error.name === "ReferenceError" but it isn't working. Where am I going wrong?
Edit:
So the answer below is correct. As I expected, you cannot access an undefined variable or trap it with try/catch/finally (see below for an explanation).
However, here is a not so elegant solution. You have to pass the name of the variable in quotes, then use eval to do the checking. It's ugly, but it works:
// Usage: isset("foo"); // Returns true or false
function isset(a) {
if (a) {
if (eval("!!window."+a)) {
if (eval("typeof "+a+" === 'object'")) { return eval("Object.keys("+a+").length > 0") ? true : false; }
return (eval(a+" === undefined") || eval(a+" === null") || eval(a+" === ''")) ? false : true;
}
else { return false; }
}
else { console.log("Empty value: isset()"); }
}
And just to follow up some more, I cleaned up the original function at the very top. It still has the same problem where if the variable doesn't exist you get a ReferenceError, but this version is much cleaner:
// Usage: isset(foo); // Returns true or false if the variable exists.
function isset(a) {
if (a) {
if (typeof a === "object") { return Object.keys(a).length > 0 ? true : false; }
return (a === undefined || a === null || a === "") ? false : true;
}
else { console.log("Empty value: isset()"); }
}
You just can't do that type of check with a function. In order to pass the variable, it needs to exist, so it will fail before your code can run.
When you call it on the undeclared variable, you're attempting to resolve the value of the identifier in the argument position.
// v----resolve identifier so it can be passed, but resolution fails
isset(foo);
And of course, it doesn't exist, so the ReferenceError is thrown.
JavaScript doesn't have pointers, so there's nothing like a nil pointer that can be passed in its place.
You cannot pass a identifier that hasn't been initialised. You could pass a string, and an object to test, like the following:
function isset(str, obj) {
return obj[str] ? true : false;
}
isset("foo", window); // >>> false

How to get nearest key index in associative JavaScript array if key does not exist?

So, this might be a weird thing to try to do, but I'm curious if it's possible:
Say I have an associative array like this:
myarray[50] = 'test1'
myarray[100] = 'test2'
I can access 'test1' by it's key, of course:
myarray[50]; // returns 'test1'
But is there a way where if I have an index key of '60', that I can look in the array and if key 60 isn't there, get the value of the next "closest" key, '50'?
The use-case for this is that I am trying to set up cue-points for a video, and if the user seeks and misses a cue point, I want to display the information from the last cue point the user seeked beyond.
I think I can check for the existence of the key with the 'in' operator. But if it's not found, how can I get the "previous" or "next smallest" array key that DOES exist?
I assume the only way to do this is to iterate through the array, saving the "last" index value until the exit condition of "index > myKey" is found. The thing is, if it's a long video with lots of queue points and the user seeks frequently, iterating through the entire array of cue points each time might be slow. Is there a better, faster way to do this?
You'd have to write your own function:
function getClosestTo(val, array) {
if (array[val] !== undefined) {
return val;
} else {
var upper = val;
var upperMatched = false;
var lower = val;
var lowerMatched = false;
while(upper < this.length) {
if (array[++upper] !== undefined) {
upperMatched = true;
break;
};
};
while(lower > -1) {
if (array[--lower] !== undefined) {
lowerMatched = true;
break;
};
};
if (upperMatched && lowerMatched) {
return upper - val < val - lower ? upper : lower;
} else if (upperMatched) {
return upper;
} else if (lowerMatched) {
return lower;
};
};
return -1;
};
You could also add this as a method of the Array prototype, to make (what I think) is more readable:
Array.prototype.getClosestTo = function (val) {
if (this[val] !== undefined) {
return val;
} else {
var upper = val;
var upperMatched = false;
var lower = val;
var lowerMatched = false;
while(upper < this.length) {
if (this[++upper] !== undefined) {
upperMatched = true;
break;
};
};
while(lower > -1) {
if (this[--upper] !== undefined) {
lowerMatched = true;
break;
};
};
if (upperMatched && lowerMatched) {
return upper - val < val - lower ? upper : lower;
} else if (upperMatched) {
return upper;
} else if (lowerMatched) {
return lower;
};
};
return -1;
};
// Usage:
// var closestKey = theArray.getClosestTo(50);

Categories

Resources