SymPyをマネして簡単な記号処理(の つくりかけ)
前回の記事の続き。条件分岐の条件式をSymPyで記述しようと思ったのですが、必要な分づつマネして実装してみることに。
SymPy
Pythonで記号処理、数式処理をする場合はSymPyを使うのが定番かと思います。Mathematicaみたく、代数的な微分積分とか、微分方程式を解くことが出来る すごいソフトです。
前回のコードだとlambda地獄になるので、SymPyにご活躍願って、条件式などを(もうちょっとだけ)すっきり記述できないかと思ったのですが、
from sympy import *
x,y=symbols("x y")
#これは 「数式として」処理される」
>>> x>y
x > y
#あとで具体的な値を代入することもできる
>>> (x>y).subs({"x":2,"y":1})
True
#2重等号は その場で処理されちゃう
>>> x==y
False条件分岐の判定には2重等号も数式として処理してほしかったのですが。(たぶん、大人の事情で、その場で処理しないと困ることがると思われ)
アドホックにパッチあてて遊ぶには内部構造が複雑だったので、必要な分づつマネして実装することにしました。
(微分積分とか そういうのまで やる気は無いので)
とりあえず紛失防止のため、できたとこまでメモ
Pythonオブジェクトの演算子をオーバーライドして、数式を書くとLispみたいなS式に変換する。。。だけのコード
あと、都合で、S式 (ConsList)をPythonの組み込みリストっぽく使うためのメソッドが少し付けてあります。
ここから もう一度 Pythonに戻したり*1とか クラスの継承関係とかが まだ適当なので 手を入れたり、いろいろ書かないといけないのですが
実行サンプル
x,y,z=symbols("x y z")
>>> x+y*z
[ + x [ * y z ] ]
>>> x
x
>>> x==y
[ == x y ] #二重等号も数式として処理
>>> a=ConsList([x,y,z])
>>> a.car
x
>>> a.cdr
[ y z ]
>>> list(a) #Pythonの組み込みリストに型変換
[x, y, z]
>>> b=ConsList([x,y,z])
>>> a==b
[ == [ x y z ] [ x y z ] ]
>>> list(a)==list(b)
True
class MyBase(object):
pass
class Atom(MyBase):
def __init__(self,name):
self.name=name
def __repr__(self):
return self.name
class Expr(MyBase):
pass
class Cons(Expr):
def __init__(self,car=None,cdr=None):
self.car=car
self.cdr=cdr
def __getitem__(self,x):
if not isinstance(x,(int,long)):
raise IndexError
if x>=0:
for i,c in enumerate(self):
if i==x:
return c
else:
raise IndexError
elif x<0:
imax=len(self)
for i,c in enumerate(self):
if (i-imax)==x:
return c
else:
raise IndexError
def __len__(self):
for i,c in enumerate(self):
pass
return i+1
def __iter__(self):
cur=self
while 1:
yield cur.car
if cur.cdr is None:
raise StopIteration
else :
cur=cur.cdr
def __repr__(self):
ret=["["]
for i in self:
ret.append(repr(i))
ret.append("]")
return " ".join(ret)
def tail(self):
for cur in self:
pass
return cur
def extend(self,seq):
if isinstance(seq,Cons):
self.tail().cdr=seq
else:
cur=self
for i in seq:
cur.cdr=Cons(i,None)
cur=cur.cdr
return self
class ConsList(Cons):
def __init__(self,seq=[]):
Cons.__init__(self)
try:
cur=self
cur.car=seq[0]
self.extend(seq[1:])
except IndexError as e:
pass
class Func(Atom):
def __init__(self,name):
self.name=name
def __call__(self,*args):
seq=[self].extend(args)
return ConsList(seq)
class Op(Func):
def __init__(self,sym,spname):
self.sym=sym
self.name=spname
def __repr__(self):
return self.sym
def __call__(self,x,y):
getattr(x,spname)(y)
optxt="""+ __add__
- __sub__
* __mul__
/ __div__
== __eq__
< __lt__
> __gt__
% __mod__"""
def defop(cls,op):
def _(self,y):
return ConsList([op,self,y])
return _
for line in optxt.splitlines():
sym,spname=line.split(" ")
op=Op(sym,spname)
setattr(Op,spname,op)
setattr(MyBase,spname,defop(MyBase,op))
def symbols(names):
return [ Atom(i) for i in names.split(" ")]
if __name__=="__main__":
c=ConsList([0,1,2,3])
assert c.car==0 and list(c.cdr)==[1,2,3]
assert len(c)==4
assert c[0]==0 and c[1]==1 and c[2]==2 and c[3]==3
assert c[-4]==0 and c[-3]==1 and c[-2]==2 and c[-1]==3
x,y,z=symbols("x y z")
a=x+y
assert (a[0] is Op.__add__) and (a[1] is x) and (a[2] is y)
b=x+y
assert list(b) ==[ Op.__add__ ,[ Op.__mul__,x,y],z]