java
71 lines · 9 steps
Evaluating math expressions with two stacks
A single-pass shunting-yard evaluator that respects operator precedence, parentheses, and right-associative exponentiation.
Explained by
highlit
1import java.util.ArrayDeque;
2import java.util.Deque;
3import java.util.Map;
4
5public final class ExpressionEvaluator {
6
7 private static final Map<Character, Integer> PRECEDENCE = Map.of(
8 '+', 1, '-', 1, '*', 2, '/', 2, '^', 3
9 );
10
11 public double evaluate(String expression) {
12 Deque<Double> values = new ArrayDeque<>();
13 Deque<Character> operators = new ArrayDeque<>();
14
15 for (int i = 0; i < expression.length(); i++) {
16 char c = expression.charAt(i);
17 if (Character.isWhitespace(c)) {
18 continue;
19 }
20 if (Character.isDigit(c) || c == '.') {
21 int start = i;
22 while (i < expression.length()
23 && (Character.isDigit(expression.charAt(i)) || expression.charAt(i) == '.')) {
24 i++;
25 }
26 values.push(Double.parseDouble(expression.substring(start, i)));
27 i--;
28 } else if (c == '(') {
29 operators.push(c);
30 } else if (c == ')') {
31 while (operators.peek() != '(') {
32 values.push(apply(operators.pop(), values.pop(), values.pop()));
33 }
34 operators.pop();
35 } else if (PRECEDENCE.containsKey(c)) {
36 while (!operators.isEmpty() && hasPrecedence(operators.peek(), c)) {
37 values.push(apply(operators.pop(), values.pop(), values.pop()));
38 }
39 operators.push(c);
40 } else {
41 throw new IllegalArgumentException("Unexpected character: " + c);
42 }
43 }
44
45 while (!operators.isEmpty()) {
46 values.push(apply(operators.pop(), values.pop(), values.pop()));
47 }
48 return values.pop();
49 }
50
51 private boolean hasPrecedence(char stackOp, char current) {
52 if (stackOp == '(') {
53 return false;
54 }
55 if (current == '^') {
56 return PRECEDENCE.get(stackOp) > PRECEDENCE.get(current);
57 }
58 return PRECEDENCE.get(stackOp) >= PRECEDENCE.get(current);
59 }
60
61 private double apply(char op, double b, double a) {
62 return switch (op) {
63 case '+' -> a + b;
64 case '-' -> a - b;
65 case '*' -> a * b;
66 case '/' -> a / b;
67 case '^' -> Math.pow(a, b);
68 default -> throw new IllegalStateException("Unknown operator: " + op);
69 };
70 }
71}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Two stacks — one for values, one for operators — let you evaluate infix expressions in a single left-to-right pass.
- 2Flushing higher-or-equal precedence operators before pushing a new one enforces correct evaluation order without building a tree.
- 3Treating exponentiation with strict '>' instead of '>=' cleanly encodes right-associativity.
Related explainers
ruby
class UserAgentParser BROWSERS = [ [/Edg\/([\d.]+)/, "Edge"], [/OPR\/([\d.]+)/, "Opera"],
Parsing user-agent strings in Ruby
regex
pattern-matching
lookup-tables
Intermediate
8 steps
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
javascript
function evaluate(expression) { const tokens = tokenize(expression); let pos = 0;
Building a recursive descent calculator
parsing
recursion
operator-precedence
Intermediate
8 steps
java
package com.acme.billing.config; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties;
Feature-flagged beans with Spring @ConditionalOnProperty
feature-flags
conditional-beans
strategy-pattern
Intermediate
5 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 steps
java
public static Map<String, String> parseCookieHeader(String header) { Map<String, String> cookies = new LinkedHashMap<>(); if (header == null || header.isBlank()) { return cookies;
Parsing an HTTP Cookie header in Java
string-parsing
http
url-decoding
Intermediate
6 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/evaluating-math-expressions-with-two-stacks-explained-java-8e76/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.