Project Eueler: problem17

問題

If the numbers 1 to 5 are written out in words: one, two, three, four, five, then there are 3 + 3 + 5 + 4 + 4 = 19 letters used in total.

If all the numbers from 1 to 1000 (one thousand) inclusive were written out in words, how many letters would be used?


NOTE: Do not count spaces or hyphens. For example, 342 (three hundred and forty-two) contains 23 letters and 115 (one hundred and fifteen) contains 20 letters. The use of "and" when writing out numbers is in compliance with British usage.

回答

ここを参考にまずRSpecでテストを作ってみた
http://d.hatena.ne.jp/myokoym/20100919/1284887970

require "p17.rb"

describe "#toEng" do
  it "1 to one" do
    toEng(1).should == "one"
  end
  
  it "14 to fourteen" do
    toEng(14).should == "fourteen"
  end
  
#略

  
  it "999" do
    toEng(999).should == "nine hundred and ninety-nine"
  end
  
  it "1000" do
    toEng(1000).should == "one thousand"
  end
end

describe "#sumLetters" do
  it "342" do
    sumLetters(toEng(342)) == 23
  end
  
  it "115" do
    sumLetters(toEng(115)) == 20
  end
  
  it "999" do
    sumLetters(toEng(999)) == 25
  end
end

describe "#sumTotal" do
  it "5" do
    sumTotal(5).should == 19
  end
  
  it "1000" do
    sumTotal(1000).should == 21124
  end
end

回答はこうなった。

$data = <<"NUMDATA"
0,zero
1,one
2,two
3,three
4,four
5,five
6,six
7,seven
8,eight
9,nine
10,ten
11,eleven
12,twelve
13,thirteen
14,fourteen
15,fifteen
16,sixteen
17,seventeen
18,eighteen
19,nineteen
20,twenty
30,thirty
40,forty
50,fifty
60,sixty
70,seventy
80,eighty
90,ninety
NUMDATA

$dic = Hash.new

def makedic()
  $data.each do |line|
    ar = line.split(/,/)
    $dic[ar[0].to_i] = ar[1].chomp!
  end
end

def toEng(num)  
  tar = num.to_s.split(//)
  str = ""
  
  if tar[-4] != nil
    return "one thousand"
  end
  
  if tar[-3] != nil
    str += $dic[tar[-3].to_i]
    str += " hundred"
    str += " and " unless num % 100 == 0
  end
    
  if tar[-2] == "0" || tar[-2] == nil
    unless tar[-1] == "0"
      str += $dic[tar[-1].to_i]
    end
  elsif tar[-2] == "1"
    str += $dic[(tar[-2]+tar[-1]).to_i]
  else
    str += $dic[(tar[-2]+"0").to_i]
    unless tar[-1] == "0"
      str += "-" + $dic[tar[-1].to_i]
    end
  end
  
  return str
end

def sumLetters(str)
  str.gsub("-"," ").split(/ /).inject(0){|sum,s| sum + s.size}
end

def sumTotal(upper)
  makedic()
  1.upto(upper).inject(0){|sum,x| sum + sumLetters(toEng(x))}
end

p sumTotal(1000)

備考

リファクタリングにはテスト有効。
テスト駆動で書く習慣をつけよう。