Build a Calculator Oops
This is a medium difficulty problem in Leetcode. This is medium only if you are talking in terms of algorithm and fourth dimension complexity. Just let's say you lot have to build a estimator for an enterprise. The calculator that will be extensible, maintainable and optimized at the same time.
How would yous build such a calculator?
Well the outset matter would nevertheless be coming up with the right logic to solve the infix expressions entered by the user. But this is just a function. You lot would want your calculator to be extensible. Maybe you would desire to add together more than and more operations to it. Too, you would want multiple developers to work on different operations at the same time so that you lot can reduce the overall time to market.
Now, that's a tough 1, isn't it?
In this article, I would attempt to build this calculator in a similar manner using the Object oriented and SOLID design principles.
If you lot are not aware about the SOLID principles then follow the link.
- How to kickoff thinking in OOPs with SOLID Principles
UML Design
I created a loftier-level UML design of the calculator. Just take a look at it so we tin can go over it briefly.
The thought process backside this blueprint is as follows:
- Operator: This is the interface that contains any behaviour associated with the operator. For example – BODMAS. This dominion applies when nosotros talk virtually operators. It could exist an arithmetic operator or group operators. Therefore, the method precedence should get the precedence of the operator.
- Arithematic Operator: This interface is used to provide the abstraction for all the arithmetics operations (ex – add-on, subtraction, multiplication, division).
- Basic Computer: This figurer is the master calculator that the user interacts with. You lot can call back of it as an orchestrator. Information technology will amass Convertor engine and evaluate engine. For now, I've used InfixToPostFixConvertor as Convertor engine and ReversePolishNotation as the calculation engine.
With this our bones UML pattern is set up and we take something to proceed with. This is notwithstanding not concrete and we might add, change or remove some of the attributes or classes subsequently on (if required).
How to proceed with the development?
I always adopt to lawmaking in the TDD style. Information technology means I write the test case first then write enough code to make that test pass and motion ahead one step at a fourth dimension.
But even before that we have to make a decision whether we want to offset coding from top-to-bottom or bottom-to-upwards.
Meridian to bottom typical means looking at the application from the hawkeye centre view and then move downwards the path. In our case it would exist writing tests for the BasicCalculator first. And whenever we identify the new component, nosotros will terminate the BasicCalculator and move on to writing test cases for that new component.
Whereas in the bottom-upwards approach, nosotros start past edifice the basic building blocks like Add-on class, so subtraction class and and then a design starts to emerge. In one case we run into the common pattern we excerpt an interface out-of-information technology. However, information technology sounds easy and straightforward but ordinarily, I find it to be tough. Going this path ways that yous already know what you want from the organization and you directly kickoff edifice the smaller components.
For this BasicCalculator, I would like to proceed with the Top-Downwardly approach.
It serves me well and helps me align my thoughts meliorate.
Basic Calculator
This is going to exist the starting point for the application. I'll take a method calculate that volition have the infix expression. This is the same expression that we are used to. For ex – 50 + (50 / 10) - 10.
But to fix a little bit of context for the code that you are about to read.
Yous will observe two classes in use:
- InfixToPostFixConverter
- ReversePolishNotation (Postfix Expression Evaluator)
At present, why would I want to convert an infix expression to postfix?
Well, there is a very expert reason, and that is Postfix doesn't crave any operator-driven gild of operations; information technology'south always explicit. And then for a stack-based compiler, it's very easy to implement.
For humans it would make sense to say something like "Add together A to B" rather than saying "A and B: Add Them". Just eh, I would chose postfix whatsoever time over infix.
And Opposite Polish Annotation is only the PostFix Expression Evaluator that you volition find below.
import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class BasicCalculator { private terminal InfixToPostfixConverter infixToPostfixConverter; private terminal ReversePolishNotation reversePolishNotation; public int calculate(String infixExpression) { String postfix = infixToPostfixConverter.convert(infixExpression); return reversePolishNotation.eval(postfix.split up(" ")); } } Infix To PostFix Converter
I've shared the entire lawmaking for the same.
At first information technology might look daunting but simply keep calm and read through the comments. The code is really simple.
And if you lot practice non understand something so please do annotate below, I volition make certain to explain that office in detail.
import java.util.LinkedList; import coffee.util.List; import java.util.Map; public grade InfixToPostfixConverter { private static final Map<Cord, Operator> OPERATORS = Map.of( "/", new Sectionalization(), "*", new Multiplication(), "+", new Addition(), "-", new Subtraction() ); private static terminal Map<Cord, GroupingOperator> GROUPING_OPERATORS = Map.of( "(", new OpenParenthesis(), ")", new CloseParenthesis() ); public static concluding String Space = " "; private final ExpressionParser expressionParser; public InfixToPostfixConverter(ExpressionParser expressionParser) { this.expressionParser = expressionParser; } /** * @param infixExpression * @return */ public String convert(String infixExpression) { List<String> tokens = expressionParser.parseInfix(infixExpression); var output = new StringBuilder(); var stack = new LinkedList<Operator>(); for (Cord token : tokens) { // if electric current token is an arithmetic operator if (OPERATORS.containsKey(token)) { Operator curr = OPERATORS.get(token); // if the precedence of the current token is less than the acme of the stack // and so pop from the stack and append it to the output // then push button the electric current token back into the stack while (!stack.isEmpty() && less(curr, stack.peekFirst())) output.append(stack.pop()).suspend(SPACE); stack.push(curr); } // if the electric current token is a grouping operator else if (GROUPING_OPERATORS.containsKey(token)) { GroupingOperator currOp = GROUPING_OPERATORS.get(token); if (currOp instanceof OpenParenthesis) stack.push(currOp); // if the electric current token is a endmost parenthesis ')' // and then popular all the operators from the stack till you reach the opening parenthesis '(' // and add them to the output expression else if (currOp instanceof CloseParenthesis) { Operator curr; while (! ((curr = stack.pop()) instanceof OpenParenthesis)) output.append(curr).append(SPACE); } } // if current token is an operand else output.append(token).append(Infinite); } // add remaining operators from stack to the output postfix expression while (!stack.isEmpty()) output.append(stack.pop()).append(Space); return output.toString().trim(); } private boolean less(Operator op1, Operator op2) { render op1.precedence().compareTo(op2.precedence()) <= 0; } } Reverse Smoothen Notation (PostFix)
This is where the actual calculation takes place. The postfix expression is evaluated and the ability of Object Oriented design kicks in.
Now, let's say you accept to add further operations. For example, you lot now want to add the powers operation to your calculator. All you accept to do is create a new form (say PowerOfNumber) and suspend information technology to the arithmeticOperations map.
Well, that's all yous have to do. No if-else ladder, not complex logics to perform. And the lawmaking is perfectly modular.
And let's say yous desire to remove some operation in the future, then merely remove it from your map. That operation won't be candy.
import java.util.*; public grade ReversePolishNotation { public Integer eval(final Cord[] notation) { var arithmeticOperations = Map.of( "/", new Division(), "*", new Multiplication(), "+", new Addition(), "-", new Subtraction() ); var operands = new LinkedList<Operand<Integer>>(); Arrays.stream(notation).forEach(token -> { if (isANumber(token)) operands.push(new IntegerOperand(token)); else { var b = operands.pop(); var a = operands.popular(); operands.button(new IntegerOperand(arithmeticOperations.get(token).eval(a, b))); } }); return operands.popular().get(); } private boolean isANumber(Cord token) { if (token.equals("-")) return false; return token.chars().filter(c -> c != '-').allMatch(Grapheme::isDigit); } } I will requite you one instance operation for the addition and similarly you lot can code the rest for yourself. This will help y'all understand the structure and the menstruation of the awarding.
Addition Functioning
Here is the Improver grade that performs the addition of two numbers.
course Addition implements ArithmeticOperator { @Override public Integer precedence() { render 1; } @Override public Integer eval(Operand<Integer> a, Operand<Integer> b) { return a.become() + b.get(); } @Override public String toString() { return "+"; } } Just like the Addition grade you tin can lawmaking Subtraction, Multiplication and Division.
I would strongly recommend you to code those classes by yourself to learn and understand the overall menses and what'southward happening in the background.
Showtime
Here'south the exam case that I wrote for the BasicCalculator. I will execute the test case and we will see if we get the correct output.
class BasicCalculatorTest { @Examination void itShouldEvaluateTheGivenExpressionAndReturnTheResult() { String infixExpression = "(ane+(iv+five+ii)-3)+(6+8)"; var expressionParser = new ExpressionParser(); var infixToPostfixConverter = new InfixToPostfixConverter(expressionParser); var reversePolishNotation = new ReversePolishNotation(); var estimator = new BasicCalculator(infixToPostfixConverter, reversePolishNotation); int outcome = figurer.calculate(infixExpression); assertEquals(23, result); } } Here'southward the result of the test case,
Determination
I chose this particular example to explain the power of object oriented programming and SOLID design principles because it is a perfect combination of complex parsing logic, infix to postfix conversion, expression evaluation and operand treatment.
There is a lot of things going on in this small application. And I also detect it a good mini-project to showcase your object-oriented skills with the proper implication of SOLID design principles.
If you are not aware about the SOLID pattern principles then do read the following article:
- SOLID Pattern Principles
And if I would have washed information technology in a procedure-oriented mode then y'all would find a bunch of small functions lying around the space which will be getting chosen from all over the places. There would definitely exist a lot many branches in the code which would go far difficult to read.
Source: https://www.bemyaficionado.com/basic-calculator-leetcode-problem-using-object-oriented-programming-in-java/
0 Response to "Build a Calculator Oops"
Post a Comment