2010-06-02 11 views

Respuesta

7

podría hacer algo como:

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 

} 

el retorno de la derecha va a ser muy agradable una vez el interruptor declaraciones se van para String s.

Esta solución tiene el siguiente aspecto en uso (sans Operator. si se utiliza una importación estática):

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

pero me gustaría ir con otra persona está ampliamente probado y usado analizador.

+0

+1 Similar a mi descripción, pero más corto y bien puesto. –

3

El uso de un analizador real probablemente sea más robusto, aunque puede ser excesivo dependiendo de qué tan complejo sea su problema. JavaCC y ANTLR son probablemente los dos generadores de analizadores más populares en el mundo de Java.

Si un generador de analizador es excesivo (posiblemente porque no tiene/quiere crear una gramática formal), probablemente podría usar Ragel para implementar un analizador personalizado.

+0

que es el mismo tipo de cosas cuando se está utilizando un programa de análisis. Puede tener todos los operadores (con 2 operandos) escaneados como OP y luego debe implementar la operación real – LB40

15

No está seguro de si usted llamaría este elegante, pero aquí es una manera:

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

entonces se podría sustituir a su estado de cuenta con if:

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

, que es cómo implementa en Java mi pseudocódigo. Y la verboseness es lo que lo mata. Por cierto, amigo, ese tipo de inicialización de Mapa puede ser problemático. Es trivial escribir una clase auxiliar que le permita escribir Map.build(). Put ("a", 1) .put ("b", 2) .map y obtener un Map ... – alex

+1

que sea el tipo de elegancia en la que estaba pensando ... – LB40

+0

@alex, la inicialización del doble refuerzo no es tan cara como la gente tiende a pensar. Vea este hilo: http://stackoverflow.com/questions/924285/efficiency-of-java-double-brace-initialization – missingfaktor

1

Se podría utilizar un JSR-223 lenguaje interpretativo como BeanShell, haga que realice la operación y luego obtenga el resultado.

2

A menos que tenga un conjunto grande de operadores o desee poder permitir expresiones más complejas en el futuro, no.

Una forma de hacer esto (pseudocódigo, si Java tenía esta sintaxis, esto sería una buena opción, incluso con un pequeño conjunto de operadores):

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

[...] 

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

se puede escribir esto en Java, pero debido a falta de literales de mapas concisos y declaraciones de clases anónimas, es mucho más detallado.

La otra opción es, ha publicado Hank Gay, utilizar un analizador/evaluador "real". Puedes escribir el tuyo o usar algo como commons-jexl. A menos que quiera permitir expresiones más complejas (y luego se verá obligado a seguir esta ruta), esto es demasiado exagerado.

5

La forma orientada a objetos para hacerlo sería usar una enumeración de las posibles operaciones. De esta forma, cada operación solo podría consumir un objeto en la 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); 

} 

Para llamar a dicho código, usted entonces hacer algo como:

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

Posteriormente, se podría crear un mapa de Cuerdas de Operaciones, así:

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

Por último, a use tal sistema:

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

Una ventaja de usar las enumeraciones de esta manera es que usted tiene el lujo de la comparación de las operaciones sobre los datos, si decide hacerlo

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

Además, se obtiene una ubicación centralizada para todas las operaciones, el único La desventaja de esto es que el archivo Operation.java puede agrandarse con un conjunto suficientemente grande de operadores.

Los únicos problemas que pueden existir a largo plazo es que, si bien dicho sistema es útil y fácil de leer y comprender, realmente no tiene en cuenta la prioridad. Suponiendo que su fórmula se evalúa en el orden de precedencia, tal problema no importa. Ejemplos de expresar la fórmula en el orden de precedencia se pueden encontrar en notación polaca inversa, notación polaca, etc.

Dónde precedencia que importa es cuando se le permite expresar artículos como:

4 + 5 * 2 

donde según según la convención típica, el 5 * 2 debe evaluarse antes del 4 + 5. La única forma correcta de manejar la precedencia es formar un árbol de evaluación en la memoria, o garantizar que todas las entradas manejen la precedencia de una manera simple y sin ambigüedades (Notación Polaca , Notación polaca inversa, etc.).

Supongo que conoce los problemas de precedencia, pero gracias por permitirme mencionarlo para el beneficio de aquellos que aún no han tenido que escribir ese código.

+1

+1, Aquí hay una variación de lo anterior donde no tiene que agregar entradas al mapa manualmente: http://paste.pocoo.org/show/221499/ – missingfaktor

+0

@Rahul Me gusta la forma en que mezcló el símbolo con la Operación, pero después de mezclar los dos, el nombre "Operador" parece un poco inapropiado. Dado que la Clase ahora contiene el símbolo y la operación, lo llamaría Evavador. Pero, "¿Qué hay en un nombre? Lo que llamamos una rosa con cualquier otro nombre podría oler tan dulce". - Shakespeare –

2

Una enumeración estaría limpia para este tipo de escenario.

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

Para llamar

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

Otra opción sería incluir un método estático que toma la representación de cadena del operador así como los operandos y tienen una única llamada al método, aunque prefiero los separan .

+0

Puede reemplazar 'EnumSet.allOf (Operator.class)' con 'values ​​()'. – missingfaktor

+0

¡Gracias! Actualizado – Robin

+1

'return null' está oliendo ... – whiskeysierra

1

También puede usar el código hash del operador y ponerlo en un switch aunque esto no es muy "elegante" desde la perspectiva de OOP.

Esto puede funcionar si no va a añadir demasiado a menudo los operadores (que no creo que lo haría)

así que esto debería ser suficiente:

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

Y tienen AND definido como:

private static final int ADD = 0x2b; 

Aquí está el código de ejemplo en ejecución 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)); 
    } 
} 

Y eso es todo. Funciona muy rápido (estoy bastante seguro de que hay una enumeración en orden)

Desde una perspectiva de POO, creo que la respuesta de Rahul sería suficiente, pero si quieres ponerte serio con esto, debes usar un analizador como sugiere Hank Gay. .

p.s. Conseguir el código hash de una cadena es fácil:

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

realidad utilicé esta:

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

y ejecutarlo con:

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

Podría hacerse más rápido al evitar las asignaciones de objetos innecesarios: http://paste.pocoo.org/show/221566/ – missingfaktor

+0

@Rahul Estoy de acuerdo, pero la diferencia es insignificante, en realidad y la ventaja de usar objetos es que pueden usar almacenados en una estructura de datos (por ejemplo, una pila) para su posterior evaluación. Aún bastante rápido. – OscarRyz

+0

No me importa el downvote, solo me gustaría saber el motivo. – OscarRyz

0

@Robin: en Java que pueda no tienen métodos abstractos en clases no abstractas, y los tipos de enumeración no pueden ser abstractos (porque todas las enum heredan de Enum, lo que significa que un tipo de enumeración no puede heredar de otro por las reglas de herencia de Java)

Además, lo que se puede hacer es añadir un método estático denominado registro:

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

y luego llamar a ese método en el constructor; y utiliza la búsqueda de mapas en el método getOperator().

+0

Su código es absolutamente correcto. ¿Intentó compilarlo antes de publicarlo? – missingfaktor

+0

Ahora veo. Lo siento por eso. Aún así, mi sugerencia de usar un mapa, como se muestra arriba, sigue en pie; especialmente si el número de operaciones definidas es grande o espera ejecutar muchas de estas comparaciones. – user268396

1

es muy fácil de hacer en 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} 
}; 

prueba:

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

impresiones 5.

Cuestiones relacionadas