NaN in the context of a recursive for loop - javascript

I am trying to understand a solution I found for a problem: "You are given coins of different denominations and a total amount of money. Write a function to compute the number of combinations that make up that amount. You may assume that you have infinite number of each kind of coin."
My question is, if I run the function with change(3,[2]), why does it spit out 0. I am having trouble with comprehending how after a single recursive call currentCoin becomes undefined, and then when the program reaches the for loop in that call, it doesn't call the change function again with total += change(amount - 0 * undefined, coins.slice(0, -1)). Why does it not crash with either an infinite recursive call with change(NaN,[]) or coins.slice(0,-1) being used on an empty array. It seems to ignore that on the for loop.
Am I misunderstanding how a for-loop works?
var change = function(amount, coins) {
if(amount == 0) return 1;
let currentCoin = coins[coins.length - 1];
let total = 0;
for(let qty = 0; qty * currentCoin <= amount; qty++){
total += change(amount - qty * currentCoin, coins.slice(0, -1))
}
return total;
};
console.log(change(3,[2]))

There are a couple things going on here.
First is the behavior of coins[coins.length - 1]. In Javascript, when you access an element of a list at an index that doesn't exist in that list, the indexer will return undefined instead of crashing with an IndexOutOfBoundsException or the like.
Second is qty * currentCoin <= amount. In the case that currentCoin is undefined (due to the above), qty * currentCoin will be NaN. In Javascript, any comparison of NaN with another number will return false by design. (e.g. NaN <= anything is false).
Put this all together and you see that, on the first recursion, the coins array will be empty which makes currentCoin NaN. This causes qty * currentCoin <= currentAmount to be false, which causes the loop to short circuit (so slice never gets called on an empty list). Since the loop never executes, total will still be 0, which is what gets returned. This continues until qty * currentCoin <= amount becomes true in the outermost recursion, and that loop exits with total still equalling 0 (since it only ever added 0).
If you intersperse console.log calls in strategic places about the function, it becomes clearer what is happening:
var change = function(amount, coins) {
console.log(amount, coins);
if(amount == 0) return 1;
let currentCoin = coins[coins.length - 1];
console.log(':', currentCoin, amount);
let total = 0;
for(let qty = 0; qty * currentCoin <= amount; qty++){
total += change(amount - qty * currentCoin, coins.slice(0, -1))
console.log('=', total);
}
console.log('recdone');
return total;
};
console.log(change(3,[2]))

not crash because a NaN in comparison with a number is every false...
NaN < number or NaN > number and so on produce false... so the
qty * currentCoin <= amount
is evaluate false and will exit from the for.
So, if you need to check the NaN you must before the for
let totalCoin = qty * currentCoin;
let check = isNaN(totalCoin);
if(check) {
// return you sentinel value;
}

var change = function(amount, coins) {
if(amount == 0) return 1;
let currentCoin = coins[coins.length - 1]; // firstpass 1-1 = 0, second pas 0-1=-1 => coins[-1] = undefined
let total = 0;
// this will 0*0<=3, second pass 0*undefined => null which is false hence never execute
for(let qty = 0; qty * currentCoin <= amount; qty++){
total += change(amount - qty * currentCoin, coins.slice(0, -1))
}
return total;
};
console.log(change(3,[2]))
In second pass when coins.length = 0 then
let currentCoin = coins[0 - 1]; // = undefined
Later in for loop you will 0 * undefined ( qty * currentCoin) which results NaN which is Not a number

There is no need for recursion in this case. One can use a bottom-up dynamic programming approach. Let ways[i] denote the number of ways to get to i dollars with the given coins and coins[i] represent the value of the ith coin. Then, ways[i] is the sum of all ways[i - coins[j]] for all j from 1 to the number of coins.
var change = function(amount, coins) {
const ways = Array(amount + 1);
ways[0] = 1;
for(const coin of coins){
for(let i = coin; i <= amount; i++){
ways[i] = (ways[i] ?? 0) + ways[i - coin] ?? 0;
}
}
return ways[amount];
};
console.log(change(5,[1,2,3,4,5]))

Related

Counting every 5th for loop

I'm having a hard time with this problem here:
Write a function named sumEvery5th that accepts a non-negative integer n and returns the sum of the integers divisible by 5 from 1 to n, including n itself. Use a for loop.
This is what I have so far:
var sumEvery5th = function(n){
let sum = 0;
for(let i = 1; n % 5 == 0; i++){
sum+ i
};
return sum;
}
I feel like I'm really close. Thanks for the help in advance.
You can start your loop at 5, since 1, 2... are not divisible by 5. And instead of i++, you can directly go 5 by 5, until i is greater than n:
var sumEvery5th = function(n) {
let sum = 0;
for (let i = 5; i <= n; i += 5) {
sum += i;
};
return sum;
}
console.log(sumEvery5th(10)); // 5 + 10 = 15
console.log(sumEvery5th(18)); // 5 + 10 + 15 = 30
First I think you should understand how a for loop works.
var sumEvery5th = function(n){
let sum = 0;
for(let i = 1; n % 5 == 0; i++){
sum+ i
};
return sum;
}
What you are doing, step by step, is:
Declaring a variable i with value 1.
Dividing n by 5 and taking the remainder value and comparing it with 0. In case it's true, you are skipping the code block inside the for and moving towards the return sum; line.
(In case you haven't skipped the code block in step 2) Run the code block with the new i value.
(In case you haven't skipped the code block in step 2) Incrementing the i value.
Go back to step 2.
Usually your for condition will depend in the variable declared in step 1. What you want to do is run the for code block n times.
For that, you need to change your condition from n % 5 == 0 to i <= n. This will make sure to run the code block while your i is less or equal than n, starting with a value of 1.
Now, inside your code block you add your divisible by 5 logic, checking against i value.
for(let i = 1; i <= n; i++){
if (i%5 == 0) sum += i;
};
Now let's say I called sumEvery5th(5).
Declare a variable i with value 1.
Check if i (1) is less than or equal n (5).
Go inside the code block.
Check if i%5 is 0.
It's not.
Increment i, now i = 2.
Check if i (2) is less than or equal n (5).
...And so on, until i = 6, and in that case the code block is skipped and the program will continue its course.
Ps.: There are ways to improve the performance of this algorithm, but now that you understand your for loop a bit better I'll leave it to you :)
var sumEvery5th = function(n){
let sum = 0;
for(let i = 1; i <= n; i++){
if (i % 5 === 0) {
sum += i;
}
}
return sum;
}

Addition with carryover in arrays in pure Javascript

I want to create simple addition of array with carryover. Also need carryover and result value for display.
Something like this:-
e.g var input = [[0,0,9],[0,9,9]];
var carryover = [];
var result = [];
Thanks...
The two parts you might have been struggling with, I assume would be how you get the carry, and how you get the result..
result [diget] = t % 10;
The % 10 part is what is called modulus, here I'm doing a modulus by 10, so that gets you the 10's unit value.
carryover [diget] = Math.trunc(t / 10);
For the carryover, you just then divide by 10, and then we strip the decimals,. That's what Math.trunc does.
var input = [[0,0,0,9],[0,9,9]];
var carryover = [];
var result = [];
var digetlength = Math.max(input[0].length, input[1].length);
//lets padd inputs to be same size
input[0].unshift(
...new Array(digetlength - input[0].length).fill(0));
input[1].unshift(
...new Array(digetlength - input[1].length).fill(0));
for (var diget = digetlength - 1; diget >= 0; diget -= 1) {
var t = input[0][diget] + input[1][diget];
if (diget < digetlength - 1)
t += carryover[diget + 1];
result [diget] = t % 10;
carryover [diget] = Math.trunc(t / 10);
}
result.unshift(carryover[0]);
console.log('result: ' + result.join(', '));
console.log('carry: ' + carryover.join(', '));
1.turn both numbers into array of digits, reverse them.
2.determine the end index of the for-loop with max length of above 2 arrays.
3.create the 3rd carryover digits array of zeros (don't forget the extra digit).
4.Add the respective digits from step1 and step3,
as you iterate through each of digits from right to left,
4.1 if the sum is greater than 9 then add 1 into next carryover slot.
5. you should have array of carried over digits when the for-loop is done
count the number of 1s you have in them.
function numberOfCarryOperations(num1, num2) {
const dd1 = [...num1.toString()].reverse()
const dd2 = [...num2.toString()].reverse()
const end = Math.max(dd1.length, dd2.length)
const carry = Array(end+1).fill(0)
for (let i = 0; i < end; i++) {
//console.log(i,(Number(dd1[i]?dd1[i]:0)),Number(dd2[i]?dd2[i]:0),carry)
if (((Number(dd1[i]?dd1[i]:0)) + Number(dd2[i]?dd2[i]:0) + carry[i]) > 9) {
carry[i+1] = 1
}
//console.log('-----',carry)
}
//console.log(num1, num2,carry)
return carry.reduce((sum,curr)=>sum+curr)
}
Here is my attempt. It will accept the following as input:
Any number of input arrays
The input arrays don't all need to have the same number of items
I've added code comments to explain what goes on, I hope they're informative enough to explain the answer.
const
input = [
[0,0,9],
[0,9,9],
[1,0,9,9]
];
function getMaxArrayLength(values) {
// Determine the number of items in the longest array. Initialize the reduce with 0.
return values.reduce((maxLength, array) => {
// Return the largets number between the last largest number and the
// length of the current array.
return Math.max(maxLength, array.length);
}, 0);
}
function sumValues(values) {
const
// Determine the number of items in the longest array.
maxLength = getMaxArrayLength(values),
result = [],
carry = [];
// Loop over the length of the longest array. The value of index will be substracted from
// the length of the input arrays. Therefore it is easier to start at 1 as this will
// return a proper array index without needing to correct it.
for (let index = 1; index <= maxLength; index++) {
const
// Get the carryover value from the last sum or 0 in case there is no previous value.
carryValue = (carry.length === 0) ? 0 : carry[carry.length-1],
// Sum up all the values at the current index of all the input arrays. After summing up
// all the values, also add the carry over from the last sum.
sum = values.reduce((sum, array) => {
// Determine the index for the current array. Start at the end and substract the
// current index. This way the values in the array are processed last to first.
const
arrayIndex = array.length - index;
// It could be the current array doesn't have as many items as the longest array,
// when the arrayIndex is less than 0 just return the current result.
if (arrayIndex < 0) {
return sum;
}
// Return the accumulated value plus the value at the current index of the
// current source array.
return sum + array[arrayIndex];
}, 0) + carryValue;
// The carry over value is the number of times 10 fits into the sum. This should be rounded
// down so for instance 5/10=.5 becomes 0.
carry.push(Math.floor(sum / 10));
// Push the remainder of the sum divided by 10 into the result so 15 % 10 = 5.
result.push(sum % 10);
}
// Return the carry over and the result, reverse the arrays before returning them.
return {
carryOver: carry.reverse(),
result: result.reverse()
};
}
const
result = sumValues(input);
console.log(`Carry over: ${result.carryOver}`);
console.log(`Result: ${result.result}`);

Randomize multiple integers to equal variable

Using JavaScript, how would I go about generating 30 random integers and have the sum of those 30 integers be 60000? I need the script to produce a different set of numbers each time it is run, making sure that the total is always 60000
var n = 30;
var total = 60000;
var min = 10;
var max = 5000;
for (i = 0; i < n; i++) {
// Math.floor(Math.random()*(max-min+1)+min); ??
}
In order to avoid excessively large and small numbers, the min and max values will likely be needed
You can try something like this:
Logic
Accept Max total and total number of resulting Numbers.
Now loop based on this number - 1 for n-1 random numbers and last value should be max - currentSum. Since rest of numbers are random, this difference will also be random and this will also ensure total being equal.
Now all you need to do is return a random number based on a given range.
I have also added a flag to check for unique values. Currently I have just added 1 to it but this will not ensure its uniqueness, but as its out of scope, not rectifying it.
Code
function getRandomInRange(max) {
var raiseVal = Math.pow(10, (max.toString().length - 1) || 1);
return Math.floor(Math.random() * raiseVal) % max;
}
function getRandomNumbers(max, n, uniqueNum) {
var nums = [];
var sum = 0;
for (var i = 0; i < n - 1; i++) {
let r = getRandomInRange(max - sum);
if(uniqueNum && nums.indexOf(r) > -1){
r += 1;
}
nums.push(r)
sum += r;
}
nums.push(max - sum);
console.log(nums)
return nums
}
getRandomNumbers(3, 3, true)
getRandomNumbers(3, 3)
getRandomNumbers(1000, 10)
getRandomNumbers(600000, 30)

How to return an array of values, the sums of which equal a specified number

I'm trying to create an array of numbers of a set length, defining the minimum and a maximum number in the set, and letting a function determine the rest of the numbers between. The kicker is that the sum of this array of numbers must be equal to a predetermined value. The trick is figuring out how that function works.
I found this on stack overflow, which got me the following function:
export const distributeValues = (amount, weights=[]) => {
const distributedAmounts = []
let totalWeights = weights.reduce( (a,b) => a + b)
weights.forEach( weight => {
const weightValue = parseFloat(weight)
const percentage = weightValue / totalWeights
const distributedAmount = Math.round(percentage * amount)
distributedAmounts.push(distributedAmount)
totalWeights -= weightValue
amount -= distributedAmount
})
return distributedAmounts
}
This seems like a good start, but I actually need to work backwards; I'm trying to figure out a function that will give me the weights that would be passed into the above function.
Right now, I have this, a function broken into two parts (apologies for the redundancy):
export const getDistributions = (amount, distributions, modifier) => {
const values = []
let amountLeft = amount;
for (let i = 0; i < distributions; i++ ) {
const value = Math.max(Math.round((amountLeft / (modifier || 4))),1)
amountLeft -= value
values.push(value)
}
// -------------------------------------------- //
// --- correct for cases where total values --- //
// --- end up greater/less than amount --- //
// -------------------------------------------- //
let iterator = 0
let totalAssignedValue = values.reduce((a,b) => a+b);
const lastIndex = (values.length - 1);
const getIndex = (iterator, values) => {
return iterator > lastIndex ? iterator % lastIndex : iterator
}
while (totalAssignedValue > amount) {
iterator = getIndex(iterator)
if (iterator !== lastIndex && iterator !== 0 && values[iterator] > 1) {
values[iterator]--
}
iterator ++
totalAssignedValue = values.reduce((a,b) => a+b);
}
while (totalAssignedValue < amount) {
iterator = getIndex(iterator)
if (iterator !== lastIndex && iterator !== 0) {
values[iterator]++
}
iterator ++
totalAssignedValue = values.reduce((a,b) => a+b);
}
// -------------------------------------------- //
// -------------- end correction -------------- //
// -------------------------------------------- //
return values;
}
The first part tries and distributes the values, but invariably I end up with values that are greater or lesser than the input amount, so there's a second part of the equation that fixes that. Seems a little unclean though, and it's a little arbitrary how the remainders get distributed, so a pure mathematical solution would be great.
I'm starting to wonder if I'm going to need calculus for this, because I basically have the integral (the sum of the array's values), the range of the integral (min and max values), and now have to figure out the formula for the curve. This may be overkill at this point, though.
Thanks for the input!
How about this? First create the set in such way that the first member is the minimum, the second member is minimum + 1, the third minimum + 2, etc. Then sum up the numbers in the set and subtract the sum from the predetermined value. Then distribute the result of the subtraction among all the numbers in the set as outlined betlow.
Set makeSet(int preDet, int min, int max, int setLength)
{
if((max + max - setLength + 1) * setLength / 2 < preDet) return null;
if((min + min + setLength - 1) * setLength / 2 > preDet) return null;
Set set = Set(setLength);
int val = min;
for (int i = 0; i < setLength; i++)
{
set[i] = val++;
}
int sum = (min + val - 1) * setLength / 2;
int dev = preDet - sum;
if(dev)
{
int adj = dev / setLength;
if(dev % setLength) adj++;
for(int i = setLength -1; dev; i--)
{
if(adj > dev) adj = dev;
set[i] += adj;
dev -= adj;
}
}
return set;
}

Algorithm that involves rounding and multiples

So I have a problem where I have an array of some length (usually long). I have an initial start index into that array and a skip value. So, if I have this:
var initial = 5;
var skip = 10;
Then I'd iterate over my array with indexes 5, 15, 25, 35, etc.
But then I may get a new start value and I need to find the closest value to the initial plus or minus a multiple of the skip and then start my skip. So if my new value is 23 then I'd iterate 25, 35, 45, etc.
The algorithm I have for this is:
index = (round((start - initial) / skip) * skip) + initial
And then I need a check to see if index has dropped below zero:
while(index < 0) index += skip;
So my first question is if there's a name for this? A multiple with random start?
My second question is if there's a better way? I don't think what I have is terribly complicated but if I'm missing something I'd like to know about it.
If it matters I'm using javascript.
Thanks!
Edit
Instead of
while(index < 0) index += skip;
if we assume that both initial and skip are positive you can use:
if (index < 0) index = initial % skip;
To get the closest multiple of a number to a test number: See if the modulo of your test number is greater than number/2 and if so, return number - modulo:
function closestMultiple(multipleTest,number)
{
var modulo = multipleTest%number;
if(0 == modulo )
{
return multipleTest;
}
else
{
var halfNumber = number/2;
if(modulo >= halfNumber)
{
return multipleTest + (number-modulo);
}
else
{
return multipleTest - modulo;
}
}
}
To check if a number is a multiple of another then compare their modulo to 0:
function isMultiple(multipleTest,number)
{
return 0 == multipleTest%number;
}
You might want to add some validations for 0 in case you expect any inside closestMultiple.
The value of index computed as you put it
index = round((start - initial)/skip) * skip + initial
is indeed the one that minimizes the distance between the sequence with general term
aj = j * skip + initial
and start.
Therefore, index can only be negative if start lies to the left of
(a-1 + a0)/2 = initial - skip/2
in other words, if
start < initial - skip/2.
So, only in that case you have to redefine index to 0. In pseudo code:
IF (start < (initial - skip/2))
index = 0
ELSE
index = round((start - initial)/skip) * skip + initial
Alternatively, you could do
index = round((start - initial)/skip) * skip + initial
IF index < 0 THEN index = 0
which is the same.
No while loop required:
function newNum(newstart, initial, skip) {
var xLow = newstart + Math.abs(newstart - initial) % skip;
var xHi = newstart + skip;
var result = (xLow + xHi) / 2 > newstart ? xLow : xHi;
if (result < 0) result += skip;
return result;
}
Take the distance between your new starting point and your initial value, and find out what the remainder would be if you marched towards that initial value (Modulus gives us that). Then you just need to find out if the closest spot is before or after the starting point (I did this be comparing the midpoint of the low and high values to the starting point).
Test:
newNum(1, 20, 7) = 6
newNum(-1, 20, 7) = 6
newNum(180, 10, 3) = 182
(Even though you stated in your comments that the range of the new starting point is within the array bounds, notice that it doesn't really matter).

Categories