|
| 1 | +""" |
| 2 | +Traveling Shopper |
| 3 | +Given an amount of money you have, and an array of items you want to buy, determine how many of them you can afford. |
| 4 | +
|
| 5 | +The given amount will be in the format ["Amount", "Currency Code"]. For example: ["150.00", "USD"] or ["6000", "JPY"]. |
| 6 | +Each array item you want to purchase will be in the same format. |
| 7 | +Use the following exchange rates to convert values: |
| 8 | +
|
| 9 | +Currency 1 Unit Equals |
| 10 | +USD 1.00 USD |
| 11 | +EUR 1.10 USD |
| 12 | +GBP 1.25 USD |
| 13 | +JPY 0.0070 USD |
| 14 | +CAD 0.75 USD |
| 15 | +If you can afford all the items in the list, return "Buy them all!". |
| 16 | +Otherwise, return "Buy the first X items.", where X is the number of items you can afford when purchased in the order given. |
| 17 | +
|
| 18 | +""" |
| 19 | + |
| 20 | +import unittest |
| 21 | + |
| 22 | +class TravelingShopperTest(unittest.TestCase): |
| 23 | + |
| 24 | + def test1(self): |
| 25 | + self.assertEqual(buy_items(["150.00", "USD"], [["50.00", "USD"], ["75.00", "USD"], ["30.00", "USD"]]),"Buy the first 2 items.") |
| 26 | + |
| 27 | + def test2(self): |
| 28 | + self.assertEqual(buy_items(["200.00", "EUR"], [["50.00", "USD"], ["50.00", "USD"]]),"Buy them all!") |
| 29 | + |
| 30 | + def test3(self): |
| 31 | + self.assertEqual(buy_items(["100.00", "CAD"], [["20.00", "USD"], ["15.00", "EUR"], ["10.00", "GBP"], ["6000", "JPY"], ["5.00", "CAD"], ["10.00", "USD"]]),"Buy the first 3 items.") |
| 32 | + |
| 33 | + def test4(self): |
| 34 | + self.assertEqual(buy_items(["5000", "JPY"], [["3.00", "USD"], ["1000", "JPY"], ["5.00", "CAD"], ["2.00", "EUR"], ["4.00", "USD"], ["2000", "JPY"]]),"Buy them all!") |
| 35 | + |
| 36 | + def test5(self): |
| 37 | + self.assertEqual(buy_items(["200.00", "USD"], [["50.00", "USD"], ["40.00", "EUR"], ["30.00", "GBP"], ["5000", "JPY"], ["25.00", "CAD"], ["20.00", "USD"]]),"Buy the first 5 items.") |
| 38 | + |
| 39 | + |
| 40 | + |
| 41 | +def buy_items(funds, items): |
| 42 | + |
| 43 | + currency_table = { |
| 44 | + "USD": 1.00, |
| 45 | + "EUR": 1.10, |
| 46 | + "GBP": 1.25, |
| 47 | + "JPY": 0.0070, |
| 48 | + "CAD": 0.75 |
| 49 | + } |
| 50 | + total_money, currency = funds |
| 51 | + currency_value = currency_table[currency] |
| 52 | + total_money_in_usd = float(total_money) * currency_value |
| 53 | + # total_money = float(total_money) |
| 54 | + |
| 55 | + |
| 56 | + |
| 57 | + total = 0 |
| 58 | + result_list = [] |
| 59 | + |
| 60 | + for price, curr in items: |
| 61 | + price = float(price) * currency_table[curr] |
| 62 | + result_list.append(price) |
| 63 | + total += price |
| 64 | + if total == total_money_in_usd: |
| 65 | + break |
| 66 | + elif total < total_money_in_usd: |
| 67 | + continue |
| 68 | + else: |
| 69 | + result_list.pop() |
| 70 | + break |
| 71 | + |
| 72 | + # print(result_list) |
| 73 | + |
| 74 | + if total == total_money_in_usd or total < total_money_in_usd: |
| 75 | + return "Buy them all!" |
| 76 | + else: |
| 77 | + return f"Buy the first {len(result_list)} items." |
| 78 | + |
| 79 | +""" |
| 80 | +There are issues with the above code |
| 81 | +
|
| 82 | +1. Logic for "Buy them all!" |
| 83 | + -> The check: |
| 84 | + if total == total_money_in_usd or total < total_money_in_usd": |
| 85 | + return "Buy them all!" |
| 86 | +
|
| 87 | + -> But this condition is true even if you din't buy all items. |
| 88 | + Example. total_money_in_usd = 150 USD, items= [50, 60] |
| 89 | + -> After buying both, total= 110 and total_money_in_usd = 150 |
| 90 | + -> Condition total < total_money_in_usd is true -> returns "Buy them all!" It is correct in some way though. |
| 91 | + 1. The correct check should be: did we buy all items? not just whether total <= total_money_in_usd. |
| 92 | +
|
| 93 | + 2. Result Tracking |
| 94 | + -> The above solution append every item cost to result_list, then pop if you overshoot. |
| 95 | + -> This works but is bit clunky. You can simply track a counter of how may items were bought. |
| 96 | +
|
| 97 | + 3. Exact equality check |
| 98 | + -> Floating point comparisons |
| 99 | + total == total_money_in_usd => can be unreliable. |
| 100 | + -> Better to check if you managed to buy all items, regardless of leftover money. |
| 101 | +
|
| 102 | +
|
| 103 | + |
| 104 | +The Logic flow of above solution |
| 105 | +
|
| 106 | +=> Accumulating total (spent so far). |
| 107 | +=> if total == wallet -> break (perfectly spent). |
| 108 | +=> if total < wallet -> continue (still have leftover) |
| 109 | +=> if total > wallet -> pop last item and break (ran out of money). |
| 110 | +
|
| 111 | +At the end: |
| 112 | + checking the condition |
| 113 | + if total == total_money_in_usd or total < total_money_in_usd: |
| 114 | + return "Buy them all!" |
| 115 | + else: |
| 116 | + return "Buy the first {len(result_list)} items." |
| 117 | +
|
| 118 | +
|
| 119 | +This can mislabel |
| 120 | +
|
| 121 | +Suppose: |
| 122 | +funds = ["150", "USD"] |
| 123 | +items = [["50", "USD"],["60", "USD"],["40", "USD"]] |
| 124 | +
|
| 125 | +-> Wallet = 150 |
| 126 | +-> Buy 50 -> total = 50 |
| 127 | +-> Buy 60 -> total = 110 |
| 128 | +-> Try 40 -> total = 150 -> equal -> break |
| 129 | +-> Works fine here. |
| 130 | +
|
| 131 | +
|
| 132 | +But now: |
| 133 | +funds = ["150", "USD"] |
| 134 | +items = [["50", "USD"], ["60", "USD"]] |
| 135 | +
|
| 136 | +-> Wallet = 150 |
| 137 | +-> Buy 50 -> total = 50 |
| 138 | +-> Buy 60 -> total = 110 |
| 139 | +-> End of list, total=110 < wallet |
| 140 | +-> The above condition says "Buy them all!" because total < wallet. |
| 141 | +-> That's corret in this case (you did buy all items, leftover is fine.) |
| 142 | +So far so good. |
| 143 | +
|
| 144 | +⚠️ Where it breaks |
| 145 | +The problem is the above condition doesn't actually check if all items were bought. It only checks |
| 146 | +if total <= wallet. That can be true even if you broke out early. |
| 147 | +
|
| 148 | +Example: |
| 149 | +
|
| 150 | +funds = ["100", "USD"] |
| 151 | +items = [["50", "USD"], ["60", "USD"], ["20", "USD"]] |
| 152 | +
|
| 153 | +-> Wallet = 100 |
| 154 | +-> Buy 50 -> total = 50 |
| 155 | +-> Try 60 -> total = 110 > wallet -> pop last item ,break |
| 156 | +-> End: total=50 < wallet |
| 157 | +
|
| 158 | +The above condition says "Buy them all!" because total < wallet |
| 159 | +
|
| 160 | +-> But you only bought 1 item, not all. |
| 161 | +
|
| 162 | +
|
| 163 | +""" |
| 164 | +""" |
| 165 | +Why this solution is safer |
| 166 | +
|
| 167 | +instead of comparing totals, this solution checks: |
| 168 | +
|
| 169 | +if count == len(items): |
| 170 | + return "Buy them all!" |
| 171 | +
|
| 172 | +That way, we only say "Buy them all!" if we actually iterated through and purchased |
| 173 | +every item in the list. Leftover money doesn't matter - you can still have leftover and it's fine. |
| 174 | +
|
| 175 | +Key Takeaway. |
| 176 | +
|
| 177 | +-> The previous solution works when you naturally reach the end of the list, but it can misfire if you break early due to overspending. |
| 178 | +-> The safer check is: did we buy all items? not just is total <= wallet |
| 179 | +-> Leftover money is perfectly fine - the difference is whether you stopped because you ran out or because |
| 180 | + you finished the list. |
| 181 | +
|
| 182 | +
|
| 183 | +This version |
| 184 | +-> Always normalize to USD using the exchange rates. |
| 185 | +-> Deduct item cost sequentially. |
| 186 | +-> Stop when funds run out. |
| 187 | +-> Return either "Buy them all!" or "Buy the first X items.". |
| 188 | +""" |
| 189 | +# This is the corrected version |
| 190 | + |
| 191 | +def buy_items(funds, items): |
| 192 | + |
| 193 | + currency_table = { |
| 194 | + "USD": 1.00, |
| 195 | + "EUR": 1.10, |
| 196 | + "GBP": 1.25, |
| 197 | + "JPY": 0.0070, |
| 198 | + "CAD": 0.75 |
| 199 | + } |
| 200 | + |
| 201 | + total_money, currency = funds |
| 202 | + total_money_in_usd = float(total_money) * currency_table[currency] |
| 203 | + |
| 204 | + count = 0 |
| 205 | + for price, curr in items: |
| 206 | + cost = float(price) * currency_table[curr] |
| 207 | + if total_money_in_usd >= cost: |
| 208 | + total_money_in_usd -= cost |
| 209 | + count += 1 |
| 210 | + else: |
| 211 | + break |
| 212 | + if count == len(items): |
| 213 | + return "Buy them all!" |
| 214 | + else: |
| 215 | + return f"Buy the first {count} items." |
| 216 | + |
| 217 | + |
| 218 | +if __name__ == "__main__": |
| 219 | + print(buy_items(["150.00", "USD"], [["50.00", "USD"], ["75.00", "USD"], ["30.00", "USD"]])) |
| 220 | + print(buy_items(["200.00", "EUR"], [["50.00", "USD"], ["50.00", "USD"]])) |
| 221 | + |
| 222 | + print(buy_items(["100.00", "CAD"], [["20.00", "USD"], ["15.00", "EUR"], ["10.00", "GBP"], ["6000", "JPY"], ["5.00", "CAD"], ["10.00", "USD"]])) |
| 223 | + unittest.main() |
0 commit comments