CodeIQ:英語でプリーズ!

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「英語でプリーズ!」(CodeIQ)を参照してください。

問題の概要

問題を引用します。
与えられた整数値を、英語に変換するプログラムを作成してください。
たとえば"123"なら"One Hundred Twenty Three"、"-50000"のような負の数は、"Negative Fifty Thousand"のように出力してください。
必要な英単語は以下になります。

Zero
One
Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten
Eleven
Twelve
Thirteen
Fourteen
Fifteen
Sixteen
Seventeen
Eighteen
Nineteen
Twenty
Thirty
Forty
Fifty
Sixty
Seventy
Eighty
Ninety
Hundred
Thousand
Million
Billion
Negative

【入力】
標準入力から1行目には入力データ数N(1≦N≦100)が、2行目以降には整数値が与えられます。
2行目以降のN行分の整数値を英語に変換してください。
ただし、入力データは符号付き32bit整数の範囲で収まるものに限ります。

【出力】
標準出力に、変換後の英語を出力してください(入力データ毎に改行してください)。
アルファベットの大文字・小文字は問いません。

【入出力サンプル】
Input
7
123
4567
89012
0
-34
-5678901
1111111111

Output
One Hundred Twenty Three
Four Thousand Five Hundred Sixty Seven
Eighty Nine Thousand Twelve
Zero
Negative Thirty Four
Negative Five Million Six Hundred Seventy Eight Thousand Nine Hundred One
One Billion One Hundred Eleven Million One Hundred Eleven Thousand One Hundred Eleven

私のプログラム

Rubyで解答しています。

#!/usr/bin/ruby

Strs = {
	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",
}

Figs = {
	1 => "Thousand",
	2 => "Million",
	3 => "Billion",
}

def parse3(n)
	ret = []

	h = n / 100
	if h > 0 then
		ret << Strs[h]
		ret << "Hundred"
	end

	m = n % 100
	if (0 < m) && (m <= 19) then
		ret << Strs[m]
	else
		l = m % 10
		k = m - l
		ret << Strs[k]

		ret << Strs[l] if l != 0
	end

	return ret
end

def solve(n)
	return ["Zero"] if n == 0

	nxt = n.abs

	ret = []
	c = 0
	while nxt > 0
		lst3 = nxt % 1000
		if lst3 != 0 then
			arr = parse3(lst3)
			arr << Figs[c] if c > 0
			ret = arr + ret
		end

		nxt /= 1000
		c += 1
	end

	ret.unshift("Negative") if n < 0

	return ret.join(" ")
end

# main
ln = 0
while line = gets
	line.strip!
	next if line.empty?

	puts solve(line.to_i) if ln > 0
	ln += 1
end

解説

3桁ごとに処理することに気づけば簡単です。

考え方

最初に書いた通り、3桁ずつ処理すれば良いことに気づけば簡単です。

つまり、入力値を1000で割った商と余りにします。
余りを英語にします(後述)。
また商を1000で割って同じようにし、これを商が0になるまで繰り返します。そして、割った回数が1回ならThousand、2ならMillion、3ならBillionをつけるという具合です。

余り部分の処理は100で割った商と余りで処理します。
商の部分が0でなければ商の英語(One〜Nine)とHundredにします。
余りは20以下ならOne〜Nineteenにし、それ以上なら更に10で割った商と余りにして商によってTwenty〜Ninetyと余りOne〜Nineにするという具合です。

main

入力値を数値にしてsolve()に渡し、結果を印字します。
1行目は入力数なので跳ばします。

solve(n)

入力値が0の時は"Zero"で終わりなのでそれでリターンします。
nxtは1000で割った商を保持する変数で、初期値はnの絶対値にします。
retは結果の英単語の配列です。
cは何回1000で割ったかをカウントします。

下から3桁ずつ処理します。
1000で割った余りをlst3に保持し、parse3()に渡して英単語にします。
結果はarrに入ってきますので、後から処理した分が前に来るようにretに連結します。73行目の処理は割った回数が1回ならThousand、2ならMillion、3ならBillionをつけるという処理です。

入力値がマイナスならretの先頭にNegativeをつけます。
最後にこれらをスペースで連結した文字列にして返します。

parse3(n)

3桁ぶんを英単語にします。
まず、100で割ってその商を〜 Hundredにします。0の時は無視します。
次に100で割った余りが1〜19ならOne〜Nineteenにします。
そうでなければ更に10で割って商と余りにし、商をTwenty〜Ninetyに、余りをOne〜Nineにします。

このようにして作った単語の配列を返します。

雑感

前に同じような問題をやった気がする上に、番号が1658と若かったので2回目かと思いましたが初めてだったみたいです。
前にやったのは逆パターン(英単語を数字に)だったかなぁ?