小町算(3)

evalする代わりに自分でパーサを書いた。

# komachi_parser.py
# -*- coding: utf-8 -*-

def e(L):
    '+, -を処理'
    v1 = t(L)
    while True:
        if L and (L[0] == '+' or L[0] == '-'):
            op = L.pop(0)
            v2 = t(L)
            if op == '+':
                val = v1 + v2
            elif op == '-':
                val = v1 - v2
            v1 = val
        else:
            break
    return v1

def t(L):
    '*, /を処理'
    v1 = f(L)
    while True:
        if L and (L[0] == '*' or L[0] == '/'):
            op = L.pop(0)
            v2 = f(L)
            if op == '*':
                val = v1 * v2
            elif op == '/':
                val = v1 / v2
            v1 = val
        else:
            break
    return v1

def f(L):
    '数値を処理'
    if L and L[0].isdigit():
        return int(L.pop(0))
    else:
        raise ValueError

if __name__ == '__main__':
    print e( ['1234', '-', '5', '*', '6', '/', '7', '-', '8', '*', '9'] )

自分のパーサとevalによるパースとで時間を比較してみた。

import komachi_parser
import re, time

a = ['1']
for n in map(str, range(2, 10)):
    b = []
    for s in a:
        for o in ['+', '-', '*', '/', '']:
            b += [s+o+n]
    a = b

t = time.time()
cnt=0
p = re.compile(r'([^\d])')
for exp in a:
    exp2 = re.split(p, exp)
    if komachi_parser.e(exp2) == 100:
        cnt += 1
print '%d pattern(s), %g sec' % (cnt, time.time() - t)

t = time.time()
cnt=0
for e in a:
    if eval(e) == 100:
        cnt += 1
print '%d pattern(s), %g sec' % (cnt, time.time() - t)

# 303 pattern(s), 16.091 sec
# 303 pattern(s), 14.311 sec

timeモジュールなので時間計測の精度は悪いかもしれないが、約2秒近く違う。evalはビルトインなのでCで書かれていると思うが、自分のパーサはPythonで書いてあるので決め打ちの単純な処理しかしていないのに遅い。exp2を作るのに、一度文字列を作ってsplitするのではなく、直接生成すれば若干早くなる可能性はある。


しかし、特殊なアルゴリズムでも考えない限り、k.inabaさんのような「リスト作成 + 式評価」を同時に行う方法が一番早いと思われる。