2010-06-02 11 views

risposta

7

potuto fare qualcosa di simile:

enum Operator { 

BITSHIFT { ... }, ADD { ... }, XOR { ... }, //...etc 

Operator public static which(String s) { //...return the correct one 
} 

public abstract int apply(int a, int b); //...defined explicitly for each enum 

} 

il ritorno quella giusta sarà veramente bello una volta passare dichiarazioni sono andare per String s.

Questa soluzione è simile al seguente in uso (sans Operator. se si utilizza un import statica):

int result = Operator.which(s).apply(a,b); 

ma mi piacerebbe andare con qualcun altro è ampiamente testato e utilizzato parser.

+0

+1 Simile alla mia descrizione, ma più corto e ben inserito. –

3

L'utilizzo di un parser effettivo sarebbe probabilmente più robusto, anche se potrebbe essere eccessivo a seconda di quanto sia complesso il problema. JavaCC e ANTLR sono probabilmente i due generatori di parser più popolari nel mondo Java.

Se un generatore di parser è eccessivo (probabilmente perché non si ha/desidera creare una grammatica formale), è possibile utilizzare probabilmente Ragel per implementare un parser personalizzato.

+0

che è lo stesso tipo di cosa quando si sta utilizzando un parser. È possibile avere tutti gli operatori (con 2 operandi) scansionati come OP e quindi è necessario implementare l'operazione effettiva – LB40

15

Non è sicuro se si chiamerebbe questo elegante, ma qui è un modo:

interface Operation { 
    int apply(int a, int b); 
} 

Map<String, Operation> operations = new HashMap<String, Operation>() {{ 
    put("+", new Operation() { public int apply(int a, int b) { return a + b; }}); 
    put("-", new Operation() { public int apply(int a, int b) { return a - b; }}); 
    put("*", new Operation() { public int apply(int a, int b) { return a * b; }}); 
    put("<<", new Operation() { public int apply(int a, int b) { return a << b; }}); 
    // some more operations here 
}}; 

allora si potrebbe sostituire la sua dichiarazione con if:

result = operations.get(n.getString(1)).apply(tmp1, tmp2); 
+0

è così che si implementa in Java il mio pseudocodice. E il verboseness è ciò che lo uccide. BTW, amico, quel tipo di inizializzazione della mappa può essere problematico. È banale scrivere una classe helper che ti permetta di scrivere Map.build(). Put ("a", 1) .put ("b", 2) .map e ottenere una Map ... – alex

+1

che è il tipo di eleganza a cui stavo pensando ... – LB40

+0

@alex, l'inizializzazione con doppia parentesi non è costosa come le persone tendono a pensare. Vedere questo thread: http://stackoverflow.com/questions/924285/efficiency-of-java-double-brace-initialization – missingfaktor

1

si potrebbe usare un JSR-223 linguaggio interpretativo come BeanShell, farlo eseguire l'operazione e quindi ottenere il risultato.

2

A meno che non si disponga di un ampio gruppo di operatori o si voglia essere in grado di consentire espressioni più complesse in futuro, no.

Un modo per fare questo (pseudocodice, se Java ha avuto questa sintassi, questa sarebbe una buona opzione anche con un piccolo insieme di operatori):

final Map<String,Function<(X,X), Y>> OPERATORS = { 
    "<<" : (x,y)->{x << y}, 
    "+" : (x,y)->{x + y}, 
    [...] 
}; 

[...] 

result1 = OPERATORS.get(n.getString(1))(tmp1, tmp2); 

si può scrivere questo in Java, ma a causa di mancanza di letterali concettuali sulla mappa e dichiarazioni di classi anonime, è molto più prolisso.

L'altra opzione è, ha pubblicato da Hank Gay, utilizzare un parser/valutatore "reale". Puoi scrivere il tuo o usare qualcosa come commons-jexl. A meno che non vogliate consentire espressioni più complesse (e quindi sarete obbligati a seguire questa strada), questo è davvero eccessivo.

5

Il modo orientato agli oggetti per farlo sarebbe utilizzare un'enumerazione delle operazioni possibili. In questo modo ogni operazione poteva solo consumare un oggetto in memoria.

public enum Operation { 


    ADD() { 
    public int perform(int a, int b) { 
     return a + b; 
    } 
    }, 
    SUBTRACT() { 
    public int perform(int a, int b) { 
     return a - b; 
    } 
    }, 
    MULTIPLY() { 
    public int perform(int a, int b) { 
     return a * b; 
    } 
    }, 
    DIVIDE() { 
    public int perform(int a, int b) { 
     return a/b; 
    } 
    }; 

    public abstract int perform(int a, int b); 

} 

Per chiamare tale codice, si dovrebbe quindi fare qualcosa di simile:

int result = Operation.ADD(5, 6); 

Poi si potrebbe creare una mappa di stringhe di operazioni, in questo modo:

Map<String, Operation> symbols = new Map<String, Operation>(); 
symbols.put("+", Operation.ADD); 
symbols.put("-", Operation.SUBTRACT); 
symbols.put("/", Operation.DIVIDE); 
symbols.put("*", Operation.MULTIPLY); 
... 

Infine, per utilizzare un tale sistema:

symbols.get(n.getString(1).apply(tmp1, tmp2)); 

Un vantaggio di utilizzare enumerazioni in questo modo è che si ha il lusso di confronto tra le operazioni sui dati, se si sceglie di farlo

Operation operation = symbols.get("*"); 
if (operation != Operation.MULTIPLY) { 
    System.out.println("Foobar as usual, * is not multiply!"); 
} 

Inoltre, si ottiene una posizione centralizzata per tutte le operazioni, l'unico il lato negativo di questo è che il file Operation.java potrebbe diventare grande con un insieme sufficientemente grande di operatori.

L'unico problema che potrebbe esistere a lungo termine è che mentre un tale sistema è utile e di facile lettura e comprensione, in realtà non tiene conto della precedenza. Supponendo che la tua formula sia stata valutata nell'ordine di precedenza, questo problema non ha importanza. Esempi di esprimere la formula nell'ordine di precedenza possono essere trovati in Reverse Polish Notation, Polish Notation, ecc

Dove precedenza che conta è quando si è permesso di esprimere elementi come:

4 + 5 * 2 

dove secondo alla convenzione tipica, il 5 * 2 dovrebbe essere valutato prima del 4 + 5. L'unico modo corretto di gestire la precedenza è quello di formare un albero di valutazione in memoria, o di garantire che tutti gli input maneggiano la precedenza in modo semplice e univoco (Notazione polacca , Notazione polacca inversa, ecc.).

Suppongo che tu sappia dei problemi di precedenza, ma grazie per avermi fatto menzionare per il beneficio di coloro che non hanno ancora dovuto scrivere un tale codice.

+1

+1, Ecco una variante di cui sopra in cui non devi aggiungere manualmente le voci alla mappa: http://paste.pocoo.org/show/221499/ – missingfaktor

+0

@Rahul Mi piace il modo in cui hai mescolato il simbolo con l'operazione, ma dopo aver mescolato i due il nome "Operatore" sembra un po 'inappropriato. Poiché la classe ora contiene il simbolo e l'operazione, la chiamerei Evaulator. Ma, "Che cosa c'è in un nome? Quella che chiamiamo una rosa con qualsiasi altro nome avrebbe un odore altrettanto dolce." - Shakespeare –

2

Un enum sarebbe pulito per questo tipo di scenario.

public enum Operator 
{ 
    ADD("+") 
    { 
     public int apply(int a, int b) 
     { 
      return a + b; 
     } 
    } 

    SHIFT_LEFT("<<") 
    { 
     public int apply(int a, int b) 
     { 
      return a << b; 
     } 
    } 

    private String opString; 

    private Operator(String op) 
    { 
     opString = op 
    } 

    static public Operator getOperator(String opRep) 
    { 
     for (Operator o:values()) 
     { 
      if (o.opString.equals(opRep)) 
       return o; 
     } 
     throw new IllegalArgumentException("Operation [" + opRep + "] is not valid"); 
    } 

    abstract public int apply(int a, int b); 
} 

Per chiamare

result = Operator.getOperator("<<").apply(456,4); 

Un'altra possibilità sarebbe quella di includere un metodo statico che prende la rappresentazione di stringa dell'operatore nonché operandi e hanno una singola chiamata di metodo, anche se preferisco li separano .

+0

Puoi sostituire 'EnumSet.allOf (Operator.class)' con 'values ​​()'. – missingfaktor

+0

Grazie! Aggiornamento – Robin

+1

'return null' sta annusando ... – whiskeysierra

1

È anche possibile utilizzare il codice hash dell'operatore e inserirlo su un switch sebbene questo non sia molto "elegante" dal punto di vista OOP.

Questo può funzionare se non si sta aggiungendo operatori troppo spesso (che non credo che si sarebbe)

quindi questo dovrebbe essere sufficiente:

String op = "+"; 

switch(op.hashCode()){ 
    case ADD: r = a + b;break; 
    case SUB: r = a - b;break; 
    case TMS: r = a * b;break; 
    case DIV: r = a/b;break; 
    case MOD: r = a % b;break; 
    case SLF: r = a << b;break; 
    case SRG: r = a >> b;break; 
    case AND: r = a & b;break; 
    case OR: r = a | b;break; 
    case XOR: r = a^b;break; 
    default: out.print("Eerr!!!"); break; 
} 

E hanno AND definito come:

private static final int ADD = 0x2b; 

Ecco il codice di esempio in esecuzione completa:

import static java.lang.System.out; 
class Evaluator { 

    private static final int ADD = 0x2b; // + 
    private static final int SUB = 0x2d; // - 
    private static final int TMS = 0x2a; // * 
    private static final int DIV = 0x2f; ///
    private static final int MOD = 0x25; // % 
    private static final int SLF = 0x780; // << 
    private static final int SRG = 0x7c0; // >> 
    private static final int AND = 0x26; // & 
    private static final int OR = 0x7c; // | 
    private static final int XOR = 0x5e; //^

    private int r; 
    private int a; 
    private String op; 
    private int b; 

    private Evaluator(int a, String op, int b) { 
     this.a = a; this.op = op; this.b = b; 
    } 
    private Evaluator eval() { 
     switch(op.hashCode()){ 
      case ADD: r = a + b;break; 
      case SUB: r = a - b;break; 
      case TMS: r = a * b;break; 
      case DIV: r = a/b;break; 
      case MOD: r = a % b;break; 
      case SLF: r = a << b;break; 
      case SRG: r = a >> b;break; 
      case AND: r = a & b;break; 
      case OR: r = a | b;break; 
      case XOR: r = a^b;break; 
      default: out.print("Eerr!!!"); break; 
     } 
     return this; 
    } 

    // For testing: 
    public static int evaluate(int a, String op , int b) { 
     return new Evaluator(a, op, b).eval().r; 
    } 

    public static void main(String [] args) { 
     out.printf(" 1 + 2 = %d%n", evaluate(1 ,"+" , 2)); 
     out.printf(" 1 - 2 = %d%n", evaluate(1 ,"-" , 2)); 
     out.printf(" 1 * 2 = %d%n", evaluate(1 ,"*" , 2)); 
     out.printf(" 1/2 = %d%n", evaluate(1 ,"/" , 2)); 
     out.printf(" 1 %% 2 = %d%n", evaluate(1 ,"%" , 2)); 
     out.printf(" 1 << 2 = %d%n", evaluate(1 ,"<<" , 2)); 
     out.printf(" 1 >> 2 = %d%n", evaluate(1 ,">>" , 2)); 
     out.printf(" 1 & 2 = %d%n", evaluate(1 ,"&" , 2)); 
     out.printf(" 1 | 2 = %d%n", evaluate(1 ,"|" , 2)); 
     out.printf(" 1^2 = %d%n", evaluate(1 ,"^" , 2)); 
    } 
} 

E questo è tutto. Funziona molto velocemente (sono abbastanza sicuro che un enum in ordine)

Da una prospettiva OOP penso che la risposta di Rahul lo farebbe, ma se vuoi fare sul serio con questo dovresti usare un parser come suggerito da Hank Gay .

p.s. Ottenere il codice hash di una stringa è facile:

System.out.println("+".hashCode()); 

realtà ho usato questo:

public class HexHashCode { 
    public static void main(String [] args) { 
     for(String s: args) { 
      System.out.printf("private final int %s = 0x%x; // %s\n",s,s.hashCode(), s); 
     } 
    } 
} 

ed eseguirlo con:

java HexHashCode + - "*"/"<<" ">>" "%" "<" ">" "==" "!=" "&" "|" "^" 
+0

Potrebbe essere reso più veloce evitando le allocazioni di oggetti non necessarie: http://paste.pocoo.org/show/221566/ – missingfaktor

+0

@Rahul Sono d'accordo, ma la differenza è trascurabile, davvero e il vantaggio dell'utilizzo di oggetti è possibile utilizzare memorizzati in una struttura di dati (ad esempio, una pila) per una valutazione successiva. Ancora piuttosto veloce. – OscarRyz

+0

Non mi dispiace il downvote, vorrei solo sapere il motivo. – OscarRyz

0

@Robin: in Java è possibile I metodi di astrazione non sono astratti e i tipi di enumerazione non possono essere astratti (perché tutti gli enum sono ereditati da Enum, il che significa che un tipo di enum non può ereditare da un altro dalle regole di ereditarietà di Java)

Inoltre, cosa si può fare è aggiungere un metodo statico chiamato registro:

private static final Map<String, Operator> registered=new HashMap<String,Operator>(); 
private static void register(String op, Operator impl) { 
    registered.put(op, impl); 
} 

e quindi chiamare quel metodo nel costruttore; e utilizzare la ricerca della mappa nel metodo getOperator().

+0

Il suo codice è assolutamente corretto. Hai provato a compilarlo prima di postarlo? – missingfaktor

+0

Vedo ora. Perdonami per quello. Tuttavia, il mio suggerimento di usare una mappa - come mostrato sopra - è ancora valido; specialmente se il numero di operazioni definite è di grandi dimensioni o se ci si aspetta di eseguire molti di questi confronti. – user268396

1

è molto facile da fare in c sharp.

var ops = new Dictionary<string, Func<int, int, int>> { 
    {"+", (a, b) => a + b}, 
    {"-", (a, b) => a - b}, 
    {"*", (a, b) => a * b}, 
    {"/", (a, b) => a/b} 
}; 

prova:

var c = ops["+"](2, 3); 
Console.WriteLine(c); 

stampe 5.

Problemi correlati