Самый элегантный способ применить оператор, найденный в виде строки в Java? - PullRequest
5 голосов
/ 02 июня 2010

Потенциально тупой: Предполагая, что у меня есть строка, содержащая оператор, как лучше всего применить этот оператор?

То, что я склонен делать, это:

if(n.getString(1).equals("<<")) {
  result = tmp1 << tmp2;
}

для каждого вида оператора, который у меня есть. Есть ли способ лучше ?

Ответы [ 10 ]

15 голосов
/ 02 июня 2010

Не уверен, что вы бы назвали это элегантным, но вот один из способов:

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

Тогда вы можете заменить ваш if оператор на:

result = operations.get(n.getString(1)).apply(tmp1, tmp2);
7 голосов
/ 02 июня 2010

Может сделать что-то вроде:

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

}

возвращение правильного будет действительно хорошим, как только операторы switch перейдут на String s.

Это решение выглядит следующим образом (без Operator., если вы используете статический импорт):

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

но я бы пошел с чужим широко протестированным и используемым парсером.

5 голосов
/ 02 июня 2010

Объектно-ориентированный способ сделать это - использовать перечисление возможных операций. Таким образом, каждая операция может использовать только один объект в памяти.

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

}

Чтобы вызвать такой код, вы должны сделать что-то вроде:

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

Тогда вы можете создать карту Строк для Операций, например так:

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

Наконец, для использования такой системы:

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

Одним из преимуществ использования перечислений таким образом является то, что у вас есть возможность сравнивать операции с данными, если вы решите это сделать

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

Кроме того, вы получаете централизованное расположение для всех операций, единственным недостатком которого является то, что файл Operation.java может стать большим с достаточно большим набором операторов.

Единственные проблемы, которые могут существовать в долгосрочной перспективе, это то, что, хотя такая система полезна и проста для чтения и понимания, она действительно не учитывает приоритет. Предполагая, что ваша формула оценивается в порядке приоритета, такая проблема не имеет значения. Примеры выражения формулы в порядке приоритета можно найти в обратной польской записи, польской записи и т. Д.

Приоритет имеет значение, когда вам разрешено выражать такие предметы, как:

4 + 5 * 2

, где в соответствии с типичным соглашением, 5 * 2 следует оценивать до 4 + 5. Единственный правильный способ обработки приоритета - это создать дерево оценки в памяти или гарантировать, что все входные данные обрабатывают приоритет в простом, однозначная манера (польская запись, обратная польская запись и т. д.).

Я предполагаю, что вы знаете о проблемах предшествования, но спасибо, что позволили мне упомянуть об этом для тех, кому еще не приходилось писать такой код.

3 голосов
/ 02 июня 2010

Использование фактического парсера, вероятно, будет более надежным, хотя это может быть излишним в зависимости от сложности вашей проблемы. JavaCC и ANTLR , вероятно, два самых популярных генератора синтаксических анализаторов в мире Java.

Если генератор синтаксического анализа является избыточным (возможно, из-за того, что у вас нет / вы не хотите создавать формальную грамматику), вы, вероятно, могли бы использовать Ragel для реализации пользовательского анализатора.

2 голосов
/ 02 июня 2010

Перечень будет чист для этого типа сценария.

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

Для звонка

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

Другим вариантом может быть включение статического метода, который принимает строковое представление оператора, а также операндов и имеет один вызов метода, хотя я предпочитаю, чтобы они были отдельными.

2 голосов
/ 02 июня 2010

Если у вас нет большого набора операторов или вы хотите иметь возможность использовать более сложные выражения в будущем, нет.

Один из способов сделать это (псевдокод, если бы у Java был такой синтаксис, это было бы хорошим вариантом даже с небольшим набором операторов):

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

[...]

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

Вы можете написать это на Java, но из-за отсутствия кратких литералов Map и объявлений анонимных классов, это намного более многословно.

Другой вариант, опубликованный Хэнком Гаем, - использовать «настоящий» анализатор / оценщик. Вы можете написать свой собственный или использовать что-то вроде commons-jexl. Если вы не хотите учесть более сложные выражения (и тогда вам придется идти по этому пути), это серьезно излишне.

1 голос
/ 08 сентября 2010

это очень легко сделать с диезом.

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

тест:

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

отпечатков 5.

1 голос
/ 03 июня 2010

Вы также можете использовать хэш-код оператора и поставить его на switch, хотя это не очень "элегантно" с точки зрения ООП.

Это может сработать, если вы не слишком часто добавляете операторов (что, я думаю, не будет)

Так что этого должно быть достаточно:

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

ИAND определен как:

private static final int ADD = 0x2b;

Вот полный пример кода:

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

И все.Работает очень-очень быстро (я уверен, что перечисление по порядку)

С точки зрения ООП, я думаю, что ответ Рахула подойдет, но если вы хотите серьезно относиться к этому, вы должны использовать парсеры как ХэнкГей предлагаю.

ps Получить хеш-код строки очень просто:

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

Я действительно использовал это:

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

И запустить его с:

java HexHashCode + - "*" / "<<" ">>" "%" "<" ">" "==" "!=" "&" "|"  "^" 
1 голос
/ 02 июня 2010

Вы можете использовать интерпретирующий язык JSR-223, такой как BeanShell, заставить его выполнить операцию, а затем получить результат обратно.

0 голосов
/ 03 июня 2010

@ Робин: в Java у вас не может быть абстрактных методов в неабстрактных классах, а типы enum не могут быть абстрактными (поскольку все enum наследуются от Enum, что означает, что один тип enum не может наследоваться от другого по правилам наследования Java) 1001 *

Кроме того, вы можете добавить статический метод с именем register:

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

И затем вызвать этот метод в конструкторе; и используйте поиск по карте в методе getOperator ().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...