Last active
December 29, 2015 20:09
-
-
Save timblair/7722251 to your computer and use it in GitHub Desktop.
Counting from 1 to 1,000,000 in words. Actually caters for numbers up to 10^36-1 (using the short scale) if you really need to...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class NumberConverter | |
UNITS = [nil] + %w{ one two three four five six seven eight nine ten eleven | |
twelve thirteen fourteen fifteen sixteen seventeen eightteen nineteen } | |
TENS = %w{ twenty thirty forty fifty sixty seventy eighty ninety } | |
SCALE = %w{ hundred thousand } + | |
%w{ m b tr quadr quint sext sept oct non dec }.map { |i| i + "illion" } | |
# cache some lookups for a ~25% speed increase | |
@@cache ||= {} | |
def initialize(num) | |
@num = num.to_i | |
end | |
def to_words | |
words = case @num | |
when -Float::INFINITY..0; nil # anything less than 1 | |
when 1..19; UNITS[@num] # just pick the correct unit | |
when 20..99; tens | |
when 100..999; hundreds | |
else thousands_or_more | |
end | |
# squash the array and join the words (using implicit string conversion) | |
[words].flatten.compact.join(" ").strip | |
end | |
# implicit string conversion instead of using .to_words everywhere | |
alias :to_str :to_words | |
private | |
def tens | |
# grab the correct "ten", plus the unit | |
@@cache[@num] ||= [TENS[(@num / 10) - 2], self.class.new(@num % 10)] | |
end | |
def hundreds | |
@@cache[@num] ||= [ | |
UNITS[@num / 100], # how many hundreds? | |
SCALE.first, # the actual "hundred" | |
@num % 100 > 0 ? "and" : "", # an "and" if necessary | |
self.class.new(@num % 100) # the tens, teens and units | |
] | |
end | |
def thousands_or_more | |
# split the number in to chunks of 3, e.g. 1234567 => [1, 234, 567] | |
seg = @num.to_s.reverse.scan(/(.{1,3})/).flatten.map(&:reverse).reverse | |
# we'll deal with all but the first segment later | |
first = seg.first | |
rest = seg.drop(1).join.to_i | |
# we might need an "and" and a comma for readability | |
sep = (1..99).include?(rest) ? " and" : (rest > 0 ? "," : "") | |
[ | |
@@cache[first] || self.class.new(first), # the first segment | |
SCALE[seg.length-1] + sep, # the scale and separator | |
self.class.new(rest) # the other segments | |
] | |
end | |
end | |
if $0 == __FILE__ | |
if ARGV.first.to_i > 0 | |
puts NumberConverter.new(ARGV.first).to_words | |
else | |
(1..1_000_000).each { |i| puts "#{i}: " + NumberConverter.new(i).to_words } | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "minitest/autorun" | |
require_relative "number_converter" | |
class TestNumberConverter < MiniTest::Unit::TestCase | |
NUMBERS_TO_WORDS = { | |
1 => "one", | |
2 => "two", | |
9 => "nine", | |
10 => "ten", | |
11 => "eleven", | |
19 => "nineteen", | |
20 => "twenty", | |
21 => "twenty one", | |
99 => "ninety nine", | |
100 => "one hundred", | |
101 => "one hundred and one", | |
110 => "one hundred and ten", | |
199 => "one hundred and ninety nine", | |
200 => "two hundred", | |
999 => "nine hundred and ninety nine", | |
1_000 => "one thousand", | |
1_001 => "one thousand and one", | |
1_100 => "one thousand, one hundred", | |
1_101 => "one thousand, one hundred and one", | |
1_999 => "one thousand, nine hundred and ninety nine", | |
2_000 => "two thousand", | |
5_678 => "five thousand, six hundred and seventy eight", | |
9_999 => "nine thousand, nine hundred and ninety nine", | |
10_000 => "ten thousand", | |
10_001 => "ten thousand and one", | |
19_000 => "nineteen thousand", | |
19_999 => "nineteen thousand, nine hundred and ninety nine", | |
20_000 => "twenty thousand", | |
20_001 => "twenty thousand and one", | |
56_789 => "fifty six thousand, seven hundred and eighty nine", | |
99_999 => "ninety nine thousand, nine hundred and ninety nine", | |
100_000 => "one hundred thousand", | |
100_001 => "one hundred thousand and one", | |
100_123 => "one hundred thousand, one hundred and twenty three", | |
119_123 => "one hundred and nineteen thousand, one hundred and twenty three", | |
567_890 => "five hundred and sixty seven thousand, eight hundred and ninety", | |
999_999 => "nine hundred and ninety nine thousand, nine hundred and ninety nine", | |
1_000_000 => "one million", | |
1_000_001 => "one million and one", | |
1_234_567 => "one million, two hundred and thirty four thousand, five hundred and sixty seven", | |
4_567_890 => "four million, five hundred and sixty seven thousand, eight hundred and ninety", | |
9_999_999 => "nine million, nine hundred and ninety nine thousand, nine hundred and ninety nine", | |
100_000_000 => "one hundred million", | |
1_000_000_000 => "one billion" | |
} | |
NUMBERS_TO_WORDS.each do |number, words| | |
define_method("test_#{words.gsub(/[^a-z]+/, '_')}") do | |
assert_equal words, NumberConverter.new(number).to_words | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment