import java.util.Deque; import java.util.ArrayDeque; /** * Classe implémentant l'algorithme proposé par Dijkstra en 1961 pour transformer une expression en notation infixe, en expression postfixe. * https://en.wikipedia.org/wiki/Shunting_yard_algorithm * * Le constructeur prend en entrée l'entrée de l'algorithme (input) qui sera une file de AbstractToken et initialise * une pile pour les opérateurs et séparateurs, et une file de sortie. * Pour uniformiser, tout est modélisé avec l'interface Deque pour des AbstractToken. * * En pratique les seuls mouvements entre ces trois structures de données sont faits par trois primitives : * shuntFromInput() (input vers output) * shuntFromStack() (stack vers output) * pushToStack() (input vers stack) * * L'algorithme fonctionne par analogie au rangement des wagons dans une gare de triage (shunting yard veut dire gare de triage). * * output ================== input * \\ // * \ / * \/ * || * stack * * Le dessin en ASCII ci-dessus a été qualifié de "Jackson Pollock" du crobar par Luc Hernandez. * Pour un exemple plus explicite : regardez svp le dessin "Shunting_yard.svg.png" * * * Les constantes et les variables sont envoyées de droite à gauche avec shuntFromInput * Les symboles d'opérations ou de séparation sont envoyés sur la pile avec pushToStack * puis envoyés en sortie avec shuntFromStack quand c'est le bon moment. * * pour les opérateurs, en gros tout se décide avec la priorité des opérations * En cas d'égalité on regarde si l'opérateur est associatif à gauche : * ceci est le cas des 4 opérations binaires * \/ + - mais pas de l'exponentiation ^ (associativité à droite). * Ainsi a^b^c sera interprété convenablement comme a^(b^c) * * @author Florent Madelaine */ public class ShuntingYard { /** * * */ private Deque input; private Deque output; private Deque stack; public ShuntingYard(Deque input){ this.input = input; this.output = new ArrayDeque(); this.stack = new ArrayDeque(); } public Deque getInput(){ return this.input; } public Deque getOutput(){ return this.stack; } public Deque getStack(){ return this.output; } // input and stack are empty public boolean isOver(){ return (this.input.isEmpty() || this.stack.isEmpty()); //BUG FIX 1 } // // output <---------------- input // // // stack /** * Envoie la premiere constante ou variables de la file d'entrée vers la file de sortie. * Si le Token récupéré n'est pas TokenConstant ou un TokenVariable, il y a 2 cas possible: * Soit il s'agit d'un TokenOperator ou d'un TokenSeparator de gauche, dans ce cas on replace l'élément extrait au départ de la file d'entrée * puis on fait appel à la méthode shuntFromStack() plutôt que celle-ci. * Soit il s'agit d'un TokenSeparator de droite, dans ce cas on la replace au départ de la file d'entrée et on fait appel à la méthode crushParenthesis() * pour la supprimer. */ public void shuntFromInput() { x=this.input.getFirst(); if(x instanceof(TokenConstant)||x instanceof(TokenVariable)) this.output.addLast(x); else if(x instanceof(TokenOperator)){ this.input.addFirst(x); shuntFromStack(); } else if(x.getSeparator().equals(Separator.LB)){ this.input.addFirst(x); shuntFromStack(); } else{ this.input.addFirst(x); crushParenthesis(); } } // output <--- input // \ // | // stack /** * Envoie le dernier élément de la pile stack vers la file de sortie. */ public void shuntFromStack() { this.output.addFirst(this.stack.removeLast()); } // output ---------- input // / // V // stack /** * Envoie le premier opérateur ou parenthèse gauche de la file d'entrée vers la pile stack. * Si le Token récupéré n'est pas un TokenOperator ou un TokenSeparator de gauche, il y a 2 cas possible: * Soit le Token est un TokenConstant ou un TokenVariable, dans ce cas on replace le Token extrait au départ de la file d'entrée * puis on fait appel à la méthode shuntFromInput() plutôt que celle-ci. * Soit il s'agit d'un TokenSeparator de droite, dans ce cas on la replace au départ de ola file d'entrée et on fait appel à la méthode crushParenthesis() * pour la supprimer. */ public void pushToStack(){ this.stack.addFirst(this.input.getFirst()); } // output ) input // ^ // ( <------| both to be destroyed // stack // // throws IllegalStateException si ce n'est pas exactement ce cas. /** * Supprime le premier élément de la file d'entrée et le dernier de la pile stack. * @exception IllegalStateException Renvoyer si il y s'agit d'un cas particulier. */ public void crushParenthesis(){ this.stack.removeLast(); this.input.removeFirst(); } // does one step of Dijkstra Shunting algorithm // if is is done it throws the exception public void shunting(){ if (isOver()) throw new IllegalStateException("the shunting is over, since both the input and the stack are empty."); else if (this.input.isEmpty()){ if (this.stack.getFirst() instanceof TokenSeparator){ TokenSeparator s = (TokenSeparator) this.stack.getFirst(); if (s.getSeparator().equals(Separator.LB)){ throw new IllegalArgumentException("the shunting is over with a parenthesis mismatch extra left bracket");} } else // should be an operator shuntFromStack(); } else if (this.input.getFirst() instanceof TokenConstant) { shuntFromInput(); shuntFromInput(); } // else if (this.input.getFirst() instanceof TokenVariable) { shuntFromInput(); } else if (this.input.getFirst() instanceof TokenSeparator) { TokenSeparator s = (TokenSeparator) this.input.getFirst(); // test si LB left bracket if (s.getSeparator().equals(Separator.LB)){ pushToStack(); } else if (s.getSeparator().equals(Separator.RB)){ if(stack.isEmpty()) throw new IllegalArgumentException("the shunting is over with a parenthesis mismatch extra right bracket"); else { if (this.stack.getFirst() instanceof TokenOperator){ shuntFromStack(); // should be the operator concerned by parenthesis } else {// it is a separator crushParenthesis(); // we discard the parenthesis. } } } } else if (this.input.getFirst() instanceof TokenOperator) { if (this.stack.isEmpty()){ pushToStack(); } else if (this.stack.getFirst() instanceof TokenSeparator){ TokenSeparator s = (TokenSeparator) this.stack.getFirst(); pushToStack(); } else if (this.stack.getFirst() instanceof TokenOperator){ TokenOperator o = (TokenOperator) this.input.getFirst(); TokenOperator o2 = (TokenOperator) this.stack.getFirst(); if (o2.takesPrecedenceOver(o)){ shuntFromStack(); } else pushToStack();// shuntFromInput();// input takes precedence } } } @Override public String toString(){ StringBuilder b = new StringBuilder(); b.append("\t\t\t\t"); for(AbstractToken t : this.input ){ b.append(t.toString()); } b.append(" :input\noutput: "); for(AbstractToken t : this.output ){ b.append(t.toString()); } b.append("\n\t\t stack: "); for(AbstractToken t : this.stack ){ b.append(t.toString()); } b.append("\n"); b.append("--------------------------------------------------------------\n"); return b.toString(); } public static void main(String[] args){ // 3 + 4 × (2 − 1) // pas de parenthèse encore, je fais 3 + 4 * 2 Deque expression = new ArrayDeque(); expression.addLast(new TokenConstant(3)); expression.addLast(new TokenOperator(Operator.ADD)); expression.addLast(new TokenConstant(4)); expression.addLast(new TokenOperator(Operator.MUL)); expression.addLast(new TokenSeparator(Separator.LB)); expression.addLast(new TokenConstant(2)); expression.addLast(new TokenOperator(Operator.SUB2)); expression.addLast(new TokenConstant(1)); expression.addLast(new TokenSeparator(Separator.RB)); ShuntingYard se = new ShuntingYard(expression); System.out.println(se); se.shunting(); System.out.println(se); se.shunting(); System.out.println(se); se.shunting(); System.out.println(se); se.shunting(); System.out.println(se); se.shunting(); System.out.println(se); se.shunting(); System.out.println(se); se.shunting(); System.out.println(se); se.shunting(); System.out.println(se); se.shunting(); System.out.println(se); se.shunting(); System.out.println(se); se.shunting(); System.out.println(se); se.shunting(); } }