Bug when attempting to time my JavaScript functions - javascript

I was given the challenge which is essentially: given an array of numbers as input, find the greatest difference between any two numbers, given that j < i, and input[i] > input[j].
I wrote two solutions, one a brute-force double nested loop and the other my attempt at optimizing it. They are as follows:
// Brute Force Method
function findGreatestDiffV1(nums) {
let start = new Date().getTime();
let greatestDiff = 0;
for (let i = 1; i < nums.length; i++) {
for (let j = 0; j < i; j++) {
let diff = nums[i] - nums[j];
if (diff > greatestDiff) greatestDiff = diff;
}
}
console.log(((new Date().getTime() - start) / 1000.0) + " Seconds");
return greatestDiff;
}
// Optimized method
function findGreatestDiffV2(nums) {
let start = new Date().getTime();
let smallestNum = nums[0];
let greatestDiff = 0;
for (let i = 1; i < nums.length; i++) {
if (nums[i] < smallestNum) smallestNum = nums[i];
if ((nums[i] - smallestNum) < greatestDiff) continue;
for (let j = 0; j < i; j++) {
let diff = nums[i] - nums[j];
if (diff > greatestDiff) greatestDiff = diff;
}
}
console.log(((new Date().getTime() - start) / 1000.0) + " Seconds");
return greatestDiff;
}
These work fine, and output both the correct results as well as the expected run-times, i.e. significantly lower for V2 for larger datasets.
I then modified them to return only the run-times:
function findGreatestDiffV1Time(nums) {
let start = new Date().getTime();
let greatestDiff = 0;
for (let i = 1; i < nums.length; i++) {
for (let j = 0; j < i; j++) {
let diff = nums[i] - nums[j];
if (diff > greatestDiff) greatestDiff = diff;
}
}
return ((new Date().getTime() - start) / 1000.0);
}
function findGreatestDiffV2Time(nums) {
let start = new Date().getTime();
let smallestNum = nums[0];
let greatestDiff = 0;
for (let i = 1; i < nums.length; i++) {
if (nums[i] < smallestNum) smallestNum = nums[i];
if ((nums[i] - smallestNum) < greatestDiff) continue;
for (let j = 0; j < i; j++) {
let diff = nums[i] - nums[j];
if (diff > greatestDiff) greatestDiff = diff;
}
}
return ((new Date().getTime() - start) / 1000.0);
}
I created this helper function, which generates inputs of length numOfDiffs and of greatest individual number maxDiff:
function generateDiffs(numOfDiffs, maxDiff) {
let diffs = [];
for (let i = 0; i < numOfDiffs; i++) {
diffs.push(Math.floor(Math.random() * maxDiff));
}
return diffs;
}
However, this is the point where things are bugging out and I cannot figure out why.
When using the versions which only return the run-time, regardless of the size of the input, I'm getting a time of 0 for V1. V2 is returning the correct time for any size set, but V1 continues to escape almost immediately and return 0.
The most likely issue is that I have an obvious bug somewhere, but I'm wondering if it might be some misunderstanding of how JavaScript performs some function, or even scope bleed somewhere. But regardless, I can't locate it and it's driving me crazy.

So, #Barmar was the man here. Chrome absolutely was attempting to optimize my code by skipping everything it saw was never being returned by the function. Once I changed my return value to
return [((new Date().getTime() - start) / 1000.0), greatestDiff];
I began getting the expected results.
And it is definitely interesting that I wasn't getting this for V2; as Barmar said, it's likely that the more complex instructions were causing Chrome to err on the side of caution.
Also, for the record, here was the function I wrote to calculate the run-times.
function runTimes(numOfDiffs, maxDiff) {
let runTimeDiffs = generateDiffs(numOfDiffs, maxDiff)
let timesV1 = 0;
let timesV2 = 0;
for (let x = 0; x < 10; x++) {
timesV1 += findGreatestDiffV1Time(runTimeDiffs)[0];
timesV2 += findGreatestDiffV2Time(runTimeDiffs)[0];
}
console.log(`V1 times for numOfDiffs = ${numOfDiffs} and maxDiff = ${maxDiff} was: ${timesV1 / 10} seconds.`)
console.log(`V2 times for numOfDiffs = ${numOfDiffs} and maxDiff = ${maxDiff} was: ${timesV2 / 10} seconds.`)
}
As for #gcampbell's suggestion of using performance.now, I'll look into that; I wasn't familiar with that functionality. Good to know these things :)

Related

merge_sort: Uncaught RangeError: Maximum call stack size exceeded

i am working on a sorting visualizer and i am trying to visualize the merge sorting algo but whenever i call the mergeSort() function, i always get a "maximum call stack size exceeded" error. i have tried to rectify the problem but to no avail.
document.querySelector(".merge").addEventListener("click", mergeSort);
var el = document.querySelectorAll(".bar");
var low = 0;
var high = el.length - 1
function mergeSort(){
if (low >= high){
return;
}
var mid = parseInt((low + high)/2);
mergeSort(el, low, mid);
mergeSort(el, mid + 1, high);
merge(el, low, mid, high);
}
function merge(arr, low, mid, high){
var n = mid - low + 1;
var m = high - mid;
var leftArray = new Array(n);
var rightArray = new Array(m);
for(var i = 0; i < n; i++){
leftArray[i] = arr[low + i];
}
for(var j = 0; j < m; j++){
rightArray[j] = arr[mid + 1 + j];
}
var i = 0;
var j = 0;
var k = low;
while (i < n && j < m){
if (leftArray[i].offsetHeight <= rightArray[j].offsetHeight){
arr[k].offsetHeight = leftArray[i].offsetHeight
i++;
}else{
arr[k].offsetHeight = rightArray[j].offsetHeight;
j++;
}
k++;
}
while (i < n){
arr[k].offsetHeight = leftArray[i].offsetHeight
i++;
k++;
}
while (j < m){
arr[k].offsetHeight = rightArray[j].offsetHeight;
j++;
k++;
}
}
You should define el, low and high as arguments in the function mergeSort:
function mergeSort(el, low, high) {
...
}
And you should call the function this way:
mergeSort(el, 0, el.length - 1);
The middle index can be computed with integer arithmetics:
var mid = low + ((high - low) >> 1);
Finally the merge function does not sort the array el, it modifies the offsetHeight properties of the array elements, which may or may not be relevant.

Creating an Iterative - Javascipt

I have a recursive function below and I was just wondering how can I create an iterative (i.e. loops without recursion) for the same thing. I would really appreciate any help or suggestions thank you!
function countPalindromes(string, count) {
if (string.length <= 1) {
return count;
}
let [ firstLetter ] = string;
let lastLetter = string[string.length - 1];
if (firstLetter === lastLetter) {
let stringWithoutFirstAndLastLetters = string.substring(1, string.length - 1);
return countPalindromes(stringWithoutFirstAndLastLetters, count + 1);
} else {
return count;
}
}
console.log(countPalindromes("level", 0));
console.log(countPalindromes("aya", 0));
Took me a while, but this should work.
function countPalindromes(string, count) {
for (i = 0; i < Math.floor(string.length/2); i++) {
if (string.charAt(i) === string.charAt(string.length - i-1)) {count++};
}
return count;
}
console.log(countPalindromes("level", 0));
console.log(countPalindromes("aya", 0));
You can do something like this:
function countPalindromes(string) {
let count = 0;
for (let i = 0; i < Math.floor(string.length / 2); i++) {
const letterFromTheStart = string[i];
const letterFromTheEnd = string[(string.length - 1) - i];
if (letterFromTheStart !== letterFromTheEnd) {
break;
}
count++;
}
return count;
}
console.log(countPalindromes("level"));
console.log(countPalindromes("aya"));
The important parts are i < Math.floor(string.length / 2) and (string.length - 1) - i.
The first one assures the loop stops before or at the half of the string and the second gets the nth character starting from the string's end.
I didn't get your recursive palindrome function. Just converted the recursive one to iterative one.
function countPalindromes(string, count) {
if (string.length <= 1) {
return count;
}
let i = 0, j = string.length-1;
for (; i < j && string[i] == string[j]; i++, j--);
return i;
}
console.log(countPalindromes("level", 0));
console.log(countPalindromes("aya", 0));
You can reverse the string and compare letters until you hit a mismatch returning the last index that matched plus one.
function isPalindrome(word) {
const drow = word.split('').reverse();
for (i = 0; i < drow.length && drow[i] === word[i]; i++);
return i;
}
console.log(isPalindrome("level"));

Is it possible to run a loop in segments? - JavaScript

Since I am still fairly new to js I thought it couldn't hurt to ask more experienced coders about ways to improve my coding habits and to learn efficient basics.
So im wondering if I could run, say 2 lines of code in a loop x amount of times and then x amount of times on the rest of the block.
So instead of this:
for (let i = 0; i <= 10; i++) {
this.shapes[i].x -= 1;
this.shapes[i].draw(this.ctx);
}
for (let i = 0; i <= 10; i++) {
this.shapes[i].x += 1;
this.shapes[i].draw(this.ctx);
}
Does something like this exist?
for (let i = 0; i <= 10; i++) {
//run this section i amount of times
this.shapes[i].x -= 1;
this.shapes[i].draw(this.ctx);
//then run this i amount of times
this.shapes[i].x += 1;
this.shapes[i].draw(this.ctx);
}
The only difference between the two loops' bodies seems to be one statement.
You can use some math to determine the index and some logical statements to determine if that value should be incremented or decremented, here is an example:
for (let i = 0; i <= 21; i++) {
const index = i % 11;
this.shapes[index].x += (i > 10) ? 1 : -1;
this.shapes[index].draw(this.ctx);
}
If it's exactly the same code you can refactor it like this:
for (let delta of [-1, +1]) {
for (let i = 0; i <= 10; i++) {
this.shapes[i].x += delta;
this.shapes[i].draw(this.ctx);
}
}
Another option is to use a function using delta as a parameter
changeShapeByDelta = (delta) => {
for (let i = 0; i <= 10; i++) {
this.shapes[i].x += delta;
this.shapes[i].draw(this.ctx);
}
}
changeShapeByDelta(-1);
changeShapeByDelta(+1);
Another option is to deep copy your initial shapes and restore it after the first draw.
You could ofc. define more variables then i:
for (let i = 0, j=0; i <= 10; i++, j+= 2) {
console.log(i, j);
}
Or use another var in the parent scope:
let j = 0;
for (let i = 0, j=0; i <= 10; i++, j+= 2) {
j += 2
console.log(i, j);
}
Or just use plain old if,else and break statements. Code does not have to look nice all the time.
You can loop 20 times instead of 10 times and then run first code if i<10 else run second. Below is example with simple logging.
for(let i = 0;i<22;i++){
if(i<11) console.log('first')
else console.log('second')
}

JavaScript: Improve performance inside nested loops

how can I optimize the incredibly slow code shown below to speed? I think, JS converts the ints to floats and vise-versa all the time. How can I spot such performance killers? I think, there is much space left to optimize :-). I think, there is a better way to handle the nibble values.
At first, I tried to ditch the &15 operation for the higher nibble. That just slowed more down instead of speed up.
(The code is incomplete. It is just a part taken from a bigger one. It is filling a buffer for createBufferSource(). It should be processed an played as soon as possible after a click event. Currently, on a mobile, it takes up to 2s for an average audio file.)
for (var len = adpcm.length, i = 0; i < len; i++) {
n = [
adpcm.charCodeAt(i) & 15,
(adpcm.charCodeAt(i) >> 4) & 15
];
for (var j = 0; j < 2; j++) {
s = n[j];
stp = imaStepTable[index];
diff = stp >> 3;
if (s & 4) diff += stp;
if (s & 2) diff += (stp >> 1);
if (s & 1) diff += (stp >> 2);
if (s & 8) diff = 0 - diff;
pred += diff;
if (pred > 32760) pred = 32760;
else if (pred < -32760) pred = -32760; //limiter
index += imaIndexTable[s];
if (index < 0) index = 0;
else if (index > 88) index = 88;
for (var k = 0; k < sampleScale; k++) { //resampler
if (!k) {
lPcm[c] = pred / 32768;
rPcm[c] = (c - shift) < 0 ? 0 : lPcm[(c - shift)];
} else {
lPcm[c] = lPcm[c - k];
rPcm[c] = rPcm[c - k];
}
c++;
}
}
}

Generating alphanumerical sequence javascript

I have written a terribly slow function for generating codes that go from AA000 to ZZ999 (in sequence not random). And I have concluded that there has to be a better way to do this. Any suggestions on how to make this faster?
function generateAlphaNumeric(){
theAlphabet = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
resultArrray = [];
resultArrray2 = [];
teller = 0;
for(i in theAlphabet){
for(x in theAlphabet){
resultArrray[teller] = theAlphabet[i] + theAlphabet[x];
teller++;
}
}
teller = 0;
for(x = 0; x<10; x++){
for(y = 0; y<10; y++){
for(z = 0; z<10; z++){
resultArrray2[teller] = x.toString() + y.toString() +z.toString();
teller++;
}
}
}
teller = 0;
finalArray = [];
for(index in resultArrray){
for(i in resultArrray2){
finalArray[teller] = resultArrray[index] + resultArrray2[i];
teller++;
}
}
//console.log(resultArrray);
//console.log(resultArrray2);
console.log(finalArray);
}
This should be considerably faster:
var theAlphabet = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O',
'P','Q','R','S','T','U','V','W','X','Y','Z'];
var theDigits = ['0','1','2','3','4','5','6','7','8','9'];
var result = [];
for (var i=0 ; i<26 ; i++) {
var prefix1 = theAlphabet[i];
for (var j=0 ; j<26; j++) {
var prefix2 = prefix1 + theAlphabet[j];
for(var x = 0; x<10; x++){
var prefix3 = prefix2 + theDigits[x];
for(var y = 0; y<10; y++){
var prefix4 = prefix3 + theDigits[y];
for(var z = 0; z<10; z++){
result.push(prefix4 + theDigits[z]);
}
}
}
}
}
Key ideas:
Generate everything in one run
Reuse partial strings as much as possible
However, I don't see how such an exhaustive list is useful. There are exactly 26 * 26 * 1000 different codes. So instead of maintaining an array with all codes it could make sense to simply build a function that generates the specific code requested:
function getCode(number) {
var z = number % 10;
number -= z; number /= 10;
var y = number % 10;
number -= y; number /= 10;
var x = number % 10;
number -= x; number /= 10;
var a = number % 26;
number -= a; number /= 26;
var b = number;
return theAlphabet[a] + theAlphabet[b] + theDigits[x] + theDigits[y] + theDigits[z];
}
function generate() {
var str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
array = [];
for (var i = 0; i < str.length; i++) {
for (var j = 0; j < str.length; j++) {
for (var k = 0; k < 10; k++) {
for (var l = 0; l < 10; l++) {
for (var m = 0; m < 10; m++) {
ar.push(str[i] + str[j] + k + l + m);
}
}
}
}
}
return array;
}
console.log(generate());
This will generate a array of all the codes .. U can save that array and parse it easily using a loop.
Try this solution:
function generate() {
var str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
ar = [];
for (var index1 = 0; index1 < str.length; index1++) {
for (var index2 = 0; index2 < str.length; index2++) {
for (var index3 = 0; index3 < 1000; index3++) {
ar.push(str[index1] + str[index2] + ('000' + index3).slice(-3));
}
}
}
return ar;
}
console.log(generate());
I didn't test it, but it should do the trick
function generateAlphaNumeric()
{
var theAlphabet = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'];
var result = [];
// Will take a random letter inside theAlphabet
// Math.floor(Math.random() * theAlphabet.length) will generate a random number between 0 and 25
var i = 0;
while(i<2)
{
var letter = theAlphabet[Math.floor(Math.random() * theAlphabet.length)];
result.push(letter);
i++;
}
i = 0;
while(i<3)
{
// Adds a random number between 0 and 9
result.push(Math.floor(Math.random() * 10));
i++;
}
return result;
}
From a computational complexity perspective, unfortunately this is the best you can do. From a sheer number of instructions perspective, you can do a bit better (as others have pointed out), but it's still going to be the same order of complexity (remember that constants / multipliers are irrelevant in big-O complexity). You can also optimize the storage a bit.
Think about it. Your array needs to have 26 * 26 * 10 * 10 * 10 members. This means you need to at least touch that many elements.
Let N = number of elements in the alphabet
Let M = number of elements in your digit queue
Best Case Order Complexity = O(N * N * M * M * M) (if all you had to do was assign values)
Best case storage complexity = same as above (you have to store all the codes)
Right now you are using the following operations:
for(i in theAlphabet){ // *O(N)*
for(x in theAlphabet){ // *O(N)*
resultArrray[teller] = theAlphabet[i] + theAlphabet[x];// *(O(1))*
}
}
for(x = 0; x<10; x++){ // O(M)
for(y = 0; y<10; y++){ // O(M)
for(z = 0; z<10; z++){ // O(M)
resultArrray2[teller] = x.toString() + y.toString() +z.toString(); // O(1) (technically this is O(length of x + y + z)
teller++;
}
}
}
for(index in resultArrray){ // O(N * N)
for(i in resultArrray2){ // O(M * M * M(
finalArray[teller] = resultArrray[index] + resultArrray2[i]; //O(1)
teller++;
}
}
So at the end of the day your order complexity is O(N * N * M * M * M), which is the best you can do.
The bigger question is why you want to generate all the codes at all. If all you want is to create a unique code per order number or something, you can make a state machine like:
function getNextCode(previousCode) {
// in here, just increment the previous code
}
If all you want is a random identifier, consider using a hash of the timestamp + something about the request instead.
If you don't care about uniqueness, you can always just generate a random code.
All of the above are O(1).

Categories

Resources