|
1 | 1 | # [Problem 273: Integer to English Words](https://leetcode.com/problems/integer-to-english-words/description/?envType=daily-question)
|
2 | 2 |
|
3 | 3 | ## Initial thoughts (stream-of-consciousness)
|
| 4 | +- I think this problem will need to be solved partly by hard-coding some number-to-word mappings, and partly by applying patterns |
| 5 | +- We'll need to start parsing by converting the number (integer) to a string |
| 6 | +- There's also a recursive component-- e.g., 12345 needs to be split into: |
| 7 | + - 12 (parsed as "Twelve", recursively using the "hundreds" parsing code) + " Thousand" |
| 8 | + - 345 (parsed as "Three Hundred Forty Five") |
| 9 | +- First, let's write a function for parsing 0--9 (single digits) |
| 10 | +- Then we can write a function for parsing 10--99 (double digits) |
| 11 | + - 10--19 need to be coded as special cases |
| 12 | + - After that, we just need to hard code in Twenty, Thirty, ..., Ninety + a call to the function for parsing single digits |
| 13 | +- To parse hundreds, we simply: |
| 14 | + - Parse the single (leading) number ("" if 0) and add " Hundred" |
| 15 | + - Then add " " + parse of the remaining two digits |
| 16 | +- To parse thousands, we can parse hundreds and add " Thousand", and similarly for parsing millions and billions |
| 17 | + - We don't need to go beyond billions, since `0 <= num <= 2^31 - 1` |
| 18 | + - To do this efficiently, let's write a base function that includes an argument for the prefix |
| 19 | +- Then we can just call the functions in blocks of 3 digits: |
| 20 | + - First the last 3: parse using "hundreds" |
| 21 | + - Next, the second-to-last 3: parse using "thousands" and prepend to the result |
| 22 | + - Next, the third-to-last 3: parse using "millions" and prepend to the result |
| 23 | + - Finally, the fourth-to-last "3": parse using "billions" and prepend to the result |
| 24 | +- The main "trickiness" in this problem will come from making sure we cover all of the edge cases. We'll need to test carefully. |
4 | 25 |
|
5 | 26 | ## Refining the problem, round 2 thoughts
|
6 |
| - |
| 27 | +- We'll need the following functions (note: here I'm being sloppy with notation by not being explicit about converting to ints or strs, but in the actual implementation we'll need to handle type casting correctly): |
| 28 | + - `ParseSingle(x)`: maps single digits 0--9 onto words (hard code) |
| 29 | + - `ParseDouble(x)`: maps double digits 10--99 onto words: |
| 30 | + - Hard code 10--19 |
| 31 | + - Hard code multiples of 10 between 20 and 90, inclusive |
| 32 | + - If `x > 19` and `x % 10 != 0` then return `ParseDouble(10 * x // 10) + " " + ParseSingle(x[1])` |
| 33 | + - `ParseTriple(x)`: maps triple digits onto words: |
| 34 | + - This is straightforward: |
| 35 | + - If `len(x) == 1` return `ParseSingle(x)` |
| 36 | + - Else if `len(x) == 2` return `ParseDouble(x)` |
| 37 | + - Else if `x % 100 == 0` return `ParseSingle(x[0]) + " Hundred"` |
| 38 | + - Otherwise return `ParseSingle(x[0] + " " + suffix + " Hundred " + ParseDouble([x[1:]]) |
| 39 | + - `ParseThousands(x)`: |
| 40 | + - return `ParseTriple(x) + " Thousand"` |
| 41 | + - `ParseMillions(x)`: |
| 42 | + - return `ParseTriple(x) + " Million"` |
| 43 | + - `ParseBillions(x)`: |
| 44 | + - return `ParseTriple(x) + " Billion"` |
| 45 | + - `Parse(x)`: |
| 46 | + - Break `x` into chunks of 3 (starting from the end)-- we could just hard code in the cases, since there aren't many of them |
| 47 | + - If `1 <= len(x) <= 3`: |
| 48 | + - return `ParseTriple(x)` |
| 49 | + - Elif `4 <= len(x) <= 6`: |
| 50 | + - return `ParseThousands(x[-6:-3]) + " " + ParseTriple(x[-3:])` |
| 51 | + - Elif `7 <= len(x) <= 9`: |
| 52 | + - return `ParseMillions(x[-9:-6]) + " " + ParseThousands(x[-6:-3]) + " " + ParseTriple(x[-3:])` |
| 53 | + - Elif `10 <= len(x) <= 12`: |
| 54 | + - return `ParseBillions(x[-12:-9]) + " " + ParseMillions(x[-9:-6]) + " " + ParseThousands(x[-6:-3]) + " " + ParseTriple(x[-3:])` |
| 55 | + - Note: actually, I think we can do this more cleanly by separately parsing the billions, millions, thousands, and hundreds, and then joining everything together. This will also make it easier to handle spacing. |
| 56 | +- Let's go with this. Again, though, we're going to need to test everything carefully using a bunch of example cases to be sure we've accounted for everything needed. |
7 | 57 | ## Attempted solution(s)
|
8 | 58 | ```python
|
9 |
| -class Solution: # paste your code here! |
10 |
| - ... |
| 59 | +class Solution: |
| 60 | + def numberToWords(self, num: int) -> str: |
| 61 | + if num == 0: |
| 62 | + return "Zero" |
| 63 | + |
| 64 | + def ParseSingle(x): |
| 65 | + map = {'0': 'Zero', '1': 'One', '2': 'Two', '3': 'Three', '4': 'Four', '5': 'Five', |
| 66 | + '6': 'Six', '7': 'Seven', '8': 'Eight', '9': 'Nine'} |
| 67 | + return map[x] |
| 68 | + |
| 69 | + def ParseDouble(x): |
| 70 | + map = {'10': 'Ten', '11': 'Eleven', '12': 'Twelve', '13': 'Thirteen', '14': 'Fourteen', '15': 'Fifteen', |
| 71 | + '16': 'Sixteen', '17': 'Seventeen', '18': 'Eighteen', '19': 'Nineteen', |
| 72 | + '20': 'Twenty', '30': 'Thirty', '40': 'Forty', '50': 'Fifty', |
| 73 | + '60': 'Sixty', '70': 'Seventy', '80': 'Eighty', '90': 'Ninety'} |
| 74 | + if x in map: |
| 75 | + return map[x] |
| 76 | + else: |
| 77 | + return map[str(10 * (int(x) // 10))] + " " + ParseSingle(x[1]) |
| 78 | + |
| 79 | + def ParseTriple(x): |
| 80 | + if len(x) == 1: |
| 81 | + return ParseSingle(x) |
| 82 | + elif len(x) == 2: |
| 83 | + return ParseDouble(x) |
| 84 | + elif int(x[0]) == 0: |
| 85 | + return ParseDouble(x[1:]) |
| 86 | + elif int(x[1:]) == 0: |
| 87 | + return ParseSingle(x[0]) + " Hundred" |
| 88 | + else: |
| 89 | + return ParseSingle(x[0]) + " Hundred " + ParseDouble(x[1:]) |
| 90 | + |
| 91 | + def ParseThousands(x): |
| 92 | + if int(x) == 0: |
| 93 | + return "" |
| 94 | + return ParseTriple(x) + " Thousand" |
| 95 | + |
| 96 | + def ParseMillions(x): |
| 97 | + if int(x) == 0: |
| 98 | + return "" |
| 99 | + return ParseTriple(x) + " Million" |
| 100 | + |
| 101 | + def ParseBillions(x): |
| 102 | + if int(x) == 0: |
| 103 | + return "" |
| 104 | + return ParseTriple(x) + " Billion" |
| 105 | + |
| 106 | + x = str(num) |
| 107 | + n = len(x) |
| 108 | + |
| 109 | + # Breaking into groups of 3 digits |
| 110 | + billion = x[-12:-9] if n > 9 else "" |
| 111 | + million = x[-9:-6] if n > 6 else "" |
| 112 | + thousand = x[-6:-3] if n > 3 else "" |
| 113 | + hundred = x[-3:] |
| 114 | + |
| 115 | + result = [] |
| 116 | + if billion: |
| 117 | + result.append(ParseBillions(billion)) |
| 118 | + if million: |
| 119 | + result.append(ParseMillions(million)) |
| 120 | + if thousand: |
| 121 | + result.append(ParseThousands(thousand)) |
| 122 | + if hundred: |
| 123 | + result.append(ParseTriple(hundred)) |
| 124 | + |
| 125 | + return ' '.join([x for x in result if len(x) > 0]) |
| 126 | +``` |
| 127 | +- Given test cases pass |
| 128 | +- Let's try a bunch of other cases: |
| 129 | + - 0: pass |
| 130 | + - 10: pass |
| 131 | + - 2918473: pass |
| 132 | + - 1478349587: pass |
| 133 | + - 49: pass |
| 134 | +- Ok...let's submit this! |
| 135 | + |
| 136 | + |
| 137 | + |
| 138 | +Uh oh...looks like I've missed something 😞. Womp womp 🎺. Let's see what's going on... |
| 139 | +- I think the issue is with `ParseDouble`: I forgot to handle cases where (if we're calling `ParseDouble` from `ParseTriple`) the result of `int(x) // 10` could be 0. I'm just going to hard code in that case by mapping `"0"` to `""` inside of `ParseDouble`. |
| 140 | +- However, when I do that, the spacing gets messed up. Rather than fixing it in a clean way (🙃) I'll just "hack" the solution at the end by splitting/joining the final result. |
| 141 | +- Revised solution: |
| 142 | + |
| 143 | +```python |
| 144 | +class Solution: |
| 145 | + def numberToWords(self, num: int) -> str: |
| 146 | + def ParseSingle(x): |
| 147 | + map = {'0': 'Zero', '1': 'One', '2': 'Two', '3': 'Three', '4': 'Four', '5': 'Five', |
| 148 | + '6': 'Six', '7': 'Seven', '8': 'Eight', '9': 'Nine'} |
| 149 | + return map[x] |
| 150 | + |
| 151 | + def ParseDouble(x): |
| 152 | + map = {'10': 'Ten', '11': 'Eleven', '12': 'Twelve', '13': 'Thirteen', '14': 'Fourteen', '15': 'Fifteen', |
| 153 | + '16': 'Sixteen', '17': 'Seventeen', '18': 'Eighteen', '19': 'Nineteen', |
| 154 | + '20': 'Twenty', '30': 'Thirty', '40': 'Forty', '50': 'Fifty', |
| 155 | + '60': 'Sixty', '70': 'Seventy', '80': 'Eighty', '90': 'Ninety', "0": ""} |
| 156 | + if x in map: |
| 157 | + return map[x] |
| 158 | + else: |
| 159 | + return map[str(10 * (int(x) // 10))] + " " + ParseSingle(x[1]) |
| 160 | + |
| 161 | + def ParseTriple(x): |
| 162 | + if len(x) == 1: |
| 163 | + return ParseSingle(x) |
| 164 | + elif len(x) == 2: |
| 165 | + return ParseDouble(x) |
| 166 | + elif int(x[0]) == 0: |
| 167 | + return ParseDouble(x[1:]) |
| 168 | + elif int(x[1:]) == 0: |
| 169 | + return ParseSingle(x[0]) + " Hundred" |
| 170 | + else: |
| 171 | + return ParseSingle(x[0]) + " Hundred " + ParseDouble(x[1:]) |
| 172 | + |
| 173 | + def ParseThousands(x): |
| 174 | + if int(x) == 0: |
| 175 | + return "" |
| 176 | + return ParseTriple(x) + " Thousand" |
| 177 | + |
| 178 | + def ParseMillions(x): |
| 179 | + if int(x) == 0: |
| 180 | + return "" |
| 181 | + return ParseTriple(x) + " Million" |
| 182 | + |
| 183 | + def ParseBillions(x): |
| 184 | + if int(x) == 0: |
| 185 | + return "" |
| 186 | + return ParseTriple(x) + " Billion" |
| 187 | + |
| 188 | + x = str(num) |
| 189 | + n = len(x) |
| 190 | + |
| 191 | + # Breaking into groups of 3 digits |
| 192 | + billion = x[-12:-9] if n > 9 else "" |
| 193 | + million = x[-9:-6] if n > 6 else "" |
| 194 | + thousand = x[-6:-3] if n > 3 else "" |
| 195 | + hundred = x[-3:] |
| 196 | + |
| 197 | + result = [] |
| 198 | + if billion: |
| 199 | + result.append(ParseBillions(billion)) |
| 200 | + if million: |
| 201 | + result.append(ParseMillions(million)) |
| 202 | + if thousand: |
| 203 | + result.append(ParseThousands(thousand)) |
| 204 | + if hundred: |
| 205 | + result.append(ParseTriple(hundred)) |
| 206 | + |
| 207 | + result = ' '.join([x for x in result if len(x) > 0]) |
| 208 | + return ' '.join(result.split()) # clean up white spaces |
11 | 209 | ```
|
| 210 | +- Submitting without checking anything!! This is life in the fast lane... ⚠️ 🚗 ⚠️ |
| 211 | + |
| 212 | + |
| 213 | + |
| 214 | +Uh oh. What have I done here... "One Thousand Zero" definitely isn't a thing...🤦 Gah!! |
| 215 | + |
| 216 | +Ok, let's try to be careful here... |
| 217 | + |
| 218 | +## More notes... |
| 219 | +- I think the problem is because, in `ParseSingle`, "0" should actually *not* map to "Zero" if that function is called from the `ParseDouble` function. I think I can just add a flag to handle this. |
| 220 | + |
| 221 | +```python |
| 222 | +class Solution: |
| 223 | + def numberToWords(self, num: int) -> str: |
| 224 | + def ParseSingle(x, ignore_zero=False): |
| 225 | + map = {'0': 'Zero', '1': 'One', '2': 'Two', '3': 'Three', '4': 'Four', '5': 'Five', |
| 226 | + '6': 'Six', '7': 'Seven', '8': 'Eight', '9': 'Nine'} |
| 227 | + if x == "0" and ignore_zero: |
| 228 | + return "" |
| 229 | + return map[x] |
| 230 | + |
| 231 | + def ParseDouble(x): |
| 232 | + map = {'10': 'Ten', '11': 'Eleven', '12': 'Twelve', '13': 'Thirteen', '14': 'Fourteen', '15': 'Fifteen', |
| 233 | + '16': 'Sixteen', '17': 'Seventeen', '18': 'Eighteen', '19': 'Nineteen', |
| 234 | + '20': 'Twenty', '30': 'Thirty', '40': 'Forty', '50': 'Fifty', |
| 235 | + '60': 'Sixty', '70': 'Seventy', '80': 'Eighty', '90': 'Ninety', "0": ""} |
| 236 | + if x in map: |
| 237 | + return map[x] |
| 238 | + else: |
| 239 | + return map[str(10 * (int(x) // 10))] + " " + ParseSingle(x[1], ignore_zero=True) |
| 240 | + |
| 241 | + def ParseTriple(x): |
| 242 | + if len(x) == 1: |
| 243 | + return ParseSingle(x) |
| 244 | + elif len(x) == 2: |
| 245 | + return ParseDouble(x) |
| 246 | + elif int(x[0]) == 0: |
| 247 | + return ParseDouble(x[1:]) |
| 248 | + elif int(x[1:]) == 0: |
| 249 | + return ParseSingle(x[0]) + " Hundred" |
| 250 | + else: |
| 251 | + return ParseSingle(x[0]) + " Hundred " + ParseDouble(x[1:]) |
| 252 | + |
| 253 | + def ParseThousands(x): |
| 254 | + if int(x) == 0: |
| 255 | + return "" |
| 256 | + return ParseTriple(x) + " Thousand" |
| 257 | + |
| 258 | + def ParseMillions(x): |
| 259 | + if int(x) == 0: |
| 260 | + return "" |
| 261 | + return ParseTriple(x) + " Million" |
| 262 | + |
| 263 | + def ParseBillions(x): |
| 264 | + if int(x) == 0: |
| 265 | + return "" |
| 266 | + return ParseTriple(x) + " Billion" |
| 267 | + |
| 268 | + x = str(num) |
| 269 | + n = len(x) |
| 270 | + |
| 271 | + # Breaking into groups of 3 digits |
| 272 | + billion = x[-12:-9] if n > 9 else "" |
| 273 | + million = x[-9:-6] if n > 6 else "" |
| 274 | + thousand = x[-6:-3] if n > 3 else "" |
| 275 | + hundred = x[-3:] |
| 276 | + |
| 277 | + result = [] |
| 278 | + if billion: |
| 279 | + result.append(ParseBillions(billion)) |
| 280 | + if million: |
| 281 | + result.append(ParseMillions(million)) |
| 282 | + if thousand: |
| 283 | + result.append(ParseThousands(thousand)) |
| 284 | + if hundred: |
| 285 | + result.append(ParseTriple(hundred)) |
| 286 | + |
| 287 | + result = ' '.join([x for x in result if len(x) > 0]) |
| 288 | + return ' '.join(result.split()) # clean up white spaces |
| 289 | +``` |
| 290 | +- Ok...submitting again 🤞... |
| 291 | + |
| 292 | + |
| 293 | + |
| 294 | +Finally solved 🥳! |
0 commit comments