本日は(ポエム)
電車の中で
の記事を iPad を通して眺めてました。
私個人としては、経験上、実行速度に差がないケースしか遭遇してないので、
ネストを深くしたくないときは、
import itertools
for _ in itertools.product(range(N),repeat=2):
#write something
Numbaで速くしたい時は
from numba import jit
@jit
def func():
for i in range(N):
for j in range(N):
#write something
のように使い分けています。
でも、Numbaって実務で使ったことがなくて、日曜大工として kaggle の後処理で使って処理が速くなったとか、遅いループを速くしてきゃっきゃと喜ぶという程度でしか使ったことないんですよね・・・。
end of poem
end of poem
ここからが本題です。
itertools.product(range(N),repeat=repeat)
のrepeat
の値を変化させたとき上と同等の作業をするループ処理を
for i in range(N):
for j in range(N):
for k in range(N):
# and so on...
をいちいち手作業で変更してあげないといけません。
自動で書けないのかな?
eval と exec と戯れる。
eval
と exec
でいけるんじゃないか?というところから始まって書いてみました。
import itertools
import time
def naive(N, repeat):
# prepare code
tab = "\t"
func = '\n'.join(['def loop_func(N):', tab + 's = 0'])
loop_txt = lambda n: (n + 1) * tab + "for item_{} in range(N):".format(n)
calc = lambda n: (n + 1) * tab + 's += ' + \
' + '.join(["item_{}".format(_) for _ in range(n)])
code = "\n".join([func] +
[loop_txt(n) for n in range(repeat)] +
[calc(repeat)] +
[tab + 'return s'])
# print(code)
exec(code)
start_time = time.time()
s = eval("loop_func({})".format(N))
end_time = time.time()
elapsed = end_time - start_time
return s, elapsed
def product(N, repeat):
# prepare code
tab = '\t'
items = ' , '.join('item_{}'.format(_) for _ in range(repeat))
sum_items = ' + '.join('item_{}'.format(_) for _ in range(repeat))
forloop = ' '.join([tab + 'for',
items, 'in',
'itertools.product(range(N), repeat=repeat):'])
code = '\n'.join(['def loop_func(N, repeat):', tab + 's = 0', forloop,
2 * tab + 's += ' + sum_items, tab + 'return s'])
# print(code)
exec(code)
start_time = time.time()
s = eval("loop_func({}, {})".format(N, repeat))
end_time = time.time()
return s, end_time - start_time
def main():
N = 10
n_trial = 50
repeat = 6
naive_times = []
product_times = []
for _ in range(n_trial):
naive_s, naive_elapsed = naive(N, repeat)
prodcut_s, prodcut_elapsed = product(N, repeat)
assert naive_s == 27000000
assert prodcut_s == 27000000
naive_times.append(naive_elapsed)
product_times.append(prodcut_elapsed)
print(sum(naive_times) / n_trial)
print(sum(product_times) / n_trial)
if __name__ == '__main__':
main()
とりあえず、自動化させたいコードをPythonの文字列として扱って、
tabと加えたり、'\n'.join([文字列たち])
にしてコードを生成させるようにしました。
例えば、
def loop_func(N):
s = 0
for item_0 in range(N):
for item_1 in range(N):
for item_2 in range(N):
for item_3 in range(N):
for item_4 in range(N):
for item_5 in range(N):
for item_6 in range(N):
s += item_0 + item_1 + item_2 + item_3 + item_4 + item_5 + item_6
return s
のような文字列を吐き出させるようにします。
あとはexec
で実行して戻り値を eval
で得ればミッション終了です。
実行結果は?
$ python meta.py
0.4330342960357666
0.40373848915100097
お、iteratools の方がちょっといい感じでは? assertのエラーも出てないようなので、正常に動作しているように見えますね。repeatの数を増やすと効果が出てくるのかもしれないです(白目)
いままでは一行程度の文字列でしか eval
, exec
していなかったのですが、複数行に渡って exec
ができることを知りました。
チョットPythonのスキルが増えました。