Simple RPN (Reverse Polish Notation) Library for Java.
It is based on Dijkstra Algorithm. (https://en.wikipedia.org/wiki/Reverse_Polish_notation)
Couple years ago I read Joshua Bloch's "Java. Effective Programming". I wanted to practice what I've learned. I didn't want to create another CRUD like application, so I found that Dijkstra's algorithm would be good to learn design patterns, and effective programming. First version's were available on Sourceforge. Couple years later I manage to publish my library on maven cetral repo. Over the years I built a small ecosystem around this library. Feel free to check my other projects that use this one.
+,-,*,/ with (), power(^) Sin, cos, tg, ctg, min, max, fib
example:
Calculator calc = Calculator.createCalulator();
BigDecimal result = calc.calculate("2^3*(12/6)+18/3+5.0/2");
BigDecimal result2 = calc.calculate("3.678^2");
BigDecimal resultSin = calc.calculate("sin(2)");
BigDecimal resultSin2 = calc.calculate("sin(1+1)");
BigDecimal resultCtg = calc.calculate("ctg(-1.65091)");
BigDecimal min = calc.calculate("min(10, 8) + 10");
<dependency>
<groupId>com.github.bartlomiej-gora</groupId>
<artifactId>RPNLibrary</artifactId>
<version>5.1.0</version>
</dependency>
- Removed Tests in Kotlin.
- Moved to Java 17 (var, and Map.of)
- Changed Interfaces names from RPNChecking -> RPNChecker, and RPNExecuting -> RPNExecutioner
- Added new Factories for RPNChecker, and RPNExecutioner.
If you want to customize the calculator by adding your own operators and functions to existing one, you can use those - Factories. Below there is an example of adding % (moduli) operator to the calculator.
var rpnChecker = RPNCheckerFactory.createRPNCheckerWithDefaults(Map.of("%", 1), Map.of());
AbstractOperatorStrategy modulo = new AbstractOperatorStrategy("%") {
@Override
public BigDecimal execute(final String first, final String second, final MathContext mathContext) {
var firstDec = new BigDecimal(first, mathContext);
var secondDec = new BigDecimal(second, mathContext);
return firstDec.remainder(secondDec);
}
};
var rpnExecutioner = RPNExecutionerFactory.createRPNExecutionerWithDefaults(Map.of("%", modulo), Map.of());
var calc = Calculator.createCalculator(rpnChecker,rpnExecutioner, MathContext.DECIMAL64, 2);
BigDecimal result = calc.calculate("4%17");
assertThat(result).isEqualTo(new BigDecimal("4.00"));
- Moved to java 8
- Refactoring, split Calculator class into smaller pieces, using java 8 functional interfaces
- Added tests written in Kotest:
example:
class RPNFactoryTest : FreeSpec({
val tested = RPNFactory(RPNChecker(DefaultStrategyProvider()))
"should Return RPN" - {
val text = "( 2 + 3 ) * 5"
val result = tested.apply(text)
result shouldBe "2 3 + 5 *"
}
"should Return RPN for Function call" - {
val text = "sin ( 1 )"
val result = tested.apply(text)
result shouldBe "1 sin"
}
"should Return RPN for Function and equation" - {
val text = "sin ( 1 ) + 27 * 8"
val result = tested.apply(text)
result shouldBe "1 sin 27 8 * +"
}
"should Return RPN for two Functions call" - {
val text = "sin ( 1 ) + ctg ( 0 )"
val result = tested.apply(text)
result shouldBe "1 sin 0 ctg +"
}
})
- Using big Decimal Math library (https://github.com/eobermuhlner/big-math)
- General refactoring.
- Added Javadoc
- Changed internal calculation from BigDecimal to Double in DefaultCalculator implementation
- Fixed bug in divide operator, that caused: ex: "10/4 = 2, and not 2.5", "5/2 = 2, and not 2.5"
- Changed RoundinMode from HALF_UP, to HALF_EVEN
- Changed internal calculation type from BigDecimal to Double
- Fixed bug in divide operator, that caused: ex: "10/4 = 2, and not 2.5", "5/2 = 2, and not 2.5"
- Changed RoundinMode from HALF_UP, to HALF_EVEN
- Changed internal calculation type from BigDecimal to Double
IMPORTANT:
Changed package names from
pl.bgora.rpn
to
com.github.bgora.rpnlibrary
Fixed bug, that prevented from exucuting functions with multiple parameters.
New functions:
max() - takes two parameters, returns greater one
min() - take two parameters, returns less one
fib() - Fibonacci number
Refactor:
Changed createCalulator, and getDefaultEngine to use CalculationEngine interface
/**
* Creates AdvanceCalculator with given CalculatorEngine
*
* @param engine CalculationEngine implementation
* @return AdvanceCalculator
*/
public CalculatorInterface createCalulator(CalculationEngine engine) {
return new AdvancedCalculator(RoundingMode.HALF_UP, engine);
}
/**
* Return default CalculationEngine implementation
*
* @return CalculatorEngine
*/
public CalculationEngine getDefaultEngine() {
return new CalculatorEngine(StrategiesUtil.DEFAULT_OPERATORS, StrategiesUtil.DEFAULT_FUNCTIONS);
}
- Added package pl.bgora.rpn.advanced
- Added AdvancedCalculatorFactory
The advanced Calculator works with CalculationEngine, which uses strategy pattern to run.
please see:
pl.bgora.rpn.AbstractOperatorStrategy
pl.bgora.rpn.AbstractFunctionStrategy
AdvancedCalculatorFactory advancedCalculatorFactory = new AdvancedCalculatorFactory();
calc = advancedCalculatorFactory.createCalulator();
Assume that you want to add a function sqrt(number), which will return The square root , You will have to extend AbstractFunctionStrategy like this:
public class SqrtFunctionStrategy extends AbstractFunctionStrategy {
public SqrtFunctionStrategy() {
super("sqrt", 1, RoundingMode.HALF_EVEN);
}
@Override
public BigDecimal execute(String... params) {
return java.math.BigDecimal.valueOf(Math.sqrt(x));
}
}
And then you can add your function like that:
CalculatorInterface calc;
AdvancedCalculatorFactory advancedCalculatorFactory = new AdvancedCalculatorFactory();
CalculatorEngine engine = advancedCalculatorFactory.getDefaultEngine();
engine.addFunctionStartegy(new SqrtFunctionStrategy());
calc = advancedCalculatorFactory.createCalulator(engine);
Assume that you want to add a function max(number, number), which will return greater value, You will have to extend AbstractFunctionStrategy like this:
public class MaxFunctionStrategy extends AbstractFunctionStrategy {
public MaxFunctionStrategy() {
super("max", 2, RoundingMode.HALF_EVEN);
}
@Override
public BigDecimal execute(String... params) {
String first = params[0];
String second = params[1];
BigDecimal result = //do you calculation here
return result; //result;
}
}
And then you can add your function like that:
CalculatorInterface calc;
AdvancedCalculatorFactory advancedCalculatorFactory = new AdvancedCalculatorFactory();
CalculatorEngine engine = advancedCalculatorFactory.getDefaultEngine();
engine.addFunctionStartegy(new MaxFunctionStrategy());
calc = advancedCalculatorFactory.createCalulator(engine);