From: Krishna Dole Date: 2007-09-10T21:21:55+09:00 Subject: Re: Count and Say (#138) # 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 = <