2010-10-05 49 views
22

我需要轉換與有效的Python語法的字符串,如:轉換一個Python數字表達式乳膠

'1+2**(x+y)' 

,並獲得等同的LaTeX:

$1+2^{x+y}$ 

我已經試過sympy的乳膠的功能,但它處理實際的表達,而不是它的字符串形式:

>>> latex(1+2**(x+y)) 
'$1 + 2^{x + y}$' 
>>> latex('1+2**(x+y)') 
'$1+2**(x+y)$' 

但即使這樣做,它要求將x和y聲明爲類型「符號」。

我想要更直接的東西,最好是使用編譯器模塊的解析器。

>>> compiler.parse('1+2**(x+y)') 
Module(None, Stmt([Discard(Add((Const(1), Power((Const(2), Add((Name('x'), Name('y'))))))))])) 

最後但並非最不重要的,原因是:我需要生成這些乳膠snipptes,這樣我可以在mathjax網頁顯示它們。

+0

一個重要的區別是你解析這些表達式不是爲了評估它們,而是要用它們做文檔佈局。這是說你知道用大括號替換'x + y'周圍的人,但肯定還有其他地方需要保留人。我認爲你將不得不徹底考慮你想要分析的內容以及完成後應該看起來的樣子。然後,您可以開始考慮如何通過給出合適的解析器來實現這種轉換。這也將幫助您制定解析器需要做的事情。 – PaulMcG 2010-10-05 21:11:09

回答

10

您可以使用sympy.latexeval

s = "1+2**(x+y)" 
sympy.latex(eval(s)) # prints '$1 + {2}^{x + y}$' 

您還必須聲明變量符號,但如果這是一個真正的問題,它更容易編寫一個解析器來做到這一點,而不是解析一切並從頭開始生成乳膠。

+0

謝謝Tom10。簡單但優雅。我的原始實現有點像這樣: s =「1 + 2 **(x + y)」 eval('sympy.latex(%s)'%s) 將變量先轉換爲符號後。 – fccoelho 2010-10-06 10:18:56

3

要建立在tom10的回答,您可以定義一個字典,將產生的符號,並使用該調用eval時:

from collections import defaultdict 
class GenerateSymbols(defaultdict): 
    def __missing__(self, key): 
    return sympy.Symbol(key) 

這時如果使用

sympy.latex(eval('1+2**(x+y)',GenerateSymbols())) 

你不應該擔心關於爲變量預先創建符號。

+0

謝謝傑夫,輝煌!看到我下面的修復。 – fccoelho 2010-10-06 11:16:56

4

只是一點點修復傑夫蘆葦叢出色答卷:

class GenerateSymbols(defaultdict): 
    def __missing__(self, key): 
     self[key] = sympy.Symbol(key) 
     return self[key] 

之前,不會將新項目添加到字典。現在你可以用你的表達用這樣的:

d= GenerateSymbols()  
eq = '(-b-sqrt(b**2-4*a*c))/(2*a)' 

,你可以將其轉換爲LaTeX的前進一步簡化它:

sympy.latex(sympy.simplify(eval(eq,d))) 

,你會得到這樣的:

'$- \\frac{b + \\operatorname{sqrt}\\left(- 4 a c + b^{2}\\right)}{2 a}$' 
+0

如果有人有不涉及sympy的解決方案,請發佈!我不想爲了這個小任務而拖拽我的app的sympy! (但我承認這種方式更好,不得不依靠其他一些CAS巨獸的聖人) – fccoelho 2010-10-06 11:19:43

16

這是一個相當很長,但仍然不完整的方法,不以任何方式涉及sympy。這是足以覆蓋其被翻譯爲\frac{- b - \sqrt{b^{2} - 4 \; a \; c}}{2 \; a},並呈現爲

alt text

它基本上創建AST並指導其生產的乳膠數學的對應於AST節點(-b-sqrt(b**2-4*a*c))/(2*a)的例子。那裏有什麼應該給予足夠的想法如何在它缺乏的地方擴展它。


import ast 

class LatexVisitor(ast.NodeVisitor): 

    def prec(self, n): 
     return getattr(self, 'prec_'+n.__class__.__name__, getattr(self, 'generic_prec'))(n) 

    def visit_Call(self, n): 
     func = self.visit(n.func) 
     args = ', '.join(map(self.visit, n.args)) 
     if func == 'sqrt': 
      return '\sqrt{%s}' % args 
     else: 
      return r'\operatorname{%s}\left(%s\right)' % (func, args) 

    def prec_Call(self, n): 
     return 1000 

    def visit_Name(self, n): 
     return n.id 

    def prec_Name(self, n): 
     return 1000 

    def visit_UnaryOp(self, n): 
     if self.prec(n.op) > self.prec(n.operand): 
      return r'%s \left(%s\right)' % (self.visit(n.op), self.visit(n.operand)) 
     else: 
      return r'%s %s' % (self.visit(n.op), self.visit(n.operand)) 

    def prec_UnaryOp(self, n): 
     return self.prec(n.op) 

    def visit_BinOp(self, n): 
     if self.prec(n.op) > self.prec(n.left): 
      left = r'\left(%s\right)' % self.visit(n.left) 
     else: 
      left = self.visit(n.left) 
     if self.prec(n.op) > self.prec(n.right): 
      right = r'\left(%s\right)' % self.visit(n.right) 
     else: 
      right = self.visit(n.right) 
     if isinstance(n.op, ast.Div): 
      return r'\frac{%s}{%s}' % (self.visit(n.left), self.visit(n.right)) 
     elif isinstance(n.op, ast.FloorDiv): 
      return r'\left\lfloor\frac{%s}{%s}\right\rfloor' % (self.visit(n.left), self.visit(n.right)) 
     elif isinstance(n.op, ast.Pow): 
      return r'%s^{%s}' % (left, self.visit(n.right)) 
     else: 
      return r'%s %s %s' % (left, self.visit(n.op), right) 

    def prec_BinOp(self, n): 
     return self.prec(n.op) 

    def visit_Sub(self, n): 
     return '-' 

    def prec_Sub(self, n): 
     return 300 

    def visit_Add(self, n): 
     return '+' 

    def prec_Add(self, n): 
     return 300 

    def visit_Mult(self, n): 
     return '\\;' 

    def prec_Mult(self, n): 
     return 400 

    def visit_Mod(self, n): 
     return '\\bmod' 

    def prec_Mod(self, n): 
     return 500 

    def prec_Pow(self, n): 
     return 700 

    def prec_Div(self, n): 
     return 400 

    def prec_FloorDiv(self, n): 
     return 400 

    def visit_LShift(self, n): 
     return '\\operatorname{shiftLeft}' 

    def visit_RShift(self, n): 
     return '\\operatorname{shiftRight}' 

    def visit_BitOr(self, n): 
     return '\\operatorname{or}' 

    def visit_BitXor(self, n): 
     return '\\operatorname{xor}' 

    def visit_BitAnd(self, n): 
     return '\\operatorname{and}' 

    def visit_Invert(self, n): 
     return '\\operatorname{invert}' 

    def prec_Invert(self, n): 
     return 800 

    def visit_Not(self, n): 
     return '\\neg' 

    def prec_Not(self, n): 
     return 800 

    def visit_UAdd(self, n): 
     return '+' 

    def prec_UAdd(self, n): 
     return 800 

    def visit_USub(self, n): 
     return '-' 

    def prec_USub(self, n): 
     return 800 
    def visit_Num(self, n): 
     return str(n.n) 

    def prec_Num(self, n): 
     return 1000 

    def generic_visit(self, n): 
     if isinstance(n, ast.AST): 
      return r'' % (n.__class__.__name__, ', '.join(map(self.visit, [getattr(n, f) for f in n._fields]))) 
     else: 
      return str(n) 

    def generic_prec(self, n): 
     return 0 

def py2tex(expr): 
    pt = ast.parse(expr) 
    return LatexVisitor().visit(pt.body[0].value) 

+0

哇!這真是太神奇了!謝謝傑夫!還沒有測試過它,但它應該成爲一個非常有用的一段代碼,一旦它被測試對比更廣泛的例子。 – fccoelho 2010-10-06 19:02:59

+0

請注意,用於函數調用的'\ operatorname'命令需要amsmath。對於mathjax,您需要根據http://www.mathjax.org/resources/docs/?tex.html#amsmath-and-amssymbol – 2010-10-06 22:07:36

+0

添加適當的擴展。謝謝。減號ej存在問題:「3-(1 + 2)」 - > 3 - 1 + 2 插入 > elif self.prec(n.op)== self.prec(n.right)和isinstance(n.op,ast.Sub): > right = r'\ left(%s \ right)'%self.visit(n.right) 第二個如果在visit_BinOp() – Edoot 2015-11-24 10:39:28

9

您可以使用SymPy。首先將字符串傳遞給sympify()函數,該函數會將其轉換爲有效的SymPy表達式(即爲您創建符號等)。所以,你可以做

>>> latex(sympify('1+2**(x+y)')) 
1 + 2^{x + y} 

S()也是一個捷徑sympify(),即latex(S('1+2**(x+y)'))也適用。