Wizualizator równań Ascii

10

Radzenie sobie z równaniami przy braku dobrego edytora równań jest nieuporządkowane i nieprzyjemne. Na przykład, jeśli chciałbym wyrazić całkę i jej rozwiązanie, mogłoby to wyglądać mniej więcej tak:

Całka [x ^ 3 e ^ (- mx ^ 2 b / 2), dx] = - ((2 + b m x ^ 2) / (b ^ 2 * e ^ ((b m x ^ 2) / 2) * m ^ 2))

W witrynie integrals.wolfram.com nazywa się to „formularzem wejściowym”. Nikt nie lubi widzieć równania w „formie wejściowej”. Idealnym sposobem wizualizacji tego równania byłoby:

wprowadź opis zdjęcia tutaj

(Wolfram nazywa to „tradycyjną formą”)

Dla tego kodegolfa napisz program, który weźmie jakieś równanie w „formie wejściowej” jako dane wejściowe i wizualizuj to równanie w postaci ascii „tradycyjnej formy”. W tym przykładzie możemy otrzymać coś takiego:

       /\      3
       |      x
       | ------------  dx = 
       |       2
      \/   (m x  b)/2
          e

              2
     2 + b m x
-(-----------------)
            2
   2  (b m x )/2  2
  b  e           m

Wymagania:

  1. Nie tasuj, nie upraszczaj ani nie zmieniaj w żaden sposób danych wejściowych. Renderuj go w dokładnie takiej samej formie, w jakiej został opisany przez dane wejściowe.
  2. Obsługuje cztery podstawowe operacje matematyczne (+, -, *, /). Gdy nie mnożymy dwóch sąsiednich liczb, sugerowany jest symbol * i należy go pominąć.
  3. Obsługa integracji (jak pokazano w powyższym przykładzie) nie jest wymagana. Możliwość obsługi danych wejściowych za pomocą funkcji takich jak Integrate [...] lub Sqrt [...] to bonus.
  4. Moce wsparcia, jak pokazano w powyższym przykładzie (n-ty pierwiastek można modelować, podnosząc do 1 / n-tej mocy).
  5. Nadmiarowe nawiasy (takie jak wokół mianownika i licznika dużej frakcji w powyższym przykładzie) należy pominąć.
  6. Wyrażenie w mianowniku i liczniku ułamka powinno być wyśrodkowane powyżej i poniżej poziomej linii podziału.
  7. Możesz wybrać, czy rozpocząć nową linię po znaku równości. W powyższym przykładzie uruchomiono nową linię.
  8. Kolejność operacji musi być dokładnie taka sama na wyjściu, jak na wejściu.

Kilka przykładów danych wejściowych i powiązanych danych wyjściowych do testowania rozwiązania:

Wejście:

1/2 + 1/3 + 1/4

Wynik:

1   1   1
- + - + -
2   3   4

Wejście:

3x^2 / 2 + x^3^3

Wynik:

   2     3
3 x     3
---- + x   
 2

Wejście:

(2 / x) / (5 / 4^2)

Wynik:

2
-
x
--
5
--
 2
4

Wejście:

(3x^2)^(1/2)

Wynik:

    2 1/2
(3 x )
Jestem
źródło
Twoje pytanie powinno zazwyczaj zawierać tag wyrażający rodzaj konkurencji. Pozwoliłem sobie na dodanie jednego, ponieważ w tekście napisałeś „codegolf”.
dmckee --- były moderator kotek
3
Ten problem jest zbyt mglisty, aby można go było grać w golfa. Nie mówisz, jakie konstrukcje muszą być obsługiwane lub jak mają wyglądać. Czy wystarczyłoby wspieranie +, -, * i / /? Czy Sigma musi być obsługiwana? Co z literami greckimi? Możliwe rozwiązania postawionego pytania mogą być zbyt zróżnicowane pod względem funkcjonalności, aby można je było porównać z długością kodu.
MtnViewMark,
@MtnViewMark, dodałem trochę „Wymagania” ... daj mi znać, czy golf jest teraz lepszy.
Ami
@Ami - tak, dużo.
MtnViewMark,
Zgadzam się z MtnViewMark, wydaje się to bardzo otwarte i niejasne. Być może lepiej byłoby ograniczyć dane wejściowe i wyjściowe do dobrze określonego zestawu skrzynek testowych na potrzeby gry w golfa. Czy wykonałeś referencyjną implementację?
gnibbler

Odpowiedzi:

10

Python 2, 1666 znaków

Układ jest właściwie dość łatwy - to parsowanie danych wejściowych jest królewskim bólem. Nadal nie jestem pewien, czy to całkowicie poprawne.

import re,shlex
s=' '
R=range

# Tokenize.  The regex is because shlex doesn't treat 3x and x3 as two separate tokens.  The regex jams a space in between.                                                 
r=r'\1 \2'
f=re.sub
x=shlex.shlex(f('([^\d])(\d)',r,f('(\d)([^\d])',r,raw_input())))
T=[s]
while T[-1]:T+=[x.get_token()]
T[-1]=s

# convert implicit * to explicit *                                                                                                                                          
i=1
while T[i:]:
 if(T[i-1].isalnum()or T[i-1]in')]')and(T[i].isalnum()or T[i]in'('):T=T[:i]+['*']+T[i:]
 i+=1

# detect unary -, replace with !                                                                                                                                            
for i in R(len(T)):
 if T[i]=='-'and T[i-1]in'=+-*/^![( ':T[i]='!'
print T

# parse expression: returns tuple of op and args (if any)                                                                                                                   
B={'=':1,',':2,'+':3,'-':3,'*':4,'/':4,'^':5}
def P(t):
 d,m=0,9
 for i in R(len(t)):
  c=t[i];d+=c in'([';d-=c in')]'
  if d==0and c in B and(B[c]<m or m==B[c]and'^'!=c):m=B[c];r=(c,P(t[:i]),P(t[i+1:]))
 if m<9:return r
 if'!'==t[0]:return('!',P(t[1:]))
 if'('==t[0]:return P(t[1:-1])
 if'I'==t[0][0]:return('I',P(t[2:-1]))
 return(t[0],)

# parenthesize a layout                                                                                                                                                     
def S(x):
 A,a,b,c=x
 if b>1:A=['/'+A[0]+'\\']+['|'+A[i]+'|'for i in R(1,b-1)]+['\\'+A[-1]+'/']
 else:A=['('+A[0]+')']
 return[A,a+2,b,c]

# layout a parsed expression.  Returns array of strings (one for each line), width, height, centerline                                                                      
def L(z):
 p,g=z[0],map(L,z[1:])
 if p=='*':
  if z[1][0]in'+-':g[0]=S(g[0])
  if z[2][0]in'+-':g[1]=S(g[1])
 if p=='^'and z[1][0]in'+-*/^!':g[0]=S(g[0])
 if g:(A,a,b,c)=g[0]
 if g[1:]:(D,d,e,f)=g[1]
 if p in'-+*=,':
  C=max(c,f);E=max(b-c,e-f);F=C+E;U=[s+s+s]*F;U[C]=s+p+s;V=3
  if p in'*,':U=[s]*F;V=1
  return([x+u+y for x,u,y in zip((C-c)*[s*a]+A+(E-b+c)*[s*a],U,(C-f)*[s*d]+D+(E-e+f)*[s*d])],a+d+V,F,C)
 if'^'==p:return([s*a+x for x in D]+[x+s*d for x in A],a+d,b+e,c+e)
 if'/'==p:w=max(a,d);return([(w-a+1)/2*s+x+(w-a)/2*s for x in A]+['-'*w]+[(w-d+1)/2*s+x+(w-d)/2*s for x in D],w,b+e+1,b)
 if'!'==p:return([' -  '[i==c::2]+A[i]for i in R(b)],a+2,b,c)
 if'I'==p:h=max(3,b);A=(h-b)/2*[s*a]+A+(h-b+1)/2*[s*a];return(['  \\/|/\\  '[(i>0)+(i==h-1)::3]+A[i]for i in R(h)],a+3,h,h/2)
 return([p],len(p),1,0)

print'\n'.join(L(P(T[1:-1]))[0])

Za duży wkład w pytanie otrzymuję:

 /\         2                     2 
 |     - m x  b          2 + b m x  
 |     --------    = - -------------
 |  3      2                    2   
\/ x  e         dx         b m x    
                           ------   
                        2     2    2
                       b  e       m 

Oto kilka trudniejszych przypadków testowych:

I:(2^3)^4
O:    4
  / 3\ 
  \2 / 

I:(2(3+4)5)^6
O:             6
  (2 (3 + 4) 5) 

I:x Integral[x^2,dx] y
O:   /\ 2     
  x  | x  dx y
    \/        

I:(-x)^y
O:     y
  (- x) 

I:-x^y
O:     y
  (- x)

Ten ostatni jest zły, jakiś błąd pierwszeństwa w parserze.

Keith Randall
źródło
czy podstawa argumentu całkowego nie powinna być wyśrodkowana po całce? Obecnie bardziej przypomina indeks dolny całki.
Joey,
Nie trudno to zmienić, ale marnowałoby trochę miejsca. Obecnie znak całki jest wystarczająco duży, aby objąć jego argument (3 wysokie minimum).
Keith Randall
Lekki golf: używaj tabulacji zamiast podwójnych spacji.
CalculatorFeline