{"id":"dsa-dp-coin-change","slug":"coin-change","title":"Coin change","text":"A vending machine has to give change. With the coins 1, 5, 10 and 25, the obvious way is greedy, like fitting the most meetings into a room in the module on sorting: take the biggest coin that fits, again and again. For these coins greedy happens to give the fewest coins. For others it doesn't:\n\n```python\ndef greedy(coins, amount):\n    count = 0\n    for coin in sorted(coins, reverse=True):\n        count += amount // coin       # as many of this coin as fit\n        amount %= coin\n    return count\n\n\nprint(greedy([1, 5, 10, 25], 63))\nprint(greedy([1, 4, 5], 8))\n```\n\n```output\n6\n4\n```\n\n63 is 25 + 25 + 10 + 1 + 1 + 1, six coins. But 8 comes out as 5 + 1 + 1 + 1, four coins, when 4 + 4 needs only two. Once greedy has taken the 5, it's stuck with the 3 that's left, and it never goes back to ask whether a smaller first coin would leave a better rest. DP does ask that, for every coin, and it can afford to: it works out each amount only once.\n\n### The fewest coins\n\nWrite `best[a]` for the smallest number of coins that add up to `a`. However the best way to make `a` looks, it ends with some **last coin** `c`. Take that coin away and `a - c` is left, made in as few coins as possible too, or there would be a better way to make `a`. So try each coin `c` that fits, and `best[a]` is the smallest `best[a - c] + 1` among them. No coins at all make 0, so `best[0] = 0`, and the list fills in for `a = 1, 2, 3` and on: `a - c` is always a smaller amount, already done.\n\nSome amounts can't be made at all: with only 3s and 5s, there's no way to make 7. They need a value that means \"impossible\" and never wins the smallest. **`math.inf`** is a float bigger than every number, and `inf + 1` is still `inf`, so an amount that can't be made stays that way:\n\n```python\nfrom math import inf\n\n\ndef fewest_table(coins, amount):\n    best = [0] + [inf] * amount\n    for a in range(1, amount + 1):\n        for coin in coins:\n            if coin <= a and best[a - coin] + 1 < best[a]:\n                best[a] = best[a - coin] + 1\n    return best\n\n\nprint(fewest_table([1, 4, 5], 8))\nprint(fewest_table([5, 3], 7))\n```\n\n```output\n[0, 1, 2, 3, 1, 1, 2, 3, 2]\n[0, inf, inf, 1, inf, 1, 2, inf]\n```\n\n`best[8]` is 2, from `best[4] + 1`: a 4 on top of the best way to make 4. The 5 was tried too, but `best[3] + 1` is 4. In the second table, 1, 2, 4 and 7 can't be made, and their entries are still `inf`. A function that answers -1 for \"impossible\" checks for `inf` at the end. With k coin values, each amount looks at k coins: O(amount × k) time and O(amount) memory. That depends on the amount itself, not on its number of digits: an amount of a billion needs a billion entries.\n\n### Counting the ways\n\nIn how many ways can you make 4 with the coins 1, 2 and 3? There's 3 + 1, 2 + 2, 2 + 1 + 1 and 1 + 1 + 1 + 1: four, where 3 + 1 and 1 + 3 count as the same way. Let `ways[a]` be the number of ways to make `a`, with `ways[0] = 1` (the empty way). Here the order of the two loops decides **what** you count:\n\n```python\ndef combinations(coins, amount):\n    ways = [1] + [0] * amount\n    for coin in coins:                       # coins outside\n        for a in range(coin, amount + 1):\n            ways[a] += ways[a - coin]\n    return ways[amount]\n\n\ndef orderings(coins, amount):\n    ways = [1] + [0] * amount\n    for a in range(1, amount + 1):           # amounts outside\n        for coin in coins:\n            if coin <= a:\n                ways[a] += ways[a - coin]\n    return ways[amount]\n\n\nprint(combinations([1, 2, 3], 4), orderings([1, 2, 3], 4))\n```\n\n```output\n4 7\n```\n\n- **Coins outside:** the first round makes every amount with 1s only, the second adds 2s on top of those ways, the third adds 3s. Every way is built with its coins in the order of the list, so 1 + 1 + 2 is built once, and never as 2 + 1 + 1. That counts **combinations**.\n- **Amounts outside:** for each amount the last coin can be any coin, so 2 + 1 + 1, 1 + 2 + 1 and 1 + 1 + 2 are three different ways. That counts **orderings**, like the climbs of the last step, where 1 + 2 and 2 + 1 were different.\n\nMixing the two up is the classic coin change bug: no error, just a bigger number. Both cost O(amount × k) time and O(amount) memory. A backtracking search that took or left each coin, coming back to the same coin again and again, would count the ways one at a time, and that takes as long as there are ways: 642 million for an amount of 300 with the coins 3 to 12.\n","task":"This program fills the table of the ways to make each amount from 0 to 7 with the coins 2 and 3, coins outside, and prints the table after each coin's round. What does it print? Type each line it prints.\n","kind":"predict","seed":"ways = [1, 0, 0, 0, 0, 0, 0, 0]\nfor coin in [2, 3]:\n    for a in range(coin, 8):\n        ways[a] += ways[a - coin]\n    print(ways)\n","solution":"ways = [1, 0, 0, 0, 0, 0, 0, 0]\nfor coin in [2, 3]:\n    for a in range(coin, 8):\n        ways[a] += ways[a - coin]\n    print(ways)\n","hints":["After the round for the coin 2, only even amounts can be made, each in exactly one way.","In the round for 3, go from `a = 3` up and add `ways[a - 3]` to each entry. Entries you've just changed in this round count: `ways[6]` adds the new `ways[3]`.","6 is 2 + 2 + 2 or 3 + 3, and 7 is 2 + 2 + 3 only."],"tests":[{"name":"Output","stdin":"","expect":"[1, 0, 1, 0, 1, 0, 1, 0]\n[1, 0, 1, 1, 1, 1, 2, 1]","hidden":false}],"require":[],"forbid":[],"lines":["[1, 0, 1, 0, 1, 0, 1, 0]","[1, 0, 1, 1, 1, 1, 2, 1]"],"more":[{"task":"Write `min_coins(coins, amount)`. It returns the smallest number of coins whose values add up to `amount`, with as many coins of each value as you like. When no mix of the coins reaches `amount` exactly, it returns -1. The tests go up to amounts of 80,000, so fill a table from the bottom up.\n\nFor example, `print(min_coins([1, 4, 5], 8), min_coins([5, 3], 7))` prints:\n\n```\n2 -1\n```\n","kind":"code","seed":"def min_coins(coins, amount):\n    pass\n\n\nif __name__ == \"__main__\":\n    print(min_coins([1, 4, 5], 8), min_coins([5, 3], 7))\n","solution":"from math import inf\n\n\ndef min_coins(coins, amount):\n    best = [0] + [inf] * amount\n    for a in range(1, amount + 1):\n        for coin in coins:\n            if coin <= a and best[a - coin] + 1 < best[a]:\n                best[a] = best[a - coin] + 1\n    if best[amount] == inf:\n        return -1\n    return best[amount]\n\n\nif __name__ == \"__main__\":\n    print(min_coins([1, 4, 5], 8), min_coins([5, 3], 7))\n","hints":["Make `best = [0] + [inf] * amount` (with `from math import inf`): 0 coins make 0, and every other amount is impossible until a way is found.","Go through the amounts `a` from 1 up to `amount`. For each coin that isn't bigger than `a`, `best[a - coin] + 1` is a candidate: keep it in `best[a]` when it's smaller. An impossible `best[a - coin]` is `inf`, and `inf + 1` never wins.","At the end, `best[amount]` is still `inf` when the amount can't be made: return -1 then."],"tests":[{"name":"print(min_coins([1, 4, 5], 8), min_coins([5, 3], 7))","stdin":"","call":"print(min_coins([1, 4, 5], 8), min_coins([5, 3], 7))\n","expect":"2 -1","hidden":false},{"name":"min_coins([4], 7)","stdin":"","call":"min_coins([4], 7)\n","expect":"-1","hidden":true,"returns":true},{"name":"min_coins([9], 0)","stdin":"","call":"min_coins([9], 0)\n","expect":"0","hidden":true,"returns":true},{"name":"min_coins([6, 4], 6)","stdin":"","call":"min_coins([6, 4], 6)\n","expect":"1","hidden":true,"returns":true},{"name":"min_coins([1, 5, 10, 25], 41)","stdin":"","call":"min_coins([1, 5, 10, 25], 41)\n","expect":"4","hidden":true,"returns":true},{"name":"min_coins([4, 10, 25], 41)","stdin":"","call":"min_coins([4, 10, 25], 41)\n","expect":"5","hidden":true,"returns":true},{"name":"min_coins([9, 6, 5, 1], 11)","stdin":"","call":"min_coins([9, 6, 5, 1], 11)\n","expect":"2","hidden":true,"returns":true},{"name":"min_coins([1, 5, 10, 25], 300)","stdin":"","call":"min_coins([1, 5, 10, 25], 300)\n","expect":"12","hidden":true,"big":true,"returns":true},{"name":"min_coins([3, 7, 405, 436], 50_000)","stdin":"","call":"min_coins([3, 7, 405, 436], 50_000)\n","expect":"119","hidden":true,"big":true,"returns":true},{"name":"min_coins([173, 391, 67, 452], 80_000)","stdin":"","call":"min_coins([173, 391, 67, 452], 80_000)\n","expect":"179","hidden":true,"big":true,"returns":true}],"require":[],"forbid":[{"pattern":"setrecursionlimit","message":"Don't raise the recursion limit: the browser's Python can crash. Fill a table from the bottom up instead."}]},{"task":"Write `count_ways(coins, amount)`: the number of different ways to make `amount` from the coins, using each coin value any number of times, where ways with the same coins in another order count once. The coin values are different positive numbers.\n\nA search that tries the coins one by one, the backtracking way, finds the ways one at a time. That's far too slow here: the tests include an amount of 300 with the coins 3 to 12, which can be made in 642 million ways, and others go up to an amount of 20,000, far deeper than recursion can go. Fill a table, with the loops in the order that counts combinations.\n\nFor example, `count_ways([2, 5, 3, 6], 10)` returns 5 (5 + 5, 2 + 2 + 6, 2 + 3 + 5, 2 + 2 + 3 + 3 and 2 + 2 + 2 + 2 + 2):\n\n```\n5\n```\n","kind":"code","seed":"def count_ways(coins, amount):\n    pass\n\n\nif __name__ == \"__main__\":\n    print(count_ways([2, 5, 3, 6], 10))\n","solution":"def count_ways(coins, amount):\n    ways = [1] + [0] * amount\n    for coin in coins:\n        for a in range(coin, amount + 1):\n            ways[a] += ways[a - coin]\n    return ways[amount]\n\n\nif __name__ == \"__main__\":\n    print(count_ways([2, 5, 3, 6], 10))\n","hints":["Make `ways = [1] + [0] * amount`: there's one way to make 0, with no coins.","The outer loop goes through the coins. For each coin, the inner loop goes through the amounts from that coin's value up to `amount`, and each amount gains `ways[a - coin]`.","With the amounts on the outside you'd count 2 + 3 + 5 and 5 + 3 + 2 as two ways: the first test's answer would be 17."],"tests":[{"name":"count_ways([2, 5, 3, 6], 10)","stdin":"","call":"count_ways([2, 5, 3, 6], 10)\n","expect":"5","hidden":false,"returns":true},{"name":"count_ways([4], 7)","stdin":"","call":"count_ways([4], 7)\n","expect":"0","hidden":true,"returns":true},{"name":"count_ways([6], 0)","stdin":"","call":"count_ways([6], 0)\n","expect":"1","hidden":true,"returns":true},{"name":"count_ways([5, 2, 3], 10)","stdin":"","call":"count_ways([5, 2, 3], 10)\n","expect":"4","hidden":true,"returns":true},{"name":"count_ways([1, 5, 10, 25], 100)","stdin":"","call":"count_ways([1, 5, 10, 25], 100)\n","expect":"242","hidden":true,"returns":true},{"name":"count_ways(list(range(3, 13)), 300)","stdin":"","call":"count_ways(list(range(3, 13)), 300)\n","expect":"642677232","hidden":true,"big":true,"returns":true},{"name":"count_ways([1, 2, 5, 10, 20, 50, 100, 200], 20_000)","stdin":"","call":"count_ways([1, 2, 5, 10, 20, 50, 100, 200], 20_000)\n","expect":"135830481354545201","hidden":true,"big":true,"returns":true}],"require":[],"forbid":[{"pattern":"setrecursionlimit","message":"Don't raise the recursion limit: the browser's Python can crash. Fill a table from the bottom up instead."}]}]}