I am trying to successfully complete this challenge on the Rosalind page. The challenge is:
Given: Positive integers n≤40 and k≤5.
Return: The total number of rabbit pairs that will be present after n months if we begin with 1 pair and in each generation, every pair of reproduction-age rabbits produces a litter of k rabbit pairs (instead of only 1 pair).
The exercise gives a text file of two numbers, the n and k mentioned above.
My code, which attempts to implement Fibonacci, works as expected for lower numbers of months. However, the result begins to become extremely large for higher numbers of months, and in each case I am given, my answer is Infinity.
Is my formula applied incorrectly? Or is Javascript a bad choice of language to use for such an exercise?
My code:
function fibonacciRabbits(months, pairs){
var months = months;
var numberOfPairs = pairs;
var result = 0;
// Declare parent & child arrays with constants
var parentArr = [1, numberOfPairs + 1]
var childArr = [numberOfPairs, numberOfPairs]
var total = []
// Loop from the point after constants set above
for(var i = 2; i < months - 2 ; i++){
parentArr.push(parentArr[i-1] + childArr[i-1])
childArr.push(parentArr[i-1] * childArr[i-2])
total.push(parentArr[i-1] + childArr[i-1])
}
result = childArr[childArr.length - 1] + parentArr[parentArr.length - 1]
console.log(months + ' months and ' + numberOfPairs + ' pairs:\n')
console.log('parentArr:\n', parentArr)
console.log('childArr:\n', childArr)
console.log('total\n', total)
console.log('result:', result)
console.log('\n\n\n')
}
fibonacciRabbits(5, 3)
fibonacciRabbits(11, 3)
fibonacciRabbits(21, 3)
fibonacciRabbits(31, 2)
And here is a REPL
Here is a more brief solution that doesn't produce such large numbers, and handles the maximum case without hitting infinity in Javascript. I think your solution was getting too big too fast.
function fibonacciRabbits(months, reproAmount){
var existingAdults = 0;
var adultPairs = 0;
var childPairs = 1;
for(var i = 2; i <= months; i++){
adultPairs = childPairs; //children mature
childPairs = (existingAdults * reproAmount); //last month's adults reproduce
existingAdults += adultPairs; //new adults added to the reproduction pool
}
console.log(existingAdults + childPairs);
}
To make sure you are on the right track, test your function with:
fibonacciRabbits(1, 1);
fibonacciRabbits(2, 1);
Which from the website says: f(1)=f(2)=1. So these should both produce "1" no matter what. Your code produces "3" for both of these.
Related
I have been working to solve this problem on the HackerRank site: Fraudulent Activity Notifications.
Below is the code I have written which satisfies the three sample test cases; however, it does not satisfy the larger test cases since it seems to take longer than 10 seconds.
The 10 second constraint is taken from here: HackerRank Environment.
function activityNotifications(expenditure, d) {
let notifications = 0;
let tmp = [];
let median = 0, medianEven = 0, iOfMedian = 0;
// Begin looping thru 'expenditure'
for(let i = 0; i < expenditure.length; i++) {
// slice from 'expenditure' beginning at 'i' and ending at 'i + d' where d = number of days
// sort 'tmp' in ascending order after
tmp = expenditure.slice(i, i + d);
tmp.sort();
// edge case, make sure we do not exceed boundaries of 'expenditure'
if((i + d) < expenditure.length) {
// if length of 'tmp' is divisible by 2, then we have an even length
// compute median accordingly
if(tmp.length % 2 == 0) {
medianEven = tmp.length / 2;
median = (tmp[medianEven - 1] + tmp[medianEven]) / 2;
// test if expenditures > 2 x median
if(expenditure[i + d] >= (2 * median)) {
notifications++;
}
}
// otherwise, we have an odd length of numbers
// therefore, compute median accordingly
else {
iOfMedian = (tmp.length + 1) / 2;
// test if expenditures > 2 x median
if(expenditure[i + d] >= (2 * tmp[iOfMedian - 1])) {
notifications++;
}
}
}
}
return notifications;
}
I am familiar with O notation for computing time complexity, so initially it seems the problem is either the excessive amount of variables declared or conditional statements used. Only one for loop is being used so I don't think the loop is where I should look to optimize the code. Unless, of course, we were to include the .sort() function used on 'tmp' which would definitely add to the time it takes to compute efficiently.
Is there anything I have not realized which is causing the code to take longer than expected? Any other hints would be greatly appreciated, thanks.
A friend of mine takes a sequence of numbers from 1 to n (where n > 0)
Within that sequence, he chooses two numbers, a and b
He says that the product of a and b should be equal to the sum of all numbers in the sequence, excluding a and b
Given a number n, could you tell me the numbers he excluded from the sequence?
Have found the solution to this Kata from Code Wars but it times out (After 12 seconds) in the editor when I run it; any ideas as too how I should further optimize the nested for loop and or remove it?
function removeNb(n) {
var nArray = [];
var sum = 0;
var answersArray = [];
for (let i = 1; i <= n; i++) {
nArray.push(n - (n - i));
sum += i;
}
var length = nArray.length;
for (let i = Math.round(n / 2); i < length; i++) {
for (let y = Math.round(n / 2); y < length; y++) {
if (i != y) {
if (i * y === sum - i - y) {
answersArray.push([i, y]);
break;
}
}
}
}
return answersArray;
}
console.log(removeNb(102));
.as-console-wrapper { max-height: 100% !important; top: 0; }
I think there is no reason for calculating the sum after you fill the array, you can do that while filling it.
function removeNb(n) {
let nArray = [];
let sum = 0;
for(let i = 1; i <= n; i++) {
nArray.push(i);
sum += i;
}
}
And since there could be only two numbers a and b as the inputs for the formula a * b = sum - a - b, there could be only one possible value for each of them. So, there's no need to continue the loop when you find them.
if(i*y === sum - i - y) {
answersArray.push([i,y]);
break;
}
I recommend looking at the problem in another way.
You are trying to find two numbers a and b using this formula a * b = sum - a - b.
Why not reduce the formula like this:
a * b + a = sum - b
a ( b + 1 ) = sum - b
a = (sum - b) / ( b + 1 )
Then you only need one for loop that produces the value of b, check if (sum - b) is divisible by ( b + 1 ) and if the division produces a number that is less than n.
for(let i = 1; i <= n; i++) {
let eq1 = sum - i;
let eq2 = i + 1;
if (eq1 % eq2 === 0) {
let a = eq1 / eq2;
if (a < n && a != i) {
return [[a, b], [b, a]];
}
}
}
You can solve this in linear time with two pointers method (page 77 in the book).
In order to gain intuition towards a solution, let's start thinking about this part of your code:
for(let i = Math.round(n/2); i < length; i++) {
for(let y = Math.round(n/2); y < length; y++) {
...
You already figured out this is the part of your code that is slow. You are trying every combination of i and y, but what if you didn't have to try every single combination?
Let's take a small example to illustrate why you don't have to try every combination.
Suppose n == 10 so we have 1 2 3 4 5 6 7 8 9 10 where sum = 55.
Suppose the first combination we tried was 1*10.
Does it make sense to try 1*9 next? Of course not, since we know that 1*10 < 55-10-1 we know we have to increase our product, not decrease it.
So let's try 2*10. Well, 20 < 55-10-2 so we still have to increase.
3*10==30 < 55-3-10==42
4*10==40 < 55-4-10==41
But then 5*10==50 > 55-5-10==40. Now we know we have to decrease our product. We could either decrease 5 or we could decrease 10, but we already know that there is no solution if we decrease 5 (since we tried that in the previous step). So the only choice is to decrease 10.
5*9==45 > 55-5-9==41. Same thing again: we have to decrease 9.
5*8==40 < 55-5-8==42. And now we have to increase again...
You can think about the above example as having 2 pointers which are initialized to the beginning and end of the sequence. At every step we either
move the left pointer towards right
or move the right pointer towards left
In the beginning the difference between pointers is n-1. At every step the difference between pointers decreases by one. We can stop when the pointers cross each other (and say that no solution can be obtained if one was not found so far). So clearly we can not do more than n computations before arriving at a solution. This is what it means to say that the solution is linear with respect to n; no matter how large n grows, we never do more than n computations. Contrast this to your original solution, where we actually end up doing n^2 computations as n grows large.
Hassan is correct, here is a full solution:
function removeNb (n) {
var a = 1;
var d = 1;
// Calculate the sum of the numbers 1-n without anything removed
var S = 0.5 * n * (2*a + (d *(n-1)));
// For each possible value of b, calculate a if it exists.
var results = [];
for (let numB = a; numB <= n; numB++) {
let eq1 = S - numB;
let eq2 = numB + 1;
if (eq1 % eq2 === 0) {
let numA = eq1 / eq2;
if (numA < n && numA != numB) {
results.push([numA, numB]);
results.push([numB, numA]);
}
}
}
return results;
}
In case it's of interest, CY Aries pointed this out:
ab + a + b = n(n + 1)/2
add 1 to both sides
ab + a + b + 1 = (n^2 + n + 2) / 2
(a + 1)(b + 1) = (n^2 + n + 2) / 2
so we're looking for factors of (n^2 + n + 2) / 2 and have some indication about the least size of the factor. This doesn't necessarily imply a great improvement in complexity for the actual search but still it's kind of cool.
This is part comment, part answer.
In engineering terms, the original function posted is using "brute force" to solve the problem, iterating every (or more than needed) possible combinations. The number of iterations is n is large - if you did all possible it would be
n * (n-1) = bazillio n
Less is More
So lets look at things that can be optimized, first some minor things, I'm a little confused about the first for loop and nArray:
// OP's code
for(let i = 1; i <= n; i++) {
nArray.push(n - (n - i));
sum += i;
}
??? You don't really use nArray for anything? Length is just n .. am I so sleep deprived I'm missing something? And while you can sum a consecutive sequence of integers 1-n by using a for loop, there is a direct and easy way that avoids a loop:
sum = ( n + 1 ) * n * 0.5 ;
THE LOOPS
// OP's loops, not optimized
for(let i = Math.round(n/2); i < length; i++) {
for(let y = Math.round(n/2); y < length; y++) {
if(i != y) {
if(i*y === sum - i - y) {
Optimization Considerations:
I see you're on the right track in a way, cutting the starting i, y values in half since the factors . But you're iterating both of them in the same direction : UP. And also, the lower numbers look like they can go a little below half of n (perhaps not because the sequence start at 1, I haven't confirmed that, but it seems the case).
Plus we want to avoid division every time we start an instantiation of the loop (i.e set the variable once, and also we're going to change it). And finally, with the IF statements, i and y will never be equal to each other the way we're going to create the loops, so that's a conditional that can vanish.
But the more important thing is the direction of transversing the loops. The smaller factor low is probably going to be close to the lowest loop value (about half of n) and the larger factor hi is probably going to be near the value of n. If we has some solid math theory that said something like "hi will never be less than 0.75n" then we could make a couple mods to take advantage of that knowledge.
The way the loops are show below, they break and iterate before the hi and low loops meet.
Moreover, it doesn't matter which loop picks the lower or higher number, so we can use this to shorten the inner loop as number pairs are tested, making the loop smaller each time. We don't want to waste time checking the same pair of numbers more than once! The lower factor's loop will start a little below half of n and go up, and the higher factor's loop will start at n and go down.
// Code Fragment, more optimized:
let nHi = n;
let low = Math.trunc( n * 0.49 );
let sum = ( n + 1 ) * n * 0.5 ;
// While Loop for the outside (incrementing) loop
while( low < nHi ) {
// FOR loop for the inside decrementing loop
for(let hi = nHi; hi > low; hi--) {
// If we're higher than the sum, we exit, decrement.
if( hi * low + hi + low > sum ) {
continue;
}
// If we're equal, then we're DONE and we write to array.
else if( hi * low + hi + low === sum) {
answersArray.push([hi, low]);
low = nHi; // Note this is if we want to end once finding one pair
break; // If you want to find ALL pairs for large numbers then replace these low = nHi; with low++;
}
// And if not, we increment the low counter and restart the hi loop from the top.
else {
low++;
break;
}
} // close for
} // close while
Tutorial:
So we set the few variables. Note that low is set slightly less than half of n, as larger numbers look like they could be a few points less. Also, we don't round, we truncate, which is essentially "always rounding down", and is slightly better for performance, (though it dosenit matter in this instance with just the single assignment).
The while loop starts at the lowest value and increments, potentially all the way up to n-1. The hi FOR loop starts at n (copied to nHi), and then decrements until the factor are found OR it intercepts at low + 1.
The conditionals:
First IF: If we're higher than the sum, we exit, decrement, and continue at a lower value for the hi factor.
ELSE IF: If we are EQUAL, then we're done, and break for lunch. We set low = nHi so that when we break out of the FOR loop, we will also exit the WHILE loop.
ELSE: If we get here it's because we're less than the sum, so we need to increment the while loop and reset the hi FOR loop to start again from n (nHi).
i have this code: the idea is to print the list of the 10 random numbers and then add 6 to each item on the list and finally print new list with the new numbers after the additions: I've tried several modification but all of them are failing, any ideas??
var myArray = [];
appendItem(myArray, randomNumber(1,10));
appendItem(myArray, randomNumber(1,10));
appendItem(myArray, randomNumber(1,10));
appendItem(myArray, randomNumber(1,10));
console.log("Before: " + myArray);
for (var i = 0; i < myArray.lenght; i++) {
myArray = [i] + 5;
}
console.log("After: " + myArray);
one solution can be
let myArray = []
for (let i = 0 ; i < 5 ; i++) {
myArray.push(Math.floor(Math.random() * 10) + 5);
}
console.log(myArray)
A couple of things to help you along your way...
It looks like you're using borrowed code somewhere, because randomNumber is not a native JavaScript function.
So, first to get a random number, you can read more about Math.random() here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
Math.random() natively provides a decimal number between 0 and 1 (0.4, 0.67, 0.321 ...) To get a range, you multiply your answer by the maximum number for the range. And then commonly you want to wrap that in a Math.floor() method to trim the decimal points (this is just a Math method that all it does is trim off anything that's a decimal point and provides a whole number)
W3C is a good place to start to read up about arrays:
https://www.w3schools.com/js/js_arrays.asp
The common convention for adding elements to an array is to use .push() but you can look into .pop() and .filter() and .map() and there's just a ton of helpful methods attached to JavaScript arrays :)
/*
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
*/
let originalArray = [];
let plusSixArray = [];
let max = 10; // set the range for your random number
// max = 10 will let the range be from 0 - 9
// max = 273 will let the range be from 0 - 272
for( var i=0; i<10; i++ ){
let number = Math.floor(Math.random() * max) + 1;
// adding 1 to the end gives you a range between 1 - 10
originalArray.push( number );
// since you're already in a loop, just add the 6
plusSixArray.push( number + 6 );
}
console.log( originalArray );
console.log( plusSixArray );
I made a Javascript code that should take two numbers in an array, arrange them from lowest to highest, and then return the sum of every number between and including them. So sumAll(1, 4) // returns the sum of 1 + 2 + 3 + 4 which is 10. However, when I do it, my variable total does not change from 0 with my for loop.
var sumAll = function(arr) {
var lower = Math.min(arr[0], arr[1]);
var upper = Math.max(arr[0], arr[1]);
var total = 0;
for (var i = lower; i <= upper; i++) {
total += i;
}
return total;
}
This code should return 10 when using 1 and 4, but it gives 0 because var total = 0. When I manually change var total to equal 10, then it turns to ten, which means my for loop is not working the way it should, and I have no idea why. Any help is greatly appreciated, I am super new to coding and have been working on this for a while.
Also, this is a TDD project so it is attached to a spec js file. So the only other part of this code is not actually me calling the array but
module.exports = sumAll
The spec file asks for this:
var sumAll = require('./sumAll')
describe('sumAll', function() {
it('sums numbers within the range', function() {
expect(sumAll(1, 4)).toEqual(10);
});
The code in your question works well, however, if you still have issues you could try this:
function sumAll(n1, n2) {
var numbers = [];
numbers.length = Math.abs(n2-n1)+1; numbers.fill(0);
numbers = numbers.map((i, j) => (Math.min(n1,n2)+j))
return numbers.reduce((acc, i) => (acc+=i))
}
sumAll(1,4); //10
So, I have successfully written the Fibonacci sequence to create an array with the sequence of numbers, but I need to know the length (how many digits) the 500th number has.
I've tried the below code, but its finding the length of the scientific notation (22 digits), not the proper 105 it should be returning.
Any ideas how to convert a scientific notation number into an actual integer?
var fiblength = function fiblength(nth) {
var temparr = [0,1];
for(var i = 2; i<=nth; i++){
var prev = temparr[temparr.length-2],
cur = temparr[temparr.length-1],
next = prev + cur;
temparr.push(next);
}
var final = temparr[temparr.length-1].toString().length;
console.log(temparr[temparr.length-1]);
return final;
};
a = fiblength(500);
console.log(a);
Why not use the simple procedure of dividing the number by 10 until the number is less than 1.
Something as simple as this should work (a recursive def obv works as well)
function getDigits(n) {
var digits = 0;
while(n >= 1) {
n/=10;
digits += 1;
}
return digits;
}
getDigits(200);//3
getDigits(3.2 * 10e20);//=>22
Here's a solution in constant time:
function fiblength(n) {
return Math.floor((n>1)?n*.2089+.65051:1);
}
Let's explain how I arrived to it.
All previous solutions will probably not work for N>300 unless you have a BigNumber library in place. Also they're pretty inneficient.
There is a formula to get any Fibonacci number, which uses PHI (golden ratio number), it's very simple:
F(n) = ABS((PHI^n)/sqrt(5))
Where PHI=1.61803399 (golden ratio, found all over the fibonacci sequence)
If you want to know how many digits a number has, you calculate the log base 10 and add 1 to that. Let's call that function D(n) = log10(n) + 1
So what you want fiblength to be is in just the following function
fiblength(n) = D(F(n)) // number of digits of a fibonacci number...
Let's work it out, so you see what the one liner code will be like once you use math.
Substitute F(n)
fiblength(n) = D(ABS((PHI^n)/sqrt(5)))
Now apply D(n) on that:
fiblength(n) = log10(ABS((PHI^n)/sqrt(5))) + 1
So, since log(a/b) = log(a) - log(b)
fiblength(n) = log10(ABS((PHI^n))) - log10(sqrt(5))) + 1
and since log(a^n) = n * log(a)
fiblength(n) = n*log10(PHI) - log10(sqrt(5))) + 1
Then we evaluate those logarithms since they're all on constants
and add the special cases of n=0 and n=1 to return 1
function fiblength(n) {
return Math.floor((n>1)?n*.2089+.65051:1);
}
Enjoy :)
fiblength(500) => 105 //no iterations necessary.
Most of the javascript implementations, internally use 64 bit numbers. So, if the number we are trying to represent is very big, it uses scientific notation to represent those numbers. So, there is no pure "javascript numbers" based solution for this. You may have to look for other BigNum libraries.
As far as your code is concerned, you want only the 500th number, so you don't have to store the entire array of numbers in memory, just previous and current numbers are enough.
function fiblength(nth) {
var previous = 0, current = 1, temp;
for(var i = 2; i<=nth; i++){
temp = current;
current = previous + current;
previous = temp;
}
return current;
};
My Final Solution
function fiblength(nth) {
var a = 0, b = 1, c;
for(var i=2;i<=nth;i++){
c=b;
b=a+b;
a=c;
}
return Math.floor(Math.log(b)/Math.log(10))+1;
}
console.log(fiblength(500));
Thanks for the help!!!
The problem is because the resulting number was converted into a string before any meaningful calculations could be made. Here's how it could have been solved in the original code:
var fiblength = function fiblength(nth) {
var temparr = [0,1];
for(var i = 2; i<=nth; i++){
var prev = temparr[temparr.length-2],
cur = temparr[temparr.length-1],
next = prev + cur;
temparr.push(next);
}
var x = temparr[temparr.length-1];
console.log(x);
var length = 1;
while (x > 1) {
length = length + 1;
x = x/10;
}
return length;
};
console.log ( fiblength(500) );