2025-03-13 12:01:03 +01:00

274 lines
8.9 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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();
}
}