# Krishna Dole's solution to Ruby Quiz 138.
# I followed Glenn Parker's reasoning on naming numbers, but didn't
look at his code.
class CountSay
def initialize(str)
@str = str
end
def succ
letters = @str.scan(/\w/)
@str = letters.uniq.sort.map do |l|
[letters.select{ |e| e == l }.size.to_words, l]
end.join(" ").upcase
end
def each(limit = nil)
if limit.nil?
while true
yield succ
end
else
limit.times { yield succ }
end
end
end
class Integer
NUMS_0_19 = %w(zero one two three four five six seven eight nine ten
eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen
nineteen)
NUMS_TENS = [nil, nil] + %w(twenty thirty forty fifty sixty seventy
eighty ninety)
NUMS_TRIPLETS = [nil] + %w(thousand million billion trillion)
# Return the english words representing the number,
# so long as it is smaller than 10**15.
# The specifications for this method follow the reasoning in:
# http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/135449
# though I wrote the code without seeing Glenn's.
def to_words
case self
when 0..19
NUMS_0_19[self]
when 20..99
a, b = self.divmod(10)
[NUMS_TENS[a], NUMS_0_19[b]].reject { |w| w == "zero" }.join("-")
when 100..999
a, b = self.divmod(100)
"#{NUMS_0_19[a]} hundred#{b == 0 ? '' : ' ' + b.to_words}"
else
raise "Too lazy to write numbers >= 10**15" if self >= 10**15
triplet = (self.to_s.size - 1) / 3 # find out if we are in
thousands, millions, etc
a, b = self.divmod(10**(3 * triplet))
"#{a.to_words} #{NUMS_TRIPLETS[triplet]}#{b == 0 ? '' : ' ' +
b.to_words}"
end
end
end
class SimpleCycleDetector
# Expects an object that responds to #each.
# Returns an array containing the number of
# iterations before the cycle started, the length
# of the cycle, and the repeated element.
def self.detect(obj)
results = []
obj.each do |e|
if i = results.index(e)
return [i, results.size - i, e]
else
results << e
end
end
end
end
unless ARGV[0] == "-test"
seed = if ARGV.empty?
"LOOK AND SAY"
else
ARGV.join(' ').upcase
end
puts "Seeding CountSay with '#{seed}'"
cs = CountSay.new(seed)
initial, period, phrase = SimpleCycleDetector.detect(cs)
puts "Repeated phrase '#{phrase}.' Started cycle of length #{period}
after #{initial} iterations."
else
require "test/unit"
class TestToWords < Test::Unit::TestCase
def test_to_words_0_19
assert_equal("zero", 0.to_words)
assert_equal("nine", 9.to_words)
assert_equal("eleven", 11.to_words)
assert_equal("nineteen", 19.to_words)
end
def test_to_words_20_99
assert_equal("twenty", 20.to_words)
assert_equal("twenty-one", 21.to_words)
assert_equal("forty-two", 42.to_words)
assert_equal("seventy-seven", 77.to_words)
assert_equal("ninety-nine", 99.to_words)
end
def test_to_words_100_999
assert_equal("one hundred", 100.to_words)
assert_equal("nine hundred three", 903.to_words)
assert_equal("two hundred fifty-six", 256.to_words)
end
def test_to_words_999_and_up
assert_equal("one thousand", 1000.to_words)
assert_equal("one thousand one hundred one", 1101.to_words)
assert_equal("twenty-two thousand", 22_000.to_words)
assert_equal("one million", (10**6).to_words)
assert_equal("twenty-two million nine hundred thousand four
hundred fifty-six", 22_900_456.to_words)
assert_equal("nine hundred ninety-nine trillion twenty-two
million four thousand one", 999_000_022_004_001.to_words)
assert_equal("nine hundred ninety-nine trillion nine hundred
ninety-nine billion nine hundred ninety-nine million nine hundred
ninety-nine thousand nine hundred ninety-nine",
999_999_999_999_999.to_words)
end
def test_error_on_big_number
assert_raise(RuntimeError) { (10**15).to_words }
assert_nothing_raised(RuntimeError) { ((10**15) - 1).to_words }
end
end
class TestSimpleCyleDetector < Test::Unit::TestCase
def setup
@nums = [1,2,3,1,2,3]
@letters = %w( x y a b c d a e f )
end
def test_detect
assert_equal([0, 3, 1], SimpleCycleDetector.detect(@nums))
assert_equal([2, 4, 'a'], SimpleCycleDetector.detect(@letters))
end
end
class TestCountSay < Test::Unit::TestCase
def setup
@output = <<END_OUTPUT
0. LOOK AND SAY
1. TWO A ONE D ONE K ONE L ONE N TWO O ONE S ONE Y
2. ONE A ONE D SIX E ONE K ONE L SEVEN N NINE O ONE S TWO T TWO W ONE Y
3. ONE A ONE D TEN E TWO I ONE K ONE L TEN N NINE O THREE S THREE T
ONE V THREE W ONE X ONE Y
END_OUTPUT
@lines = @output.to_a.map { |l| l[3..-2] } # without leading
number or newline
@cs = CountSay.new(@lines.first)
end
def test_succ
@lines[1..-1].each do |line|
assert_equal(line, @cs.succ)
end
end
def test_each_with_limit
@lines.shift
@cs.each(3) do |str|
assert_equal(@lines.shift, str)
end
end
end
end