274 lines
8.9 KiB
Java
274 lines
8.9 KiB
Java
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<AbstractToken> input;
|
||
private Deque<AbstractToken> output;
|
||
private Deque<AbstractToken> stack;
|
||
|
||
public ShuntingYard(Deque<AbstractToken> input){
|
||
this.input = input;
|
||
this.output = new ArrayDeque<AbstractToken>();
|
||
this.stack = new ArrayDeque<AbstractToken>();
|
||
}
|
||
|
||
public Deque<AbstractToken> getInput(){
|
||
return this.input;
|
||
}
|
||
|
||
public Deque<AbstractToken> getOutput(){
|
||
return this.stack;
|
||
}
|
||
|
||
public Deque<AbstractToken> 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<AbstractToken> expression = new ArrayDeque<AbstractToken>();
|
||
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();
|
||
}
|
||
|
||
}
|
||
|