Skip to content

Commit b164ada

Browse files
committed
String Calculate
1 parent 5dbf768 commit b164ada

File tree

1 file changed

+272
-0
lines changed

1 file changed

+272
-0
lines changed

src/medium/StringCalculate.java

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
package medium;
2+
3+
import java.util.ArrayList;
4+
import java.util.Dictionary;
5+
import java.util.Hashtable;
6+
import java.util.Stack;
7+
import java.util.regex.Matcher;
8+
import java.util.regex.Pattern;
9+
10+
/**
11+
* Have the function StringCalculate(str)
12+
* take the str parameter being passed and evaluate
13+
* the mathematical expression within in.
14+
* The double asterisks (**) represent exponentiation.
15+
* ---
16+
* For example, if str were "(2+(3-1)*3)**3" the output should be 512.
17+
* Another example: if str is "(2-0)(6/2)" the output should be 6.
18+
* There can be parenthesis within the string, so you must evaluate
19+
* it properly according to the rules of arithmetic.
20+
* The string will contain the operators: +, -, /, *, (, ), and **.
21+
* If you have a string like this: #/#*# or #+#(#)/#, then evaluate from left to right.
22+
* So divide then multiply, and for the second one multiply, divide, then add.
23+
* The evaluations will be such that there will not be any decimal operations,
24+
* so you do not need to account for rounding.
25+
*/
26+
public class StringCalculate {
27+
28+
// evaluate the Postfix expression
29+
private static final Stack<String> posStack = new Stack<>();
30+
31+
/**
32+
* Cleaning, parsing and splitting the input string into the array of tokens.
33+
*
34+
* @param str input string
35+
* @return String array with parsed operators and operands
36+
*/
37+
private static String[] parseConsoleInput(String str) {
38+
// 1. convert to lowercase
39+
// 2. replace exponent symbol ** with ^
40+
// 3. replace (a)(b) with (a) * (b)
41+
// 4. replace a(b) with a * (b)
42+
// 5. split two bundled characters (but only when the second character is not a digit)
43+
// 6. split bundled command/operator and a digit. Excl. minus to keep negative numbers intact.
44+
// 7. remove multiple spaces
45+
// 8. trim (remove leading and trailing spaces)
46+
// 9. split to array of strings
47+
return str
48+
.toLowerCase()
49+
.replaceAll("(\\*\\*)", "^")
50+
.replaceAll("(\\)\\()", ")*(")
51+
.replaceAll("([0-9])(?=[(])", "$1 *")
52+
.replaceAll("([\\p{P}\\p{S}a-z0-9])(?=[\\p{P}\\p{S}a-z])", "$1 ")
53+
.replaceAll("([^0-9])(?=[0-9])", "$1 ")
54+
.replaceAll(" +", " ")
55+
.trim().split(" ");
56+
}
57+
58+
/**
59+
* Prints out a message to the console if the user input is invalid.
60+
*
61+
* @param op single element of the input string
62+
*/
63+
private static void printInputError(String op) {
64+
System.out.println("Unrecognised operator or operand: \"" + op + "\".");
65+
}
66+
67+
/**
68+
* Reduces two operands to a single result
69+
* by performing an arithmetical operation.
70+
*
71+
* @param a operand A
72+
* @param b operand B
73+
* @param operator denotes operation type (addition, substraction, division etc.)
74+
* @return result of the arithmetical operation
75+
* @throws ArithmeticException if divisor is 0
76+
*/
77+
public static long reduceOperands(long a, long b, String operator) {
78+
switch (operator) {
79+
case "+":
80+
return a + b;
81+
case "-":
82+
return a - b;
83+
case "*":
84+
return a * b;
85+
case "/":
86+
if (b == 0) {
87+
System.out.println("Divide by 0.");
88+
throw new ArithmeticException();
89+
}
90+
return a / b;
91+
case "^":
92+
return (long) Math.pow(a, b);
93+
default:
94+
return 0;
95+
}
96+
}
97+
98+
/**
99+
* Checks if the token is an operand (0-9).
100+
*
101+
* @param op a single token from the input string
102+
* @return true if the token is an operand, false if not
103+
*/
104+
private static boolean isOperand(String op) {
105+
Pattern pattern = Pattern.compile("^[\\d]|^-[\\d]");
106+
Matcher matcher = pattern.matcher(op);
107+
return matcher.find();
108+
}
109+
110+
/**
111+
* Checks if the token is an operator + - * / : ^ ( ) etc.
112+
*
113+
* @param op a single token from the input string
114+
* @return true if the token is an operator, false if not
115+
*/
116+
private static boolean isOperator(String op) {
117+
Pattern pattern = Pattern.compile("^[+\\-*/^%]");
118+
Matcher matcher = pattern.matcher(op);
119+
return matcher.find();
120+
}
121+
122+
/**
123+
* Converts the Infix expression to Postfix.
124+
*
125+
* @param tokens expression tokens that are already parsed and split
126+
* @return a postfix expression
127+
*/
128+
private static String[] convertToPostfix(String[] tokens) {
129+
Stack<String> infStack = new Stack<>();
130+
String terminating = "#";
131+
Dictionary<String, Integer> precedence = new Hashtable<>() {
132+
{
133+
put(terminating, 0);
134+
put("(", 0);
135+
put(")", 0);
136+
put("+", 1);
137+
put("-", 1);
138+
put("*", 2);
139+
put("/", 2);
140+
put("^", 3);
141+
}
142+
};
143+
ArrayList<String> output = new ArrayList<>();
144+
infStack.push(terminating);
145+
for (String token : tokens) {
146+
// token is an operand, add to output and move on
147+
if (isOperand(token)) {
148+
output.add(token);
149+
continue;
150+
}
151+
// left parenthesis, push it to stack and move on
152+
if (token.equals("(")) {
153+
infStack.push(token);
154+
continue;
155+
}
156+
// right parenthesis, keep popping until the left parenthesis is found
157+
if (token.equals(")")) {
158+
while (true) {
159+
String op = infStack.pop();
160+
if (op.equals("(")) {
161+
break;
162+
} else {
163+
output.add(op);
164+
}
165+
}
166+
continue;
167+
}
168+
// token is an operator
169+
if (isOperator(token)) {
170+
int cmp1 = precedence.get(token);
171+
while (true) {
172+
int cmp2 = precedence.get(infStack.peek());
173+
// operand has higher precedence than item on the top of stack
174+
if (cmp1 > cmp2) {
175+
infStack.push(token);
176+
break;
177+
} else {
178+
output.add(infStack.pop());
179+
}
180+
}
181+
}
182+
}
183+
// pop the remaining items until the terminating symbol is reached (complete)
184+
while (!infStack.empty() && !infStack.peek().equals(terminating)) {
185+
output.add(infStack.pop());
186+
}
187+
return output.toArray(new String[0]);
188+
}
189+
190+
/**
191+
* Takes two operands from stack and perform the operation with a provider operator.
192+
*
193+
* @param operator denotes operation type (addition, substraction, division etc.)
194+
* @return result of the evaluation
195+
*/
196+
private static String performArithOperation(String operator) {
197+
if (posStack.size() >= 2) {
198+
// Safe to evaluate
199+
String elementB = posStack.pop();
200+
String elementA = posStack.pop();
201+
long opB = Long.parseLong(elementB);
202+
long opA = Long.parseLong(elementA);
203+
long result = reduceOperands(opA, opB, operator);
204+
return Long.toString(result);
205+
} else {
206+
// Stack underflow since at least one element is null
207+
return null;
208+
}
209+
}
210+
211+
/**
212+
* Computes the entire expression in Reverse Polish Notation.
213+
*
214+
* @param tokens expression tokens that are already parsed and split to Array of Strings
215+
* @return result of the evaluation
216+
*/
217+
private static Long evaluateExpression(String[] tokens) {
218+
for (String token : tokens) {
219+
// token is an operand, push it to stack and move on
220+
if (isOperand(token)) {
221+
posStack.push(token);
222+
continue;
223+
}
224+
// token is an operator, evaluate
225+
if (isOperator(token)) {
226+
String result = performArithOperation(token);
227+
if (result != null) {
228+
posStack.push(result);
229+
}
230+
continue;
231+
}
232+
// token is illegal
233+
printInputError(token);
234+
}
235+
// all tokens have been processed
236+
if (posStack.isEmpty()) {
237+
return null;
238+
}
239+
return Long.parseLong(posStack.peek());
240+
}
241+
242+
/**
243+
* String Calculate function.
244+
*
245+
* @param expression input string to evaluate
246+
* @return a result as a string
247+
*/
248+
public static String stringCalculate(String expression) {
249+
String[] tokens = parseConsoleInput(expression);
250+
String[] postfix = convertToPostfix(tokens);
251+
Long result = evaluateExpression(postfix);
252+
return result == null ? "" : result.toString();
253+
}
254+
255+
/**
256+
* Entry point.
257+
*
258+
* @param args command line arguments
259+
*/
260+
public static void main(String[] args) {
261+
String expression1 = "7-4-1+8(3)/2";
262+
String result1 = stringCalculate(expression1);
263+
System.out.println(result1);
264+
String expression2 = "(2+2)*2";
265+
String result2 = stringCalculate(expression2);
266+
System.out.println(result2);
267+
String expression3 = "2+2*2";
268+
String result3 = stringCalculate(expression3);
269+
System.out.println(result3);
270+
}
271+
272+
}

0 commit comments

Comments
 (0)