2010-10-05 5 views
22

je dois convertir des chaînes avec la syntaxe python valide tels que:Conversion d'un python expression numérique LATEX

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

et obtenir le LATEX équivalent:

$1+2^{x+y}$ 

J'ai essayé la fonction de latex de sympy mais processus expression réelle, plutôt que la forme de chaîne de celui-ci:

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

mais cela même, il requiert que x et y soient déclarés comme des "symboles" de type.

Je veux quelque chose de plus simple, de préférence faisable avec l'analyseur du module de compilation.

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

Last but not least, le pourquoi: je dois générer ces snipptes en latex afin que je puisse les afficher dans une page Web avec MathJax.

+0

Une différence importante est que vous analysez ces expressions pour ne pas les évaluer, mais pour faire la mise en page du document avec elles. C'est dire que vous avez su remplacer les parens autour de «x + y» par des accolades, mais il y a certainement d'autres endroits où les parens devraient être conservés. Je pense que vous allez devoir réfléchir sérieusement à ce que vous essayez d'analyser et à quoi cela devrait ressembler lorsque vous avez terminé. Ensuite, vous pouvez commencer à réfléchir à la façon dont cette transformation pourrait être réalisée avec un parseur approprié. Cela vous aidera également à formuler ce que l'analyseur devra faire. – PaulMcG

Répondre

10

Vous pouvez utiliser sympy.latex avec eval:

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

Vous devez toujours déclarer les variables comme des symboles, mais si cela est vraiment un problème, il est beaucoup plus facile d'écrire un analyseur pour ce faire que de tout analyser et générer le latex à partir de zéro.

+0

merci Tom10. Simple, mais élégant. Ma mise en œuvre d'origine s'est déroulée comme suit: s = "1 + 2 ** (x + y)" eval ('sympy.latex (% s)'% s) après avoir d'abord transformé les variables en symboles. – fccoelho

3

Pour construire sur la réponse de tom10 vous pouvez définir un dictionnaire qui va générer des symboles et l'utiliser lors de l'appel eval:

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

Ensuite, si vous utilisez

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

vous ne devriez pas avoir à vous soucier à propos de la pré-création de symboles pour les variables.

+0

Merci Geoff, brillant! voir mes correctifs ci-dessous. – fccoelho

4

Juste un petit correctif pour Geoff Reedy excellente réponse:

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

Avant qu'il ne serait pas ajouter le nouvel élément à la dict. Maintenant, vous pouvez l'utiliser avec votre expression:

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

et vous pouvez simplifier davantage avant de le convertir en LaTeX:

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

et vous obtenez ceci:

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

Si quelqu'un a une solution ne impliquant pas sympy, s'il vous plaît poster! Je ne veux pas avoir à faire glisser sympy le long de mon application juste pour cette petite tâche! (Mais j'admets que c'est mieux d'avoir à dépendre de quelques autres monstres de la CAS) – fccoelho

16

Voici un peu méthode longue mais encore incomplète qui n'implique aucunement sympy. Il suffit de couvrir l'exemple de (-b-sqrt(b**2-4*a*c))/(2*a) qui obtient traduit à \frac{- b - \sqrt{b^{2} - 4 \; a \; c}}{2 \; a} et rend comme

alt text

Il crée essentiellement l'AST et marche il produire le calcul de latex les correspond aux nœuds AST. Ce qui est là devrait donner assez d'idée pour l'étendre dans les endroits qui lui manquent.


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

Wow! c'est assez incroyable! Merci Geoff! Je ne l'ai pas encore testé, mais il devrait devenir un morceau de code très utile une fois qu'il sera testé sur une plus grande variété d'exemples. – fccoelho

+0

Notez que la commande '\ operatorname' utilisée pour les appels de fonction requiert amsmath. Pour mathjax vous devez ajouter l'extension appropriée par http://www.mathjax.org/resources/docs/?tex.html#amsmath-and-amssymbol –

+0

Merci. Il y a un problème avec moins ej: "3- (1 + 2)" -> 3 - 1 + 2. Insérer > elif self.prec (n.op) == self.prec (n.right) et isinstance (n.op, ast.Sub): > right = r '\ gauche (% s \ right)'% self.visit (n.right) en second si à l'intérieur visit_BinOp() – Edoot

9

Vous pouvez utiliser SymPy. Passez simplement la chaîne à la fonction sympify() en premier, qui la convertira en une expression SymPy valide (c.-à-d., Créez les symboles pour vous, etc.). Ainsi, vous pouvez faire

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

S() est également un raccourci vers sympify(), à savoir latex(S('1+2**(x+y)')) fonctionne également.