#問題
こどもの頃、「車のナンバープレートにある4つの数字を組み合わせて10を作る」
という遊びをした人がいるかもしれません。
ここでは、並んでいる数字の各桁の間に四則演算の演算子を入れて計算することにします。
(演算子を入れない場所があっても構いませんが、最低でも1つは入れるものとします)。
例)
1234 → 1 + 2 * 3 - 4 = 3
9876 → 9 * 87 + 6 = 789
できあがった式を計算した結果、
「元の数の桁を逆から並べた数字」と同じになるものを考えます。
なお、式の計算は数学の順序で行います。(乗除が先、加減は後)。
100 ~ 900の場合、以下の3つがあります。
351 → 3 * 51 = 153
621 → 6 * 21 = 126
886 → 8 * 86 = 688
1000 ~ 9999のうち、上記の条件を満たす数を求めてください。
引用「プログラマ脳を鍛える数学パズル 著者 増井 敏克」
#回答
import re
op = ["*", ""]
num = 1000
while num < 10000:
c = str(num)
for j in range(len(op)):
for k in range(len(op)):
for l in range(len(op)):
val = c[3] + op[j] + c[2] + op[k] + c[1] + op[l] + c[0]
val_l = val.split("*")
val = "*".join([re.sub(r"0(.)", r"\1", s) for s in val_l])
if len(val) > 4:
if num == eval(val):
print(val + " = " + str(num))
num += 1
#つまづいたところ
基本的には本に載っているjavascriptのコードをpythonに書き直すような形になります。
しかし、pythonでは0
ではじまる数値はエラーとなるので、
それを防がなければなりませんでした。
それがこの部分になります。
val_l = val.split("*")
val = "*".join([re.sub(r"0(.)", r"\1", s) for s in val_l])
ここを加えないでプログラムを実行させるとわかるのですが、
はじめの1000
からいきなりエラーをかまします・・・
# num = 1000 かつ、op[l]が "" のとき
for l in range(len(op)):
val = c[3] + op[j] + c[2] + op[k] + c[1] + op[l] + c[0]
# (1) val = "0*0*01" という文字列型になります。
# (2) 一旦下記箇所を無しにすると
#val_l = val.split("*")
#val = "*".join([re.sub(r"0(.)", r"\1", s) for s in val_l])
if len(val) > 4:
if num == eval(val): # (3) ここでエラーが発生します。
print(val + " = " + str(num))
(3)のエラーの原因は、eval関数の引数valが文字列としては問題ないけど、
実行する(評価する)には問題あるよねってところです。
なので(1)の時点ではエラーになりません。
解決策としては、文字列になったものを配列にし、
さらに配列化を行い変数valに再代入します。
再代入直前の配列化のときに、
特定の条件を満たすものだけ置換処理を行います。
この置換処理で01
のような0
で始まる数値を1
にします。
# num = 1000 かつ、op[l]が "" のとき
for l in range(len(op)):
val = c[3] + op[j] + c[2] + op[k] + c[1] + op[l] + c[0]
# (1) val = "0*0*01" という文字列型になります。
val_l = val.split("*")
# (2) val_l = ["0", "0", "01"] という配列にします。
val = "*".join([re.sub(r"0(.)", r"\1", s) for s in val_l])
# (3) val = "0*0*1" という文字列が代入されます。
if len(val) > 4:
if num == eval(val):
print(val + " = " + str(num))
(1)は特に変わりません。
(2)でsplit()
を使い、変数valを*
を基準にして区切った配列にします。
その結果が["0", "0", "01"]
ですね。
(3)ではまず、(2)で宣言した変数val_lとリスト内包表記を使って再度配列を作ります。
この配列を作る際に、re.sub()
を使います。
re.sub()
の引数はいろいろな言い方がありますがここでは、
re.sub(置換したい文字列, 置換後の文字, 置換される文字列)
とします。
引数の内訳は下記のようになります。
- 置換したい文字列:
0
ではじまり、0
の直後に1文字あること。その1文字はグループ化する。 - 置換後の文字列:置換したい文字列の1番目のグループそのもの。
- 置換される文字列:変数val_lの中身。
今回はここの置換したい文字列の1番目のグループそのもの
というのに手こずりました。
はじめはコピペでうまくいったのですが、なぜうまくいくのかがわかりませんでした。
ググった結果、このプログラム上での僕の解釈では下記となります。
- forで回された要素が置換したい文字列か評価する。(
01
が置換対象となる) - 置換したい文字列だった場合、
0
の直後の1文字はグループ化する。(1
をグループ化する) - 置換対象をグループ化した1番目と置換する。(
01
を1
と置換する)
グループ化した(()
と記した)のが複数であれば、
\1
、\2
、\3
・・・指定することができるようです。
今回は1つだけでしたので\1
で指定できました。
さて、配列を作り終えましたが文字列に直さなくてはなりません。
そこでjoin()
を使います。
"*".join()
とすることで、
()
内の配列を*
で区切った文字列にすることができます。
その結果が0*0*1
ですね。
これでもし0
が数値の先頭にきてもエラーが発生しないようになりました。
長くなってしまいましたが、ご拝読ありがとうございました。