恢宏  
复制代码

/**
* 计算字符串表达式的值, 不支持小数, 支持八进制(012)、十六进制(0x2a)、二进制(0b10)和十进制
* <ul>
* <li>加法('+')</li>
* <li>减法('-')</li>
* <li>乘法('*')</li>
* <li>除法, 保留两位小数('/')</li>
* <li>取余, 获取商('//')</li>
* <li>取余, 获取余数('%')</li>
* </ul>
* <pre>{@code
* var calculator = new Calculator();
* String value = calculator.eval("48*((70-65)-43)+8*1");
* // 结果: -1816
* }</pre>
*/
class Calculator {

record Token(String str, int begin) {
}

enum NumericPattern {
DEF("0|[1-9][0-9]*"),
HEX("0x[0-9a-f]+"),
OCT("0[0-7]+"),
BIN("0b[01]+");
private final Pattern pattern;

NumericPattern(String regex) {
this.pattern = Pattern.compile(regex);
}

boolean matches(String str) {
return pattern.matcher(str).matches();
}
}

public String eval(String expr) {
List<Token> tokens = getTokens(expr);
var value = expr(tokens);
if (!"eof".equals(tokens.getFirst().str)) {
tokens.removeLast();
String msg = "token没有被全部消耗: " + tokens.stream().map(Token::str).collect(Collectors.joining());
throw new IllegalArgumentException(msg);
}
return value.toString();
}

private List<Token> getTokens(String expr) {
List<Token> tokens = new LinkedList<>();
char[] chars = expr.toCharArray();
int i = 0;
while (i < chars.length) {
char ch = chars[i];
if (ch >= '0' && ch <= '9') {
int begin = i;
do {
i += 1;
} while (i < chars.length && (chars[i] >= '0' && chars[i] <= '9' || chars[i] >= 'a' && chars[i] <= 'z'));
String str = new String(chars, begin, i - begin);
tokens.add(new Token(str, begin));
} else if (ch == '+' || ch == '-' || ch == '*' || ch == '%' || ch == '(' || ch == ')') {
String str = String.valueOf(ch);
tokens.add(new Token(str, i));
i += 1;
} else if (ch == '/') {
if (i + 1 < chars.length && chars[i + 1] == '/') {
tokens.add(new Token("//", i));
i += 2;
} else {
tokens.add(new Token("/", i));
i += 1;
}
} else if (ch == ' ') {
i += 1;
} else {
String msg = String.format("非法字符'%c'(%d)", ch, i);
throw new IllegalArgumentException(msg);
}
}
tokens.add(new Token("eof", chars.length));
return tokens;
}

private BigDecimal expr(List<Token> tokens) {
// expr -> term | term '+' expr | term '-' expr
var value = term(tokens);

Token token = tokens.getFirst();
if ("+".equals(token.str)) {
tokens.removeFirst();
return value.add(expr(tokens));
}
if ("-".equals(token.str)) {
tokens.removeFirst();
return value.subtract(expr(tokens));
}

return value;
}

private BigDecimal term(List<Token> tokens) {
// term -> factor | factor '*' term | factor '/' term | factor '//' term | factor '%' term
var value = factor(tokens);

Token token = tokens.getFirst();
if ("*".equals(token.str)) {
tokens.removeFirst();
return value.multiply(term(tokens));
}
if ("/".equals(token.str)) {
tokens.removeFirst();
return value.divide(term(tokens), 2, RoundingMode.HALF_UP);
}
if ("//".equals(token.str)) {
tokens.removeFirst();
return value.divideAndRemainder(term(tokens))[0];
}
if ("%".equals(token.str)) {
tokens.removeFirst();
return value.divideAndRemainder(term(tokens))[1];
}

return value;
}

private BigDecimal factor(List<Token> tokens) {
// factor -> num | '-' num | '(' expr ')'
Token token = tokens.getFirst();

if ("-".equals(token.str)) {
tokens.removeFirst();
return num(tokens).negate();
}
if ("(".equals(token.str)) {
tokens.removeFirst();
var value = expr(tokens);

Token next = tokens.removeFirst();
if (!")".equals(next.str)) {
String msg = String.format("括号没有关闭 %s", token);
throw new IllegalArgumentException(msg);
}

return value;
}
return num(tokens);
}

private BigDecimal num(List<Token> tokens) {
Token token = tokens.removeFirst();
// 十进制的0
if (token.str.equals("0")) {
return BigDecimal.ZERO;
}
// 读取十六进制
if (token.str.startsWith("0x")) {
if (NumericPattern.HEX.matches(token.str)) {
return new BigDecimal(Long.parseLong(token.str.substring(2), 16));
}
throw new IllegalArgumentException("非法的十六进制数值: " + token);
}
// 读取二进制
if (token.str.startsWith("0b")) {
if (NumericPattern.BIN.matches(token.str)) {
return new BigDecimal(Long.parseLong(token.str.substring(2), 2));
}
throw new IllegalArgumentException("非法的二进制数值: " + token);
}
// 读取八进制
if (token.str.startsWith("0")) {
if (NumericPattern.OCT.matches(token.str)) {
return new BigDecimal(Long.parseLong(token.str.substring(1), 8));
}
throw new IllegalArgumentException("非法的八进制数值: " + token);
}
// 读取十进制
if (NumericPattern.DEF.matches(token.str)) {
return new BigDecimal(token.str);
}
throw new IllegalArgumentException("非法的十进制数值: " + token);
}
}
复制代码
posted on   恢宏  阅读(210)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
 
点击右上角即可分享
微信分享提示