8

I'm trying to replace the common switch for arithmetical operations by BinaryOperator functional interface.

The base method is:

private static int computeOne(int res, String operand, String operation) {
    int number = Integer.parseInt(operand);

    switch (operation) {
        case "+":
            res += number;
            break;
        case "-":
            res -= number;
            break;
        case "*":
            res *= number;
            break;
        case "/":
            res = (number != 0 ? res / number : Integer.MAX_VALUE);
            break;
        default:
            res = 0;
            System.out.println("unknown operation");
    }

    return res;
}

As I understand it's nesessary to write something like:

BinaryOperator<Integer> action = (a,b) -> computeExpression(a + operation + b);
action.apply(res, operand);

But I don't understand how to avoid switch in computeExpression that would be the same as computeOne.

Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
  • I have to admit, that I don't understand the question, even if there is an accepted answer. computeExpression seems only vaguely related to ComputeOne, operand and operation are Strings for unclear reasons. What is the restriction (operation being a variable? Of type String?), what is the goal? Converting a String to Int doesn't seem to be the problem, so why is it that way? `a + operation + b` looks like it would result in "42*14". Why merge them, when you later need to split them? – user unknown Apr 07 '18 at 08:30
  • @userunknown, I think he meant `computeExpression(a, operation, b)` to make it similar to the signature of `computeOne` – Andrew Tobilko Apr 07 '18 at 08:58

2 Answers2

8

You can define a BinaryOperator<Integer> for each arithmetic operation:

// a = operand 1
// b = operand 2
(a, b) -> a * b;
(a, b) -> a + b;
(a, b) -> a / b;
(a, b) -> a - b;

Then you can apply one passing 2 arguments:

// result = operation.apply(a, b);
int result = ((BinaryOperator<Integer>) ((a, b) -> a * b)).apply(2, 2);

I would use an enum to enumerate these operations:

class Test {

    public static void main(String[] args) {
         System.out.println(computeOne(4, "2", "/"));  // 2
         System.out.println(computeOne(4, "2", "*"));  // 8
         System.out.println(computeOne(4, "2", "-"));  // 2
         System.out.println(computeOne(4, "2", "+"));  // 6
    }

    private static int computeOne(int res, String operand, String operation) {
        return Operation.getOperationBySymbol(operation)
                        .getBinaryOperator()
                        .apply(res, Integer.parseInt(operand));
    }

    private enum Operation {
        // operation = symbol, action
        MULTIPLICATION("*", (a, b) -> a * b),
        ADDITION("+", (a, b) -> a + b),
        SUBTRACTION("-", (a, b) -> a - b),
        DIVISION("/", (a, b) -> a / b);

        private final BinaryOperator<Integer> binaryOperator;
        private final String symbol;

        Operation(String symbol, BinaryOperator<Integer> binaryOperator) {
            this.symbol = symbol;
            this.binaryOperator = binaryOperator;
        }

        public BinaryOperator<Integer> getBinaryOperator() {
            return binaryOperator;
        }

        public String getSymbol() {
            return symbol;
        }

        public static Operation getOperationBySymbol(String symbol) {
            for (Operation operation : values()) {
                if (operation.getSymbol().equals(symbol)) {
                    return operation;
                }
            }

            throw new IllegalArgumentException("Unknown symbol: " + symbol);
        }
    }

}

You also can "simplify" it with a BiFunction<BinaryOperator<?>, Pair<?, ?>, ?>:

// BiFunction<Operator, Operands, Result>
// Operator = BinaryOperator<?>
// Operands = Pair<?, ?>
BiFunction<BinaryOperator<Integer>, Pair<Integer, Integer>, Integer> f = 
    (operator, operands) -> 
        operator.apply(operands.getKey(), operands.getValue());

f.apply((a, b) -> a + b, new Pair<>(2, 2)); // 4
Andrew Tobilko
  • 48,120
  • 14
  • 91
  • 142
3

The arithmetical operator cannot be a variable.
By using a functional interface or without as in your actual code, you would have the same constraint : transform the String operator to an arithmetical operator.

Besides, actually in computeOne() you accept an int and two Strings as parameter and you returns an int.
BinaryOperator<Integer> accepts two Integer and return an Integer.
So it it not compatible.
You would need a TriFunction but it doesn't exist.
Either create your own functional interface such as TriFunction<T,U,V,R> or reduce the number of parameter passed to your function.

Here is an example using an enum Operator combined with BiFunction that performs the same thing as your actual method.
Note that as the operator is now represented by the enum Operator responsible to execute the function, the function needs only two parameters now : the Integer and the String that you convert into an int.
So BiFunction<Integer, String, Integer> is fine.

public enum Operator{
    ADD("+", (a,b) -> a + Integer.parseInt(b)), 
    SUBSTRACT("-", (a,b) -> a - Integer.parseInt(b)),
    MULTIPLY("*", (a,b) -> a * Integer.parseInt(b)),
    //       ...
    DEFAULT("", (a,b) -> 0);

    public BiFunction<Integer, String, Integer> function;
    private String symbol;

    Operator(String symbol, BiFunction<Integer, String, Integer> function){
        this.symbol = symbol;
        this.function = function;
    }

    public int compute(int actualValue, String operand){
        return function.apply(actualValue, operand);
    }

    public static Operator of(String symbol) {
        for (Operator value : values()) {
            if (symbol.equals(value.symbol)) {
                return value;
            }
        }

        return Operator.DEFAULT;
    }

}

You could create so an operation such as :

int res = 10;
String operand = "15";
String symbol = "+";
res = Operator.of(symbol).compute(res, operand);
davidxxx
  • 125,838
  • 23
  • 214
  • 215