1

I have two arrays and for each number in the first array I need to find the largest number from the second array that fits into that number and break the first number into its components. Since 25 from the second array is the largest number that fits into 80 of the first, I need to transform 80 into two numbers - 75, 5. Likewise for 6 and 5 the result should be 5, 1. So the end result should be an array [75, 5, 5, 1]

let arr = [80, 6]
let arr2 = [25, 5]
for (let x of arr) {
    for (let y of arr2) {
        if (x / y > 1 && x / y < 4) {
        let mlt = Math.floor(x / y)
        largestFit = y * mlt
        arr.splice(arr.indexOf(x), 1, largestFit)
        }   
    }
}

console.log(arr)

The code above gives [75, 5] so thought I could add one more splice operation to insert the remainders, but doing this arr.splice(arr.indexOf(x + 1), 0, x - largestFit) just crashes the code editor. Why isn't this working and what is the solution? Thank you.

Qaqli
  • 179
  • 7
  • `x + 1` is unlikely to be a value in `arr`, so indexOf will return -1 in that case. – James May 30 '22 at 20:17
  • @James but index of ````6```` is 1 and i'm able to insert elements using splice() at index 2 – Qaqli May 30 '22 at 20:18
  • If the first number would have been 82, and it would split into 75, 7, should then the 7 still be split into 5 and 2, or should it always be left as is? – trincot May 30 '22 at 20:18
  • Why does your `if` check for what it checks? What is so special about 4? – trincot May 30 '22 at 20:19
  • Perhaps you meant `arr.indexOf(x) + 1` – James May 30 '22 at 20:19
  • @trincot for my project yes, it should break down as much as possible, but i didn't mention that here for simplicity's sake. I figured i could use recursion once i figure this out – Qaqli May 30 '22 at 20:21
  • @trincot it's just the default condition from my project, I'm writing a function that calculates the amount of change due based on cash received, i know i'm not doing it the most efficient way but i'm tryna see if my logic leads me to the solution. I'm almost there, just need to figure out the breaking down of numbers – Qaqli May 30 '22 at 20:22
  • @James ahh yes, you're right, i fixed it and now it inserts the remainders but for some reason it's not inserting them in order – Qaqli May 30 '22 at 20:26
  • I still don't understand why `x / y < 4` is a condition? Why is there such a limit? What should happen if instead of 80 we have 180? Should 180 then not be split into 175 and 5? – trincot May 30 '22 at 20:30
  • @trincot for the full second array includes denominations up to 100, if the cash received exceeds say 400, then i modify the conditional statement – Qaqli May 30 '22 at 20:34
  • Why would you *keep* it? What advantage does it bring? I don't see any? It seems you have pre-knowledge about the values you'll find in `arr2`, but shouldn't an algorithm be independent from that, and work well with *any* value it will find in `arr2`? – trincot May 30 '22 at 20:34
  • @trincot i know it's very sketchy, ideally for each number in the first array i would like to find the largest number that fits into it from the second, but i can't think of any other way to do it other than hardcoding conditions. Do you have a suggestion? – Qaqli May 30 '22 at 20:41
  • Posted an answer with my suggestion. – trincot May 30 '22 at 21:04

2 Answers2

0

It is not advised to splice an array that is being iterated, and it is the reason why your loop got suck sometimes.

Instead build a new array, so it doesn't affect the iteration on the input array.

If you first sort the second array in descending order, you can then find the first value in that array that fits the amount, and be sure it is the greatest one. For sorting numerically in descending order, you can use sort with a callback function.

Once you have found the value to subtract, you can use the remainder operator (%) to determine what is left over after this subtraction.

function breakDown(amounts, coins) {
    // Get a sorted copy (descending) of denominations:
    coins = [...coins].sort((a, b) => b - a);
    const result = []; // The results are stored here
    for (let amount of amounts) {
        for (let coin of coins) {
            if (coin <= amount) {
                result.push(amount - amount % coin);
                amount %= coin;
            }
        }
        if (amount) result.push(amount); // remainder
    }
    return result;
}

// Example 1
console.log(breakDown([80, 6], [25, 5]));
// Example 2
console.log(breakDown([100, 90, 6], [100, 75, 15, 5, 1]));

Explanations

The coins are sorted in descending order so that when we search for a fitting coin from left to right, we can be sure that if we find one, it will be the greatest one that fits, not just any. So for instance, if our amount is 7 and we have coins 2 and 5, we don't want to use coin 2 just yet -- we want to use coin 5 first. So if we sort those coins into [5, 2], and then start looking for the first coin that is smaller than our amount, we will be sure to find 5 first. The result would not be as expected if we would have found 2 first.

We can calculate the remainder of a division with the % operator, and there is a shortcut for when we want to assign that remainder back to the amount: it is the %= operator. amount %= coin is short for amount = amount % coin.

When the inner loop completes, it might be that amount is not zero, i.e. there is still an amount that remains that cannot be consumed by any available coin. In that case we want to still have that remainder in the result array, so we push it.

Often, the amount will already be zero when the loop ends. This will be ensured when the smallest coin is the smallest unit of amount one can expect. For instance if the amount is expressed as an integer, and the smallest coin is 1, then there will never be any remainder left when the inner loop has completed. If however the smallest coin would be 2, and we are left with an a amount of 1, then there is no way to reduce that amount to zero, so after the loop ends, we could be stuck with that remainder. And so we have this code to deal with that:

if (amount) result.push(amount)

Floating point

Be careful with using non-integers: floating point arithmetic does not always lead to expected results, because numbers like 0.1 cannot be accurately represented in floating point. You can end up with a non-zero amount after the inner loop finishes, when zero would have been expected. That amount will be tiny like 1e-15, and really indicates there was such an inaccuracy at play.

When calculating with monetary amounts, it is common practice to do that in number of cents, so to make all calculations based on integers. This will give reliable results.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • Thank you, there's one more modification i need to make. Currently I have an array ````[1, 0.9, 0.06 ]```` and the second one is ````[1, 0.75, 0.05 ]````. If i remove the 1's it gives the desired results, however i need to keep them. So the result should be ````[1, 0.75, 0,15, 0.05, 0.01]````. Is there a way to achieve this? – Qaqli May 30 '22 at 21:21
  • Why do you want the output to start with 1? Isn't 0.75 the largest value that is smaller than 1, which means that 1 needs to split into 0.75 and 0.25? – trincot May 30 '22 at 21:27
  • OK, I updated the snippet, so that because of the 1 in the second array, it will not consider that 0.75 for the first amount. Note that if you use floating point values, there will be imprecision. It is better to work with integers (e.g. cents instead of dollars) – trincot May 30 '22 at 21:30
  • It all depends on the cash received. I have two arrays, one for denominations which acts as the second array and one array holding the broken down cash, so ````1.75```` is ````[1, 0.7, 0.05]````. Here's the denominations array ````[0.01, 0.05, 0.1, 0.25, 1, 5, 10, 20, 100]````. I compare each value of cash against the den. array and on first iteration it has to be the largest fitting value, which for ````1```` is ````1```` and for ````0.7```` is ````0.25````, but since 0.25 doesn't fit evenly, 0.75 becomes 0.5, 0.15, and i keep breaking them down recursively until they all can contain even den – Qaqli May 30 '22 at 21:34
  • The updated code gives an unexpected token error – Qaqli May 30 '22 at 21:36
  • Maybe your js doesnt support underscore in number literal. Remove it then. – trincot May 31 '22 at 02:45
  • thank you very much, it seems to be working! I almost solved it with my original method but any initial number over 10 other than 15 was crashing the editor and I've no idea why but 15 gives the desired result. I posted the question on FCC, could you please take a look at it? I just want to know why it doesn't work this way and where the error is https://forum.freecodecamp.org/t/increasing-number-by-1-crashes-code-editor/513200 – Qaqli May 31 '22 at 04:51
  • I have commented on your answer about that. – trincot May 31 '22 at 05:54
  • @Thank you, I have a few questions about the code you provided. Why is it necessary to sort the coins array in descending order? What does amount %= coin; do? And lastly, what exactly does if (amount) result.push(amount); do? Because if i remove it, some remainders are still pushed while others are not. – Qaqli May 31 '22 at 06:35
  • I added an explanation to my answer. – trincot May 31 '22 at 06:40
  • so ````amount %= coin; ```` gets pushed into array and broken down until there is no remainder, at which point the loop moves on to the next ````amount```` of ````amounts````? – Qaqli May 31 '22 at 07:04
  • Yes, it is like that. – trincot May 31 '22 at 07:27
  • 1
    thanks a lot for all your help, I hope I can get to your level of proficiency one day – Qaqli May 31 '22 at 17:03
-1

I found the issue. After the first splice() operation indexOf(x) was returning -1, since x's are being replaced, so the solution is to assign indexOf(x) to a variable and use that variable for both splice() operations.

let arr = [80, 6]
let arr2 = [25, 5]
for (let x of arr) {
    for (let y of arr2) {
        if (x / y > 1 && x / y < 4) {
        let mlt = Math.floor(x / y)
        largestFit = y * mlt
        let idx = arr.indexOf(x)
        arr.splice(idx, 1, largestFit)
        arr.splice(idx + 1, 0, x - largestFit)
        }
    }
}

console.log(arr)
Qaqli
  • 179
  • 7
  • If first value is 75, it inserts a 0 into the array. Is this intended? – trincot May 30 '22 at 21:12
  • No but i would filter out ````0```` values later on – Qaqli May 30 '22 at 21:22
  • Another problem is that this will potentially find more than one possible denomination `y` in the inner loop that is a match for `x`. For instance an input of [12] with [5, 10] will use 5 first and then 10. This is a problem, as then the second time there is no more `x` in the array, and so `indexOf` returns -1, leading to an infinite loop. – trincot May 31 '22 at 05:54
  • @trincot i see, thank you! It's odd then that 15 works for some reason, is that a bug? – Qaqli May 31 '22 at 06:04
  • That's because there is no remainder, and `x` is re-inserted by the first `splice`. Did you [debug](https://stackoverflow.com/questions/988363/how-can-i-debug-my-javascript-code)? You can use tools to step through the code, set breakpoints and inspect variables. This should definitely help you understand what is going on. – trincot May 31 '22 at 06:11
  • @I'll look into that, thank you,. I have a few questions about the code you provided. Why is it necessary to sort the coins array in descending order? What does ````amount %= coin;```` do? And lastly, what exactly does ````if (amount) result.push(amount);```` do? Because if i remove it, some remainders are still pushed while others are not. – Qaqli May 31 '22 at 06:22
  • If you have a question about my code, then it is better ask it below my answer, not here ;-) – trincot May 31 '22 at 06:28