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]

*1:S式ではなく、Pythonで読める語順に変換してevalするだけの予定ですけど