How to manage memory in this Javascript algorithm? - javascript

My solution to the below problem gives correct answer in the compiler but gets rejected by the online judge due to the following error: JS Allocation failed - process out of memory.
What should I change in my algorithm to get rid of this error?
Codewars Kata:You have to code a function getAllPrimeFactors wich take an integer as parameter and return an array containing its prime decomposition by ascending factors, if a factors appears multiple time in the decomposition it should appear as many time in the array.
exemple:
getAllPrimeFactors(100) returns [2,2,5,5] in this order.
This decomposition may not be the most practical.
You should also write getUniquePrimeFactorsWithCount, a function which will return an array containing two arrays: one with prime numbers appearing in the decomposition and the other containing their respective power.
exemple:
getUniquePrimeFactorsWithCount(100) returns [[2,5],[2,2]]
You should also write getUniquePrimeFactorsWithProducts an array containing the prime factors to their respective powers.
exemple:
getUniquePrimeFactorsWithProducts(100) returns [4,25]
Errors, if:
n is not a number
n not an integer
n is negative or 0
The three functions should respectively return
[], [[],[]] and [].
Edge cases:
if n=0, the function should respectively return [], [[],[]] and [].
if n=1, the function should respectively return [1], [[1],[1]], [1].
if n=2, the function should respectively return [2], [[2],[1]], [2].
The result for n=2 is normal. The result for n=1 is arbitrary and has been chosen to return a usefull result. The result for n=0 is also arbitrary but can not be chosen to be both usefull and intuitive.
([[0],[0]]
would be meaningfull but wont work for general use of decomposition,
[[0],[1]]
would work but is not intuitive.)
Here is my algorithm:
function getAllPrimeFactors(n) {
var fact=[];
while(n%2===0)
{
fact.push(2);
n=n/2;
}
var i=3;
while(i<=Math.floor(Math.sqrt(n)))
{
while(n%i===0)
{
fact.push(i);
n=n/i;
}
i++;
}
if(n>2)
{
fact.push(n);
}
return fact;
}
function getUniquePrimeFactorsWithCount(n) {
var fact=getAllPrimeFactors(n);
var i=0;
var count=[];
var unique=[];
var c=0;
while(i<fact.length)
{
if(fact[i]===fact[i+1])
{
c++;
}
else
{
count.push(c+1);
c=0;
unique.push(fact[i]);
}
i++;
}
var fact_count=[];
fact_count.push(unique);
fact_count.push(count);
return fact_count;
}
function getUniquePrimeFactorsWithProducts(n) {
var fact_count=getUniquePrimeFactorsWithCount(n);
var fact_prod=[];
var i=0;
while(i<fact_count[0].length)
{
var prod=1;
var j=1;
while(j<=fact_count[1][i])
{
prod*=fact_count[0][i];
j++;
}
fact_prod.push(prod);
i++;
}
return fact_prod;
}

Your problem is that you enter an endless loop if you are passed 0. You should make 0 and negatives into special cases.

Related

RangError: too many arguments provided for a function call

I got a nice solution to get HTML Comments from the HTML Node Tree
var findComments = function(el) {
var arr = [];
for (var i = 0; i < el.childNodes.length; i++) {
var node = el.childNodes[i];
if (node.nodeType === 8) {
arr.push(node);
} else {
arr.push.apply(arr, findComments(node));
}
}
return arr;
};
var commentNodes = findComments(document);
// whatever you were going to do with the comment...
console.log(commentNodes[0].nodeValue);
from this thread.
Everything I did was adding this small loop to print out all the nodes.
var arr = [];
var findComments = function(el) {
for (var i = 0; i < el.childNodes.length; i++) {
var node = el.childNodes[i];
if (node.nodeType === 8) {
arr.push(node);
} else {
arr.push.apply(arr, findComments(node));
}
}
return arr;
};
var commentNodes = findComments(document);
//I added this
for (var counter = arr.length; counter > 0; counter--) {
console.log(commentNodes[counter].nodeValue);
}
I keep getting this Error Message:
RangeError: too many arguments provided for a function call debugger
eval code:9:13
EDIT: i had a typo while pasting changed the code from i-- to counter--
see this comment in MDN docs about the use of apply to merge arrays:
Do not use this method if the second array (moreVegs in the example) is very large, because the maximum number of parameters that one function can take is limited in practice. See apply() for more details.
the other note from apply page:
But beware: in using apply this way, you run the risk of exceeding the JavaScript engine's argument length limit. The consequences of applying a function with too many arguments (think more than tens of thousands of arguments) vary across engines (JavaScriptCore has hard-coded argument limit of 65536), because the limit (indeed even the nature of any excessively-large-stack behavior) is unspecified. Some engines will throw an exception. More perniciously, others will arbitrarily limit the number of arguments actually passed to the applied function. To illustrate this latter case: if such an engine had a limit of four arguments (actual limits are of course significantly higher), it would be as if the arguments 5, 6, 2, 3 had been passed to apply in the examples above, rather than the full array.
As the array start from index of 0, actually the last item in the array is arr.length - 1.
you can fix it by:
for (var counter = arr.length - 1; counter >= 0; counter--)
Notice I've added arr.length -1 and counter >= 0 as zero is the first index of the array.
Adding the for loop is not the only thing you changed (and see the other answer about fixing that loop too). You also moved the declaration of arr from inside the function to outside, making arr relatively global.
Because of that, each recursive call to findComments() works on the same array, and the .apply() call pushes the entire contents back onto the end of the array every time. After a while, its length exceeds the limit of the runtime.
The original function posted at the top of your question has arr declared inside the function. Each recursive call therefore has its own local array to work with. In a document with a lot of comment nodes, it could still get that Range Error however.

Why isn't this prime factors thing working?

function prime(number) {
var primeNumbers = [];
var numberDivide = 2;
for(var i=0; i<number; i++) {
var nice = number/numberDivide;
if(Math.floor(nice) == nice) {
number = nice;
primeNumbers.push(numberDivide);
} else {
numberDivide++
}
console.log(primeNumbers)
}
}
That is my code. It is not logging the final prime number, I don't know why. I went through and spoke it out loud but it isn't working...
First a code translation that works with integers to see what the function is doing:
function prime(number) {
var primeNumbers = [];
for(var divisor =2; divisor <= number;){
if( number % divisor == 0){
primeNumbers.push(divisor);
number = number/divisor;
continue;
}
++divisor;
}
return primeNumbers;
}
console.log( prime(18)); // returns [2,3,3]
Here number is replaced by the quotient of dividing it by a prime factor to see if additional prime factors can be found over and above those determined already. Note that number keeps getting smaller, and dividend is not incremented when found to be a factor so the next loop can test if it is a multiple factor.
So how many times does the loop iterate? This version doesn't count them, but the posted version stops iterating when the loop counter (not otherwise used) is greater or equal to number which as discussed keeps getting smaller.
The posted function fails for prime(18) and returns [2,3] instead of [2,3,3]. If it is allowed to iterate one more time it produces the correct result. I haven't looked into pre-calculating how many times to loop, but the method of finding when to exit the loop in the posted code is the cause of the problem.

use Array.indexOf for relatively big arrays

Is there a way to achieve indexOf functionality, to find out if a string is on an array, for very big arrays relatively fast? When my array grows beyond 40,000 values, my app freezes for a few seconds.
Consider the following code:
var arr = [];
function makeWord()
{
var text = "";
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < 5; i++ )
text += possible.charAt(Math.floor(Math.random() * possible.length));
return text;
}
function populateArr(){
for (var i=0;i<40000;i++){
arr[i] = makeWord();
}
console.log("finished populateArr");
}
function checkAgainst(){
for (var i=0;i<40000;i++){
var wordToSearch = makeWord();
if (isFound(wordToSearch)){
console.log("found "+wordToSearch);
}
}
console.log("finished checkAgainst");
}
function isFound(wordToSearch){
//return $.inArray(wordToSearch,arr) > -1;
return arr.indexOf(wordToSearch) > -1;
}
populateArr();
checkAgainst();
FIDDLE here
In this code I'm populating an array arr with 40k random strings. Than, in checkAgainst I'm creating 40,000 other random strings, and than each one is checked if it is found on arr. This makes chrome freeze for about 2 seconds. Opening the profiler on Chrome DevTools, I see that isFound is obviously expensive in terms of CPU. even if I lower the for loop iterations number to just 4000 in checkAgainst , it still freezes for about a second or so.
In reality, I have a chrome extension and an array of keywords that grows to about 10k strings. Than, I have to use Array.indexOf to see if chucks of 200 other keywords are in that array. This makes my page freeze every once in a while, and from this example I suspect this is the cause. Ideas?
Try using keys in an object instead:
var arr = {};
function makeWord() // unchanged
function populateArr(){
for (var i=0;i<40000;i++){
arr[makeWord()] = true;
}
console.log("finished populateArr");
}
function checkAgainst() // unchanged
function isFound(wordToSearch){
return arr[wordToSearch];
}
populateArr();
checkAgainst();
If you then need the array of words, you can use Object.keys(arr)
Alternatively, combine the two: have an array and an object. Use the object to look up if a word is in the array, or the array to get the words themselves. This would be a classic compromise, trading memory usage for time.

Trouble pushing to an array in JS

Below is just a section of my code but I know it's problematic because I can't get it to return any value except 'undefined'. I have been over this for hours and cannot figure it out.
I want to be able to input a number and have its factors pushed to an array. I have tested it by alerting the first item in the array and I get nothing. I'm sure this is a pretty easy but I just can't figure it out. Here is the code:
var numberInQuestion = prompt("Of what number are you wanting to find the largest prime factor?");
//determine factors and push to array for later use
var factorsArray = [];
function factors(numberInQuestion){
for(var i = 2; i < numberInQuestion-1; i++){
if(numberInQuestion % i === 0){
return factorsArray.push[i];
} else {
continue;
}
}
};
factors(numberInQuestion);
alert(factorsArray[0]);
Thanks for any help!
you can only return one value
you must use (), not [] for calling push
factorsArray should be local to factors (put the definition inside the function)
the else { continue; } is useless
Here is the fully corrected code:
var numberInQuestion = prompt("Of what number are you wanting to find the factors of?");
//determine factors
function factors(numberInQuestion){
var factorsArray = []; // make it local
for (var i = 2; i < numberInQuestion-1; i++){
if(numberInQuestion % i === 0){
factorsArray.push(i); // use (), and don't return here
} // no need for else { continue; } because it's a loop anyway
}
return factorsArray; // return at the end
};
var result = factors(numberInQuestion); // assign the result to a variable
alert(result);
Here's a JSFiddle.
You have an error in your pushing syntax. Correct syntax for pushing is -
factorsArray.push(i);
Also returning immediately from the function after finding the first divisor will not give you the full list. You probably want to return after you've found out all the divisors.
Taking all of the above into consideration, you should rewrite your function as follow -
function factors(numberInQuestion){
for(var i = 2; i < numberInQuestion - 1; i++){
if(numberInQuestion % i === 0) {
factorsArray.push(i);
}
}
}
and you will be OK.
You've coded this so that when you find the first factor your function returns immediately. Just get rid of the return keyword in that statement. (What "return" means in JavaScript and other similar languages is to immediately exit the function and resume from where the function was called.)
Oh, also, you call functions (like .push()) with parentheses, not square brackets.
The function should not return when pushing to the array. Return the array after executing the loop. The else clause is also unnecessary.
var numberInQuestion = prompt("Of what number are you wanting to find the largest prime factor?");
function factors(numberInQuestion){
var factorsArray = [];
for(var i = 2; i < numberInQuestion-1; i++){
if(numberInQuestion % i === 0 && isPrime(i)){
factorsArray.push(i);
}
}
return factorsArray;
};
var factors = factors(numberInQuestion);
alert(factors[factors.length-1]);
//From: http://stackoverflow.com/questions/11966520/how-to-find-prime-numbers
function isPrime (n)
{
if (n < 2) return false;
var q = Math.sqrt (n);
for (var i = 2; i <= q; i++)
{
if (n % i == 0)
{
return false;
}
}
return true;
}
Given the purpose of the example two items must be considered
The code does not determine if the number is actually prime. The code will return the smallest factor possible since the loop starts at two and increments, then returns the first element in the array. The largest factor would actually be the last element in the array. I have corrected the example to find the greatest prime factor. You can test it via this fiddle: http://jsfiddle.net/whKGB/1/

I'm having trouble writing a recursive function in JavaScript - it doesn't seem to "fall back" to the lesser depth correctly

var diceToRoll = [2,2];
var diceRolled = new Array();
function recurse(diceToRoll, diceRolled) {
roll = diceToRoll[0]
diceLeftToRoll = diceToRoll;
diceLeftToRoll.shift();
for(loop=1; loop<(roll+1); loop++) {
result = diceRolled;
result.push(loop);
if(diceLeftToRoll.length == 0) {
console.log(result);
result.pop();
} else {
recurse(diceLeftToRoll, result);
}
}
}
recurse(diceToRoll, diceRolled);
I'm trying to write a recursive function which prints the possible results of any number of dice. For example, a dd100 (diceToRoll = [6, 10, 10, 100])(diceToRoll = [6, 6, 6, 6, 6]) etc. In the example I have used the simplest case(or two 2-sided dice).
I expected the results to be [1,1], [1,2], [2,1], [2,2] however it only logs [1,1], [1,2]. This is the same with any number or type of dice - only the deepest level of recursion works correctly.
I figure I'm missing something obvious in the logic of it / or misunderstanding variable scope in JavaScript, but I'm just really struggling to get my head around it.
Edit 1 (To make explanation of program's purpose clearer)
The programs purpose is to list all possible values on any number of dice. So a dice 6 implies the range of values 1..6. Likewise, a two-sided dice, 2, implies the range of values 1..2. So for two two-sided dice in the example (diceToRoll[2,2]) the possible values are 1,1 1,2 2,1 and 2,2 - which is what should be returned.
There are several issues with your code:
Use var keyword in order to define local variables.
Assigning array to another variable not copies its content, just reference to the same array. Use Array.slice() if you want to clone array.
Here is a fixed function:
var diceToRoll = [2,2],
diceRolled = [];
function recurse(diceToRoll, diceRolled) {
var roll = diceToRoll[0],
diceLeftToRoll = diceToRoll.slice(1),
loop,
result;
for(loop=1; loop<=roll; loop++) {
result = diceRolled.slice(0);
result.push(loop);
if(diceLeftToRoll.length === 0) {
console.log(result);
result.pop();
} else {
recurse(diceLeftToRoll, result);
}
}
}
recurse(diceToRoll, diceRolled);
NOTE
diceToRoll = diceToRoll.slice(1)
is equivalent to
diceToRoll = diceToRoll.slice(0);
diceToRoll.shift();
Fiddle here: http://jsbin.com/isebef/1/edit
You must declare "roll" (and other local variables) with var.
function recurse(diceToRoll, diceRolled) {
var roll = diceToRoll[0]
var diceLeftToRoll = diceToRoll;
diceLeftToRoll.shift();
for(var loop=1; loop<(roll+1); loop++) {
var result = diceRolled;
result.push(loop);
if(diceLeftToRoll.length == 0) {
console.log(result);
result.pop();
} else {
recurse(diceLeftToRoll, result);
}
}
}
Without var, "roll" and "loop" are global.
I'm not sure what the point of "result" is; it's just a reference to the "diceRolled" array, so I'm not sure why you wouldn't just use that.
edit — I'm not sure exactly what your code is trying to do here, but another significant problem is this:
var diceLeftToRoll = diceToRoll;
diceLeftToRoll.shift();
When you make an assignment like that of a value that's a reference to an array, you do not make a copy of the array. Thus both variables refer to the same array object, and the first element will be removed from it. If you instead make "diceLeftToRoll" a copy of the other array, then things work differently:
var diceLeftToRoll = diceToRoll.slice(1); // copy all but 1st element
I don't think the whole thing will work, however, because I now think that the "result" variable was an attempt to do something similar.
edit again Here is an alternative version that returns results in a list. This one avoids making copies except for the final entries added to the result.
function allRolls( dice ) {
var list = [], rolled = [];
function roll( dn ) {
var dp = rolled.length;
for (var dv = 1; dv <= dice[dn]; ++dv) {
rolled[dp] = dv;
if (dn < dice.length - 1)
roll(dn + 1)
else
list.push(rolled.slice(0));
}
rolled.length = dp;
}
if (dice.length) roll(0);
return list;
}
allRolls([3, 3, 3]);
The function involves an inner function that does all the work. It's passed the index in the "dice" list of a dice to roll; initially, that's 0.
The function keeps track of two other lists: the accumulated possible rolls, and an array representing the "rolled so far" for use by the recursive inner function.
At each recursive level, the function iterates through the faces of the current dice (that is, dice[dn]). Each iteration places that dice value in a slot at the end of the "rolled" array; the same slot is used for each iteration. Now, if the loop notices that "dn" represents the last dice in the list, then it makes a copy of the "rolled" array and adds it to the result list. If not, then it makes a recursive call, passing the next dice index down.
The outer function then just checks to see if there are any dice to roll, and rolls them if so. It returns the accumulated list.

Categories

Resources