Function slows down when working with high numbers - javascript

I've wrote a script that instead of giving the real average of a set of data returns a windows that contains most data points. Let me show some code:
time.tic()
var selectedAverage = 0;
var highestPointCount = 0;
for (var i = 1; (i*step) <= maxValue; i++) {
var dataPointCount = 0;
for (var j = 0; j < myArray.length; j++) {
if (myArray[j] >= minValue+(i-1)*step && myArray[j] <= minValue+i*step) {
dataPointCount++;
}
}
if (dataPointCount > highestPointCount) {
highestPointCount = dataPointCount;
selectedAverage = (minValue+(i-1)*step)+Math.round(0.5*step);
}
}
console.log(time.toct().ms)
return selectedAverage;
First the step value is calculated by subtracting the minimum value from the maximum value and then deciding by 10. So there are 10 'horizontal' windows. Then the script counts the amount of datapoint within each window and returns a appropriate average.
It appears however that script slows down extremely (sometimes more than 200 times) when an array of larger numbers is passed in (1.000.000 for example). Array lengths are roughly 200 but always the same length so it must be associated with the actual values. Any idea where it is going wrong?
EDIT:
The code to get the step value:
var minValue = myArray.min();
var maxValue = myArray.max();
var step = Math.round((maxValue-minValue)/10);
if (step === 0) {
step = 1
}
The .min() and .max() are prototypes attached to Array. But this all goes very fast. I've measured every step and it is the for loop that slows down.

If I understood your algorithm correctly, this should remove all unnecesary calculations and be much faster:
var arr = [];
var maxQty=0;
var wantedAverage = 0;
for (var j = 0; j < 11; j++) {
arr[j]=0;
}
for (var j = 0; j < myArray.length; j++) {
var stepIndex = Math.floor((myArray[j]-minValue)/step)
arr[stepIndex]+=1;
if(arr[stepIndex] > maxQty ){
wantedAverage = minValue + stepIndex*step +Math.round(0.5*step);
maxQty = arr[stepIndex]
}
}
console.log(maxQty, wantedAverage)
We just iterate over each element of the array only once, and calculate the index of the window it belongs to, adding one to the quantity array. Then we update the wantedAverage if we have a bigger amount of points in window found

There are 2 different things I think of your issue:
Removed unnecessary / repeated calculation
Inside your nested code you have minValue+(i-1)*step and minValue+i*step calculated everytime for the same value of minValue, i and step.
You should pull it up before the 2nd for-loop where it becomes:
var dataPointCount = 0;
var lowerLimit = minValue+(i-1)*step;
var higherLimit = minValue+1*step;
for (var j = 0; j < myArray.length; j++) {
if (myArray[j] >= lowerLimit && myArray[j] <= higherLimit) {
dataPointCount++;
}
}
You got severe performance hit when you are handling big data array are likely caused by memory swapping. From your point of view you are dealing with a single array instance, however when you have such a big array it is unlikely the JavaScript VM has access to consecutive memory space to hold all those values. It is very likely JavaScript VM has to juggle multiple memory blocks that it gets from the operating system and have to spend extra effort to translate which value is where during reading/writing.

Related

a question discerning space complexity from newbie

Can someone explain why the space complexity of this algo is O(n) and not O(1)?
function subtotals(array) {
var subtotalArray = Array(array.length);
for (var i = 0; i < array.length; i++) {
var subtotal = 0;
for (var j = 0; j <= i; j++) {
subtotal += array[j];
}
subtotalArray[i] = subtotal;
}
return subtotalArray;
}
You're creating a new element in subtotalArray for every item in the array parameter. So if you have 1000 items in the input array, the output array will require a certain amount of memory, let's say X. If you have 100,000 items in the input array, the output array will require 100* X memory, or thereabouts.
(There's also the subtotal number that gets created on every iteration)

Why won't my function work when I use splice?

I am trying to write a function which should calculate all prime numbers up to an input parameter and return it. I am doing this for practice.
I wrote this function in a few ways but I was trying to find new ways to do this for more practice and better performance. The last thing I tried was the code below:
function primes(num){
let s = []; // sieve
for(let i = 2; i <= num; i++){
s.push(i);
}
for(let i = 0; i < s.length; i++) {
for(let j = s[i]*s[i]; j <= num;) {
//console.log(j);
if(s.indexOf(j)!= -1){
s.splice(s.indexOf(j), 1, 0);
}
j+=s[i];
}
}
s = s.filter(a => a != 0);
return s;
}
console.log(primes(10));
The problem is that when I run this in a browser it keeps calculating and won't stop and I don't know why.
Note: when I comment out the splice and uncomment console.log(j); everything works as expected and logs are the things they should be but with splice, the browser keep calculating and won't stop.
I am using the latest version of Chrome but I don't think that can have anything to do with the problem.
Your problem lies in this line:
s.splice(s.indexOf(j), 1, 0);
Splice function third argument contains elements to be added in place of the removed elements. Which means that instead of removing elements, you are swapping their values with 0's, which then freezes your j-loop.
To fix it, simply omit third parameter.
function primes(num){
let s = []; // sieve
for(let i = 2; i <= num; i++){
s.push(i);
}
for(let i = 0; i < s.length; i++) {
for(let j = s[i]*s[i]; j <= num;) {
//console.log(j);
if(s.indexOf(j)!= -1){
s.splice(s.indexOf(j), 1);
}
j+=s[i];
}
}
return s;
}
console.log(primes(10));
Your problem is in this loop:
for(let j = s[i]*s[i]; j <= num;)
This for loop is looping forever because j is always less than or equal to num in whatever case you're testing. It is very difficult to determine exactly when this code will start looping infinitely because you are modifying the list as you loop.
In effect though, the splice command will be called setting some portion of the indexes in s to 0 which means that j+=s[i] will no longer get you out of the loop.

How come it doesn't slice a number until the end despite including the number's length

I made a code to extract every odd numbers from one number, and it works for numbers that are not too long such as "1341" (which give me the numbers "1,13,1341,341,41,1") but oddly doesn't work for very long numbers.
function solve(s) {
var newarray = [];
for (var i = 0; i <= s.length; i++) {
for (var j = 0; j <= s.length; j++) {
var slicing = s.slice(i, j);
if (slicing % 2 !== 0) {
newarray.push(slicing);
}
}
}
return newarray.length;
}
Despite putting s.length, it slices until a certain point. For example:
With "93711892377292643581488317", it slices until "9371189237729", then when it starts from 3 it slices until "93711892377292643" (until the next odd number)
With "65266112954758467", from the start it slices until "6526611295475", then when it starts from 5, it slices until "65266112954758467" (until the next odd number).
What's going on?
slicing % 2 doesn't work properly when slicing is large. Javascript treats large numbers as floating-point numbers, which means it's not accurate to know the value to the nearest integer - in binary, the units bit becomes 0, so it's a multiple of 2.
You want to count all odd numeric substrings within a numeric string.
First, consult the documentation of str.slice(beginIndex[, endIndex]).
Then, in order to gain a better understanding of your code, it is helpful to slowly iterate through a few steps of your loops and write down the expected vs. the observed output.
I recommend to use the debugger built into all modern browsers:
Add a debugger; statement into the inner for-loop:
function solve(s) {
var newarray = [];
for (var i = 0; i <= s.length; i++) {
for (var j = 0; j <= s.length; j++) {
var slicing = s.slice(i, j);
debugger; // <-- we want to break here and check our values
if (slicing % 2 !== 0) {
newarray.push(slicing);
}
}
}
return newarray.length;
}
Press [F12] and run this code in your browser's console for some exemplary input.
The debugger tab should now pop up. Press [F8] to step through your code and keep track of the value of your slicing variable.
You will probably notice that slicing is empty at the beginning. You should start your inner loop from j = i + 1 to fix that.
Also, you might notice that your i iterates one time too many, so that slicing is empty during the final iterations of the inner for-loop. You need to terminate your outer loop one step earlier.
Then, for the problematic input "93711892377292643581488317" you will notice that large numeric slices such as "93711892377292643" will not be recognized as odd. "93711892377292643" % 2 evaluates to 0 instead of 1. In order to understand this, you need to know that JavaScript numbers are internally represented as limited precision floating point values. If you put 93711892377292643 into your browser console, it will evaluate to 93711892377292640 - an even number! JavaScript can only handle integer numbers up to Number.MAX_SAFE_INTEGER == 9007199254740991 without introducing such truncation errors.
Now, how to solve this issue? Well, a number is odd if and only if the last digit is odd. So we don't have to inspect the full number, just the last digit:
function solve(s) {
var newarray = [];
for (var i = 0; i < s.length; i++) {
for (var j = i; j < s.length; j++) {
var digit = s.slice(j, j + 1);
if (digit % 2 !== 0) {
var slicing = s.slice(i, j + 1);
newarray.push(slicing);
}
}
}
return newarray.length;
}
console.log(solve("1234567890")); // 25
Once you have sufficient understanding of this code, you could start improving it. You could for example replace the newarray with a simple counter, as you are only interested in the number of off digits, not the digits themselves.
A faster solution could be written down as follows:
function solve(str) {
let count = 0;
for (let i = 0; i < str.length; i++) {
if (str[i] % 2) count += i + 1;
}
return count;
}
console.log(solve("1234567890")); // 25
Or, written in a more declarative way:
const solve = (str) =>
str.split('').reduce((count, chr, i) => chr % 2 ? count + i + 1 : count, 0);
console.log(solve("1234567890")); // 25

Is creating a new array in a loop in Javascript a bad practice?

Sometimes we need to use temporary array in a loop to store data. For example, when we need to deal with 2 dimension arrays. But I am not sure if it is a bad practice to create a new array in a loop, especially if I need to do it often, such as in animation.
for (let i = 0; i < 10000; i++) {
const temp = [];
for (let j = 0; j < 10; j++) {
temp.push(j);
}
arr.push(temp);
}
If this is a variable I should be able to use a global variable and reassign value to it. So I have tried to use a global array and clear the array using temp.length = 0 but then because the array's reference is stored the data will all become the last pushed values. I have also tried a global const temp = new Set() but then when I push the temp set into the arr array it will be arr.push([...temp]). So is it inevitable to create new array in such situation?
Typed arrays
TypedArrays should always be the first option when you need an array. They offer massive performance and memory benefits over standard arrays.
The quickest way to create an array of numbers is
var i,j,b,a = [];
for (i = 0; i < 100000; i += 1) {
a[i] = b = new Float64Array(10);
for (j = 0; j < 10; j += 1) {
b[j] = j;
}
}
Which benchmarked at 634, a huge 5.5 times quicker than the fastest Array method
var i,j,b,a = [];
for (i = 0; i < 100000; i += 1) {
a[i] = b = [];
for (j = 0; j < 10; j += 1) {
b[j] = j;
}
}
Which benchmarked 3664 which is another 3.4 times quicker than
const arr = [...Array(10000)];
for (let i = 0; i < arr.length; i ++) {
arr[i] = [...Array(10).keys()]
}
That benchmarks a sad 12352
Typed arrays are fixed size, and when declared they are pre filled with zero. They will never become sparse and if use well can eliminate GC overheads.
With Atomics and you can share them with workers via SharedArrayBuffers(True sharing a shared array only has one address)
Though they are limited to Doubles, floats, signed and unsigned integers, they can hold any type of data (computers are just binary processing machines after all) and will give noticeable performance increases for any type of array work
Is creating arrays in loops bad.
There nothing wrong with creating arrays in loops.

is there any significant performance difference between this 2 methods of for-loop usage?

Just wondering what is the difference in the following 2 methods?
var a = 0;
var b = 0;
var c = 0;
for(var i = 0; i < 6; i++ ){
a+=i;
b+=i;
c+=i;
}
and
var a = 0;
var b = 0;
var c = 0;
for(var i = 0; i < 6; i++ ){
a+=i;
}
for(var i = 0; i < 6; i++ ){
b+=i;
}
for(var i = 0; i < 6; i++ ){
c+=i;
}
*edited thanks locrizak for the correction
The second one is doing 3X the amount of iterations it needs to. In the second one there are 18 iterations through the loops while the first there is only 6 making the script run faster. (In these circumstances you will not notice a difference because you are not doing much in the loops but once you want to do more it will be a performance issue)
Ps. a+i isn;'t doing anything you probably want a+=i
When you are in doubt about JavaScript peformance, have an objective opinion:
http://jsperf.com/testing-snippets
Well, you are doing 3 times the work.
In the grand of scheme of things, 18 iterations of what your doing isn't going to have much impact, however comparitively it is much worse.
Without knowing any of the details of the javascript interpreter, assume that the second code block is marginally worse than the first, but definitely not worth refactoring if it makes the code harder to read. Think about it this way:
for(var i = 0; i < 6; i += 1) {
doSomeExpensiveThing(); // takes 500ms to process
doSomeExpensiveThing();
doSomeExpensiveThing();
}
And:
for(var i = 0; i < 6; i += 1) {
doSomeExpensiveThing(); // takes 500ms to process
}
for(var i = 0; i < 6; i += 1) {
doSomeExpensiveThing();
}
for(var i = 0; i < 6; i += 1) {
doSomeExpensiveThing();
}
The two are going to be effectively identical because the overhead of running the loop will disappear compared to the cost of doing the inner computation.
I disagree with locrizak. The second is definitely not doing 3 times the work since each loop has a third of the statements as the first example. The extra work on the second example is that it needs to run the loop iteration steps 2 times as often as the first example.
initalize i (only once)
increment i (every iteration)
check if i < 6; (every iteration)
Therefore, in any real world example where the loop has more statements and the loop overhead gets smaller and smaller, you're very unlikely to notice a difference. That means you shold use multiple loops if it makes the code more readable.
I created this more real-life example to prove my point that both loops are, for most intents and purposes, the same: http://jsperf.com/testing-snippets/3
The only difference is the number of assignments, additions and comparisons on the variable i - and unless you're programming for a 1970s embedded computer (which you're not, as this is JavaScript), the speed difference is effectively zero; do not waste time on trying to nanooptimize it (e.g. the speed of computers makes this a non-issue, and modern JS interpreters may compile it anyway, so the difference is lost altogether).
If you're doing anything meaningful inside those loops, that will be your bottleneck, not the looping itself.
The only significant difference is maintenance - so use whichever form is easier to understand for the next person down the line who will inherit this.
Paste it into the firebug console and time it. Making it work with large loops and you can see the differences easier.
console.time("group");
for(var x = 0; x<1000; x++){
var a = 0;
var b = 0;
var c = 0;
for(var i = 0; i < 6; i++ ){
a+i;
b+i;
c+i;
}
}
console.timeEnd("group");
console.time("sep");
for(var x = 0; x<1000; x++){
var a = 0;
var b = 0;
var c = 0;
for(var i = 0; i < 6; i++ ){
a+i;
}
for(var i = 0; i < 6; i++ ){
b+i;
}
for(var i = 0; i < 6; i++ ){
c+i;
}
}
console.timeEnd("sep");
I get
group: 8ms
sep: 13ms

Categories