Skip to content

Commit e6a900f

Browse files
committed
more writing and markdown preview
1 parent a2d01a7 commit e6a900f

File tree

2 files changed

+368
-6
lines changed

2 files changed

+368
-6
lines changed

integer-to-english/README.md

+353
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,353 @@
1+
[This is a leetcode problem](https://leetcode.com/problems/integer-to-english-words/description/) posted to the underdog devs slack. I recently got laid off and I should practice
2+
3+
This is the actual process of me solving the problem. The final ruby-file can be viewed [here](./number_to_english.rb).
4+
5+
6+
# Problem Statement
7+
8+
> Convert a non-negative integer num to its English words representation.
9+
10+
0 <= num <= 2**31 - 1
11+
12+
13+
## Example 1
14+
15+
Input: num = 123
16+
Output: "One Hundred Twenty Three"
17+
18+
19+
## Example 2
20+
21+
Input: num = 12345
22+
Output: "Twelve Thousand Three Hundred Forty Five"
23+
24+
25+
## Example 3
26+
27+
Input: num = 1234567
28+
Output: "One Million Two Hundred Thirty Four Thousand Five Hundred Sixty Seven"
29+
30+
31+
# Brainstorming
32+
33+
What's the max here? That detemrmines how many decimal place value names I have to hard code `2147483648`. Ok, so that's just "billions", easy.
34+
35+
Let's see if we can just say the rules in english and I'll try to avoid saying that the right answer is just to throw it at ChatGPT.
36+
37+
I'm thinking about examining the number form the back forward
38+
39+
- If literally 0 then say zero
40+
- For the ones
41+
- if 1-9 then say the digit
42+
- if 0 then say nothing
43+
- For the tens
44+
- if 4, 6-9 then its the digit+ty
45+
- if 5, then fifty
46+
- if 3, then thirty
47+
- if 2, then twenty
48+
- if 0, then nothing
49+
- if 1, then
50+
- do not run the ones
51+
- if ones place is
52+
- 0 then ten
53+
- 1 then eleven
54+
- 2 then twelve
55+
- 3 then thirteen
56+
- 5 then fifteen
57+
- 4, 6-9 then digit+teen
58+
- For hundres
59+
- digit+hundred
60+
- For thousands
61+
- digit+thousand
62+
- Tens thousands
63+
- same rules as tens+thousand
64+
- Hundred thousands
65+
- same rules as hundreds+thousand
66+
- Millions
67+
- same as ones+million
68+
69+
I'm not the most clear on the more advanced numbers but I feel like if I get 0-9999 right then the rest will fall into place
70+
71+
I do think that maybe we need to handle the case of a two digit number together, all the exceptions are in the interactions there
72+
73+
74+
# Playground
75+
76+
You know, I've seen ruby pop up several times in JDs I've looked at and&#x2026;I'm not a ruby guy, so like, lets do it in ruby to demenstrate that I can still sling it
77+
78+
Clearly I'm going to need to do pattern matching, lets see how it looks in ruby
79+
80+
I'm on a mac which already has ruby installed, but pattern matching in ruby got more powerful in 2.7 and the installed version is `ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin23]`. I can `brew install ruby` to get latest but it doesn't symlink automatically. I'll have to explicitly call `/opt/homebrew/opt/ruby/bin/ruby` (or `irb`)
81+
82+
case num
83+
in 0
84+
"zero"
85+
in x if 1 <= x && x <= 5
86+
"low"
87+
in x if 5 < x && x < 10
88+
"high"
89+
end
90+
91+
zero
92+
93+
zero
94+
95+
low
96+
97+
high
98+
99+
low
100+
101+
102+
# Implementation
103+
104+
Well, we'll need a way to convert just flat out digits to english, right? But for our needs we never say "zero" (except literally for 0), you just ignore it
105+
106+
def digit_to_english(digit)
107+
case digit
108+
in "0"
109+
""
110+
in "1"
111+
"one"
112+
in "2"
113+
"two"
114+
in "3"
115+
"three"
116+
in "4"
117+
"four"
118+
in "5"
119+
"five"
120+
in "6"
121+
"six"
122+
in "7"
123+
"seven"
124+
in "8"
125+
"eight"
126+
in "9"
127+
"nine"
128+
end
129+
end
130+
131+
132+
133+
digit_to_english "4"
134+
135+
four
136+
137+
Now lets try to do two digits
138+
139+
def two_digits_to_english(digits)
140+
case digits
141+
in [d]
142+
digit_to_english d
143+
in ["0", "0"]
144+
""
145+
in ["0", d]
146+
digit_to_english d
147+
in ["1", "0"]
148+
"ten"
149+
in ["1", "1"]
150+
"eleven"
151+
in ["1", "2"]
152+
"twelve"
153+
in ["1", "3"]
154+
"thirteen"
155+
in ["1", "5"]
156+
"fifteen"
157+
in ["1", d]
158+
"#{digit_to_english d}teen"
159+
in ["2", d]
160+
"twenty #{digit_to_english d}"
161+
in ["3", d]
162+
"thirty #{digit_to_english d}"
163+
in ["5", d]
164+
"fifty #{digit_to_english d}"
165+
in ["8", d]
166+
"eighty #{digit_to_english d}"
167+
in [d1, d2]
168+
"#{digit_to_english d1}ty #{digit_to_english d2}"
169+
end
170+
end
171+
172+
173+
174+
175+
[0, 4, 12, 16, 25, 36, 50, 99].map { |n| (two_digits_to_english (n.to_s.split "")) }
176+
177+
<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
178+
179+
180+
<colgroup>
181+
<col class="org-left" />
182+
183+
<col class="org-left" />
184+
185+
<col class="org-left" />
186+
187+
<col class="org-left" />
188+
189+
<col class="org-left" />
190+
191+
<col class="org-left" />
192+
193+
<col class="org-left" />
194+
195+
<col class="org-left" />
196+
</colgroup>
197+
<tbody>
198+
<tr>
199+
<td class="org-left">&#xa0;</td>
200+
<td class="org-left">four</td>
201+
<td class="org-left">twelve</td>
202+
<td class="org-left">sixteen</td>
203+
<td class="org-left">twenty five</td>
204+
<td class="org-left">thirty six</td>
205+
<td class="org-left">fifty</td>
206+
<td class="org-left">ninety nine</td>
207+
</tr>
208+
</tbody>
209+
</table>
210+
211+
woah look at that, it worked!
212+
213+
Ok, so now we're getting to understand the rest of the pattern. First of all, I'll observe that we can use `two_digits_to_english` with single digit numbers too, so lets alias it to `dte` and use that as much as possible
214+
215+
- for a 3 digit number its `(dte d1) hundred (dte d23)` we'll alias this `3dte`
216+
- for a 4 digit number its `(dte d1) thousand (3dte d234)`
217+
- for a 5 digit number its `(dte d12) thousand (3dte d345)`
218+
- for a 6 digit number its `(3dte d123) thousand (3dte d456)` we'll alias this to 6dte
219+
- for a 7 digit number its `(dte d1) million (6dte d234567)`
220+
- for a 8 digit number its `(dte d12) million (6dte d345678)`
221+
- for a 9 digit number its `(3dte d123) million (6dte d456789)` - we'll alias this to 9dte
222+
- for a 10 digit number its `(dte d1) billion (9dte d234567890)`
223+
224+
Ok, so its becoming clear that it might be useful for `dte` to be able to handle 3 digits, that would simplify things
225+
226+
def three_digits_to_english(digits)
227+
case digits
228+
in x if x.length <= 2
229+
two_digits_to_english x
230+
in ["0", *d23]
231+
two_digits_to_english d23
232+
in [d1, *d23]
233+
"#{two_digits_to_english [d1]} hundred #{two_digits_to_english d23}".strip
234+
end
235+
end
236+
237+
238+
239+
240+
241+
[0, 4, 12, 99, 100, 145, 232, 911].map { |n| (three_digits_to_english (n.to_s.split "")) }
242+
243+
<table border="2" cellspacing="0" cellpadding="6" rules="groups" frame="hsides">
244+
245+
246+
<colgroup>
247+
<col class="org-left" />
248+
249+
<col class="org-left" />
250+
251+
<col class="org-left" />
252+
253+
<col class="org-left" />
254+
255+
<col class="org-left" />
256+
257+
<col class="org-left" />
258+
259+
<col class="org-left" />
260+
261+
<col class="org-left" />
262+
</colgroup>
263+
<tbody>
264+
<tr>
265+
<td class="org-left">&#xa0;</td>
266+
<td class="org-left">four</td>
267+
<td class="org-left">twelve</td>
268+
<td class="org-left">ninety nine</td>
269+
<td class="org-left">one hundred</td>
270+
<td class="org-left">one hundred fourty five</td>
271+
<td class="org-left">two hundred thirty two</td>
272+
<td class="org-left">nine hundred eleven</td>
273+
</tr>
274+
</tbody>
275+
</table>
276+
277+
now this can be simplified to the following. Here we alias our new `three_digits_to_english` as `dte`
278+
279+
- for a 4 digit number its `(dte d1) thousand (dte d234)`
280+
- for a 5 digit number its `(dte d12) thousand (dte d345)`
281+
- for a 6 digit number its `(dte d123) thousand (dte d456)` we'll alias this to 6dte
282+
- for a 7 digit number its `(dte d1) million (6dte d234567)`
283+
- for a 8 digit number its `(dte d12) million (6dte d345678)`
284+
- for a 9 digit number its `(dte d123) million (6dte d456789)` - we'll alias this to 9dte
285+
- for a 10 digit number its `(dte d1) billion (9dte d234567890)`
286+
287+
So now, we just know the breaks and the word associated to each of the breaks and then we do something like `(dte head..break) word rest`
288+
289+
Ok so lets do that. There's the question of what the structure for those breaks/word associations should look like. While we could do an array or a hash, because we're always processing it from highest break to lowest I think the best approach is more like a linked list as it can be unrolled more easily. Quick google tells me Ruby has one-line structs that can be used for this. Note that we have special handling for "hundreds and below" already so no need to go lower
290+
291+
PlaceName = Struct.new(:place, :name, :next)
292+
ALL_PLACE_NAMES = PlaceName.new(10, "billion",
293+
PlaceName.new(7, "million",
294+
PlaceName.new(4, "thousand")))
295+
296+
Note the capitalization here is interesting. I got stuck on it for a bit. In ruby - unlike other languages - all caps matters for making your variable visible down the scope chain
297+
298+
We're almost there, we can now unroll this across all our digits
299+
300+
There's one gocha here, in that if the next set of digits are all 0, then we don't want to say anything. This will allow us to handle situations like `10000` recursively without saying the "hundred" that you **would** say if you've got a number like `100` or `10100`
301+
302+
def many_digits_to_english(digits, place_name)
303+
if digits.all? { |d| d == "0" } # the hundreds in 1000
304+
""
305+
elsif not place_name # terminal condition and when 3 digit or lower
306+
three_digits_to_english digits
307+
elsif digits.length < place_name.place # when not in the billions and need to get down to the place name that matters
308+
many_digits_to_english(digits, place_name.next)
309+
else
310+
split_at = digits.length - place_name.place
311+
place_digits = digits[0..split_at]
312+
rest_digits = digits[(split_at + 1)..-1]
313+
if place_digits.all? { |d| d == "0" } # situations like 1000001
314+
many_digits_to_english(rest_digits, place_name.next)
315+
else # normal case
316+
"#{three_digits_to_english place_digits} #{place_name.name} #{many_digits_to_english(rest_digits, place_name.next)}"
317+
end
318+
end
319+
end
320+
321+
Now we just have to do the splitting of digits. Oh, and handle zero
322+
323+
def number_to_english(num)
324+
if num == 0
325+
"zero"
326+
else
327+
many_digits_to_english(num.to_s.split(""), ALL_PLACE_NAMES).strip
328+
end
329+
end
330+
331+
And put it all together
332+
333+
lets test it out.
334+
335+
336+
337+
[0, 4, 12, 99, 100, 911, 1000, 10001, 100000, 1000001, 1000000000, 2000000011].each { |n| puts "#{n}, #{(number_to_english n)}" }
338+
339+
0, zero
340+
4, four
341+
12, twelve
342+
99, ninety nine
343+
100, one hundred
344+
911, nine hundred eleven
345+
1000, one thousand
346+
10001, ten thousand one
347+
100000, one hundred thousand
348+
1000001, one million one
349+
1000000000, one billion
350+
2000000011, two billion eleven
351+
352+
I've also tangled the file so the full ruby-only can be viewed [here](./number_to_english.rb).
353+

0 commit comments

Comments
 (0)