2012年5月29日火曜日

プロジェクトオイラー Problem17

「プロジェクト オイラー」の問題をpythonでやってみます。

出典:
Project Euler (日本語翻訳サイト)
参考:
サイエンスもの置き場 プロジェクトオイラーを遊び倒すガイド(初級編)

Problem 17
1 から 5 までの数字を英単語で書けば one, two, three, four, five であり、全部で 3 + 3 + 5 + 4 + 4 = 19 の文字が使われている。
では 1 から 1000 (one thousand) までの数字をすべて英単語で書けば、全部で何文字になるか。

: 空白文字やハイフンを数えないこと。
例えば、342 (three hundred and forty-two) は 23 文字、
115 (one hundred and fifteen) は20文字と数える。
なお、"and" を使用するのは英国の慣習。

私の解答例は以下です。
-----

D = {0:"", 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"}

def f2(n): 
	sho, amari = divmod(n, 10)
	return D.get(n, " ".join([D[sho*10], D[amari]]))

def f3(n):
	sho, amari = divmod(n, 100)
	L  = [D[sho], "hundred"]+{0:[]}.get(amari, ["and", f2(amari)]) 
	return " ".join(L)

def f4(n):
	sho, amari = divmod(n, 1000)
	L = [D[sho], "thousand", {True:f3(amari)}.get(amari, "")]
	return " ".join(L)

def f(n):
	L = [f2(i) for i in xrange(1, n+1) if i<100] \
	  + [f3(i) for i in xrange(1, n+1) if 100<=i<1000] \
	  + [f4(i) for i in xrange(1, n+1) if 1000<=i]
	M = [len(s.replace(" ","")) for s in L]
	return sum(M)
 
print f(1000)
-----
・pythonでは連想配列のことを辞書dictionaryと呼びます。
・辞書Dに100未満の数字の部品の英単語を設定しました。
 100と1000は処理中に直書きしています。


1.f2(n)
・2桁以下の数字の英単語を返します。


・sho, amari = divmod(n, 10)
 divmod関数は2つの引数を除算したときの商と余りの組を返します。
 ここでは、10で割った商をsho、余りをamariに設定します。


・" ".join([D[sho*10], D[amari]])
 区切り文字.join(要素)で、要素を区切り文字で連結した文字列を作成します。
 ここではD[sho*10]とD[amari]を半角空白区切りで連結した文字列を作成します。
 例)n=23の場合、D[20]とD[3]の空白区切りで「twenty three」。


・return D.get(n, " ".join([D[sho*10], D[amari]]))
 「D.get(n, 式)」は、辞書Dのキーに値aがあればD[n]を返し、無ければ式の値を返す関数です。
 辞書Dにnの英単語があればそれを返し、無ければ10の位と1の位から英単語を作成して返します。
 

2.f3(n)
・3桁の数字の英単語を返します。


・sho, amari = divmod(n, 100)
 divmod関数は2つの引数を除算したときの商と余りの組を返します。
 ここでは、100で割った商をsho、余りをamariに設定します。


・L  = [D[sho], "hundred"]+{0:[]}.get(amari, ["and", f2(amari)])
 [D[sho], "hundred"]は100の位で、["one",  "hundred"]などとなります。
 {0:[]}.get(amari, ["and", f2(amari)])は、3桁の数字のうち下2桁の処理です。
 amariが0の場合、1,2桁目が0ということで、空リスト[]です。
 amariがある場合、["and", amariの2桁の英単語]です。
 

・return " ".join(L)
 ここまで設定してきた英単語の部品を半角空白区切りで連結して返します。


3.f4(n)
・4桁の数字の英単語を返します。

・上記のf3(n)関数とほとんど同じなので、違う部分のみ書きます。
・L = [D[sho], "thousand", {True:f3(amari)}.get(amari, "")]
 1000の位の数字、"thousand"、amariが論理的にTrue(0以外)ならば3桁数字の英単語、これらをリストに設定し、次の行で半角空白区切りで連結して返します。


4.f(n)
・L = [f2(i) for i in xrange(1, n+1) if i<100] \
   + [f3(i) for i in xrange(1, n+1) if 100<=i<1000] \
   + [f4(i) for i in xrange(1, n+1) if 1000<=i]


 「\」は継続行を示しています。
3つのリストを連結します。それぞれのリストは同じ構造をしています。
for文で1からnまでの値を取り出し、後ろのif文に送り、Trueの場合のみfor文の前の処理をします。
最初のリストは、2桁以下の数字をf2関数で処理した1つひとつの英単語リスト
次のリストは、3桁の数字をf3関数で処理した1つひとつの英単語リスト
最後のリストは、4桁の数字をf4関数で処理した1つひとつの英単語リスト

・M = [len(s.replace(" ","")) for s in L]
 上記で作成した1からnまでの英単語の1つひとつについて、半角空白を削除した文字列長をリストMに設定します。

・return sum(M)
 上記文字列長の合計値を返します。


解答はこのすぐ下の行です。文字の色を白にしてます。選択状態にすると見えます。
21124

(追記)
・20120715
 ソースコード部分にSyntaxHighlighterを導入。

0 件のコメント:

コメントを投稿