Convertir des expressions infixes en notation postfixée

23

Quand j'ai vu le titre de cette question fermée , j'ai pensé que cela ressemblait à un défi de golf de code intéressant. Alors laissez-moi le présenter comme tel:

Défi:

Écrivez un programme, une expression ou un sous-programme qui, étant donné une expression arithmétique en notation infixe , comme 1 + 2, génère la même expression en notation postfixe , c'est-à-dire 1 2 +.

(Remarque: un défi similaire a été publié plus tôt en janvier. Cependant, je pense que les deux tâches sont suffisamment différentes en détail pour justifier ce défi distinct. De plus, je n'ai remarqué l'autre fil après avoir tapé tout ci-dessous, et je préfère pas simplement jeter le tout.)

Contribution:

L'entrée se compose d'une expression arithmétique infixée valide consistant en nombres (entiers non négatifs représentés comme des séquences d'un ou plusieurs chiffres décimaux), équilibré entre parenthèses pour indiquer une sous - expression regroupés, et les quatre binaires infix opérateurs + , -, *et /. N'importe lequel d'entre eux peut être séparé (et l'expression entière entourée) par un nombre arbitraire de caractères d'espace, qui doivent être ignorés. 1

Pour ceux qui aiment les grammaires formelles, voici une grammaire simple de type BNF qui définit les entrées valides. Pour des raisons de concision et de clarté, la grammaire n'inclut pas les espaces facultatifs, qui peuvent apparaître entre deux jetons (autres que les chiffres d'un nombre):

expression     := number | subexpression | expression operator expression
subexpression  := "(" expression ")"
operator       := "+" | "-" | "*" | "/"
number         := digit | digit number
digit          := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"

1 Le seul cas où la présence d'espaces peut affecter l'analyse est lorsqu'ils séparent deux nombres consécutifs; cependant, puisque deux nombres non séparés par un opérateur ne peuvent pas apparaître dans une expression d'infixe valide, ce cas ne peut jamais se produire dans une entrée valide.

Sortie:

La sortie doit être une expression suffixe équivalente à l'entrée. L'expression de sortie doit contenir que des nombres et les opérateurs, avec un seul caractère d'espace entre chaque paire de jetons adjacents, comme dans la grammaire ci - après (qui ne comprennent les espaces) 2 :

expression  := number | expression sp expression sp operator
operator    := "+" | "-" | "*" | "/"
number      := digit | digit number
digit       := "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"
sp          := " "

2 Encore pour plus de simplicité, la numberproduction dans cette grammaire admet des nombres avec des zéros en tête, même s'ils sont interdits dans la sortie par les règles ci-dessous.

Priorité des opérateurs:

En l'absence de parenthèses, les règles de priorité suivantes s'appliquent:

  • Les opérateurs *et /ont une priorité plus élevée que +et -.
  • Les opérateurs *et /ont une priorité égale les uns aux autres.
  • Les opérateurs +et -ont une priorité égale les uns aux autres.
  • Tous les opérateurs sont associatifs à gauche.

Par exemple, les deux expressions suivantes sont équivalentes:

1 + 2 / 3 * 4 - 5 + 6 * 7
((1 + ((2 / 3) * 4)) - 5) + (6 * 7)

et ils devraient tous deux produire la sortie suivante:

1 2 3 / 4 * + 5 - 6 7 * +

(Ce sont les mêmes règles de priorité que dans le langage C et dans la plupart des langues qui en dérivent. Elles ressemblent probablement aux règles qui vous ont été enseignées au primaire, sauf peut-être pour la priorité relative de *et /.)

Règles diverses:

  • Si la solution donnée est une expression ou un sous-programme, l'entrée doit être fournie et la sortie renvoyée sous la forme d'une chaîne unique. Si la solution est un programme complet, elle doit lire une ligne contenant l'expression infixe de l'entrée standard et imprimer une ligne contenant la version postfixe sur la sortie standard.

  • Les nombres dans l'entrée peuvent inclure des zéros non significatifs. Les nombres dans la sortie ne doivent pas avoir de zéros en tête (à l'exception du nombre 0, qui doit être sorti comme 0).

  • Vous n'êtes pas censé évaluer ou optimiser l'expression en aucune façon. En particulier, vous ne devez pas supposer que les opérateurs satisfont nécessairement toutes les identités associatives, commutatives ou autres identités algébriques. Autrement dit, vous ne devez pas supposer que, par exemple, 1 + 2est égal 2 + 1ou 1 + (2 + 3)égal à (1 + 2) + 3.

  • Vous pouvez supposer que les nombres en entrée ne dépassent pas 2 31 - 1 = 2147483647.

Ces règles visent à garantir que la sortie correcte est définie de manière unique par l'entrée.

Exemples:

Voici quelques expressions d'entrée valides et les sorties correspondantes, présentées sous la forme "input" -> "output":

"1"                  ->  "1"
"1 + 2"              ->  "1 2 +"
" 001  +  02 "       ->  "1 2 +"
"(((((1))) + (2)))"  ->  "1 2 +"
"1+2"                ->  "1 2 +"
"1 + 2 + 3"          ->  "1 2 + 3 +"
"1 + (2 + 3)"        ->  "1 2 3 + +"
"1 + 2 * 3"          ->  "1 2 3 * +"
"1 / 2 * 3"          ->  "1 2 / 3 *"
"0102 + 0000"        ->  "102 0 +"
"0-1+(2-3)*4-5*(6-(7+8)/9+10)" -> "0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * -"

(Au moins, j'espère que tout cela est correct; j'ai fait la conversion à la main, donc des erreurs auraient pu se glisser.)

Juste pour être clair, les entrées suivantes sont toutes invalides; il ne pas d' importance ce que votre solution ne se les donne (bien que, bien sûr, par exemple , renvoyer un message d'erreur est plus agréable que, par exemple, la consommation d' une quantité infinie de mémoire):

""
"x"
"1 2"
"1 + + 2"
"-1"
"3.141592653589793"
"10,000,000,001"
"(1 + 2"
"(1 + 2)) * (3 / (4)"
Ilmari Karonen
la source
La notation de type Lisp est-elle acceptable? Par exemple, 1 2 3 4 +signifie «1 + 2 + 3 + 4».
Hauleth
3
@Hauleth: Pas dans ce défi, non. D'ailleurs, sans parenthèses, comment analyseriez-vous 1 2 3 4 + *?
Ilmari Karonen
Donc, aucun espace de fin (y compris une nouvelle ligne) n'est autorisé dans l'otuput?
boîte à pain
@breadbox: les sauts de ligne sont corrects. En fait, permettez-moi de préciser explicitement que tout espace de fin est autorisé.
Ilmari Karonen
J'ai une solution qui affiche "0 1 - 2 3 - 4 * 5 6 7 8 + 9 / - 10 + * - +" pour le dernier exemple valide, qui me semble correct. Peux-tu vérifier? (Remarquez le dernier opérateur +)
coredump

Réponses:

8

Shell utils - 60 caractères

bc -c|sed -re's/[@iK:Wr]+/ /g;s/[^0-9]/ &/g;s/ +/ /g;s/^ //'

Correction des divers problèmes, mais cela a pris beaucoup plus de temps :(

Geoff Reedy
la source
1
C'est plutôt intelligent, sauf qu'il ne semble pas gérer correctement les nombres supérieurs à 9.
boîte à pain
@breadbox, le sed -re's/[:@iKWr]+/ /g'corrige au coût de 1 caractère.
ugoren
oups, bien que la suggestion @ugoren ne fonctionne pas car les opérateurs consécutifs n'ont plus d'espace entre eux; Je dois trouver un correctif pour cela aussi
Geoff Reedy
4

C, 250 245 236 193 185 caractères

char*p,b[99];f(char*s){int t=0;for(;*p-32?
*p>47?printf("%d ",strtol(p,&p,10)):*p==40?f(p++),++p:
t&&s[t]%5==2|*p%5-2?printf("%c ",s[t--]):*p>41?s[++t]=*p++:0:++p;);}
main(){f(p=gets(b));}

Voici une version lisible de la source non golfée, qui reflète toujours la logique de base. C'est en fait un programme assez simple. Le seul vrai travail qu'il a à faire est de pousser un opérateur à faible associativité sur une pile lorsqu'un opérateur à haute associativité est rencontré, puis de le réactiver à la "fin" de cette sous-expression.

#include <stdio.h>
#include <stdlib.h>

static char buf[256], stack[256];
static char *p = buf;

static char *fix(char *ops)
{
    int sp = 0;

    for ( ; *p && *p != '\n' && *p != ')' ; ++p) {
        if (*p == ' ') {
            continue;
        } else if (*p >= '0') {
            printf("%ld ", strtol(p, &p, 10));
            --p;
        } else if (*p == '(') {
            ++p;
            fix(ops + sp);
        } else {
            while (sp) {
                if ((ops[sp] == '+' || ops[sp] == '-') &&
                        (*p == '*' || *p == '/')) {
                    break;
                } else {
                    printf("%c ", ops[sp--]);
                }
            }
            ops[++sp] = *p;
        }
    }
    while (sp)
        printf("%c ", ops[sp--]);
    return p;
}

int main(void)
{
    fgets(buf, sizeof buf, stdin);
    fix(stack);
    return 0;
}
boite à pain
la source
Enregistrez les caractères en les supprimant if. Par exemple if(!*p||*p==41)return p;s[++t]=*p;}->return*p&&*p-41?s[++t]=*p:p;
ugoren
Déclaration de style K&R:*f(p,s)char*p,s;{
ugoren
1. C'est une erreur de retourner si le iftest échoue. 2. Je sais, mais la fonction K&R decls est l'endroit où je trace la ligne. Je ne peux tout simplement pas y revenir.
boîte à pain
Je pensais que le retour était de toute façon à la fin de la fonction. Manqué le }}et for. Mais voici une amélioration:printf(" %ld"+!a,...
ugoren
1
Je pense également que vous devriez rendre pglobal (l'appel récursif assigne simplement pl'appelé à l'appelant). Alors fais f(p=gets(b)).
ugoren
2

Bash avec Haskell avec Préprocesseur C sed, 180 195 198 275

echo 'CNumO+O-O*fromInteger=show
CFractionalO/
main=putStr$'$*|sed 's/C\([^O]*\)/instance \1 String where /g
s/O\(.\?\)/a\1b=unwords\[a,b,\"\1\"];/g'|runghc -XFlexibleInstances 2>w

Enfin, elle n'est plus plus longue que la solution C. La partie cruciale de Haskell est presque aussi paresseuse que la solution bc ...

Prend l'entrée comme paramètres de ligne de commande. Un fichier wcontenant des messages d'avertissement ghc sera créé si vous n'aimez pas cette modification runghc 2>/dev/null.

a cessé de tourner dans le sens antihoraire
la source
1
Basked? ( Bas h + H aske ll + s ed )
CalculatorFeline
2

Python 2, 290 272 268 250 243 238 octets

Maintenant enfin plus court que la réponse JS!

Il s'agit d'un programme complet qui utilise une implémentation de base de l' algorithme de triage . L'entrée est donnée sous forme de chaîne entre guillemets et le résultat est imprimé dans STDOUT.

import re
O=[];B=[]
for t in re.findall('\d+|\S',input()):exec("O=[t]+O","i=O.index('(');B+=O[:i];O=O[i+1:]","while O and'('<O[0]and(t in'*/')<=(O[0]in'*/'):B+=O.pop(0)\nO=[t]+O","B+=`int(t)`,")[(t>'/')+(t>')')+(t>'(')]
print' '.join(B+O)

Essayez-le en ligne!


Explication:

La première chose que nous devons faire est de convertir l'entrée en jetons. Nous le faisons en utilisant en trouvant toutes les correspondances de l'expression régulière\d+|\S , traduit grossièrement « tout groupe de chiffres, et un caractère non-espace ». Cela supprime les espaces, analyse les chiffres adjacents comme des jetons uniques et analyse les opérateurs séparément.

Pour l'algorithme de triage, il y a 4 types de jetons distincts que nous devons gérer:

  • ( - Parenthèse gauche
  • ) - Parenthèse droite
  • +-*/ - Les opérateurs
  • 9876543210 - Littéraux numériques

Heureusement, les codes ASCII de ceux-ci sont tous regroupés dans l'ordre indiqué, nous pouvons donc utiliser l'expression (t>'/')+(t>')')+(t>'(')pour calculer le type de jeton. Il en résulte 3 pour les chiffres, 2 pour les opérateurs, 1 pour une parenthèse droite et 0 pour une parenthèse gauche.

En utilisant ces valeurs, nous indexons dans le grand tuple après execpour obtenir l'extrait correspondant à exécuter, en fonction du type de jeton. Ceci est différent pour chaque jeton et constitue l'épine dorsale de l'algorithme de triage. Deux listes sont utilisées (sous forme de piles): O(pile d'opérations) et B(tampon de sortie). Une fois tous les jetons exécutés, les opérateurs restants de la Opile sont concaténés avec le tampon de sortie et le résultat est imprimé.

FlipTack
la source
2

Prolog (SWI-Prolog) , 113 octets

c(Z,Q):-Z=..[A,B,C],c(B,S),c(C,T),concat_atom([S,T,A],' ',Q);term_to_atom(Z,Q).
p(X,Q):-term_to_atom(Z,X),c(Z,Q).

Essayez-le en ligne!

SWI Prolog a pour cela un ensemble bien mieux intégré que GNU Prolog, mais il est encore quelque peu freiné par la verbosité de la syntaxe de Prolog.

Explication

term_to_atom, si elle est exécutée à l'envers, analysera une expression de notation infixée (stockée sous forme d'atome) dans un arbre d'analyse (en respectant les règles de priorité habituelles et en supprimant les zéros et les espaces). Nous utilisons ensuite le prédicat d'aide cpour effectuer une récursion structurelle sur l'arbre d'analyse, en convertissant en notation postfixe en profondeur d'abord.


la source
1

Javascript (ES6), 244 octets

f=(s,o={'+':1,'-':1,'*':2,'/':2},a=[],p='',g=c=>o[l=a.pop()]>=o[c]?g(c,p+=l+' '):a.push(l||'',c))=>(s.match(/[)(+*/-]|\d+/g).map(c=>o[c]?g(c):(c==')'?eval(`for(;(i=a.pop())&&i!='(';)p+=i+' '`):c=='('?a.push(c):p+=+c+' ')),p+a.reverse().join` `)

Exemple:
Appel: f('0-1+(2-3)*4-5*(6-(7+8)/9+10)')
Sortie: 0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * -(avec un espace de fin)

Explication:

f=(s,                                                     //Input string
    o={'+':1,'-':1,'*':2,'/':2},                          //Object used to compare precedence between operators
    a=[],                                                 //Array used to stack operators
    p='',                                                 //String used to store the result
    g=c=>                                                 //Function to manage operator stack
        o[l=a.pop()]>=o[c]?                               //  If the last stacked operator has the same or higher precedence
            g(c,p+=l+' '):                                //  Then adds it to the result and call g(c) again
            a.push(l||'',c)                               //  Else restack the last operator and adds the current one, ends the recursion.
)=>                                                       
    (s.match(/[)(+*/-]|\d+/g)                             //Getting all operands and operators
    .map(c=>                                              //for each operands or operators
        o[c]?                                             //If it's an operator defined in the object o
            g(c)                                          //Then manage the stack
            :(c==')'?                                     //Else if it's a closing parenthese
                eval(`                                    //Then
                    for(;(i=a.pop())&&i!='(';)            //  Until it's an opening parenthese
                        p+=i+' '                          //  Adds the last operator to the result
                `)                                        
                :c=='('?                                  //Else if it's an opening parenthese
                    a.push(c)                             //Then push it on the stack
                    :p+=+c+' '                            //Else it's an operand: adds it to the result (+c removes the leading 0s)
        )                                                 
    )                                                     
    ,p+a.reverse().join` `)                               //Adds the last operators on the stack to get the final result
Hedi
la source
1

R, 142 octets

R est capable de s'auto-analyser, donc plutôt que de réinventer la roue, nous mettons simplement l'analyseur au travail, qui génère une notation de préfixe, et utilisons une fonction récursive pour la basculer en notation postfixée.

f=function(x,p=1){
if(p)x=match.call()[[2]]
if((l=length(x))>1){
f(x[[2]],0)
if(l>2)f(x[[3]],0)
if((z=x[[1]])!="(")cat(z,"")
}else cat(x,"")
}

L' pargument est de contrôler l'utilisation de l'évaluation non standard (le fléau des programmeurs R partout), et il y a quelques ifs supplémentaires pour contrôler la sortie des crochets (que nous voulons éviter).

Contribution: (0-1+(2-3)*4-5*(6-(7+8)/9+10))

Sortie: 0 1 - 2 3 - 4 * + 5 6 7 8 + 9 / - 10 + * -

Contribution: (((((1))) + (2)))

Sortie: 1 2 +

En prime, il fonctionne avec des symboles arbitraires et toutes les fonctions prédéfinies avec jusqu'à deux arguments:

L'identité d'Euler

Contribution: e^(i*pi)-1

Sortie: e i pi * ^ 1 -

Dividendes de 13 entre 1 et 100

Contribution: which(1:100 %% 13 == 0)

Sortie: 1 100 : 13 %% 0 == which

Régression linéaire du poids du poulet en fonction du temps

Contribution: summary(lm(weight~Time, data=ChickWeight))

Sortie: weight Time ~ ChickWeight lm summary

Le dernier exemple est peut-être un peu en dehors de la portée de l'OP, mais il utilise la notation postfixe, donc ...

rturnbull
la source