typescript - 打字稿 - 评估算术表达式
问题描述
如何评估 Typescript 中的算术表达式?一个例子是'(3+5*(-3+-1))'。
eval(...) 是被禁止的。
运行时不接受建议的解决方案:
let input = '(3+5)';
let resultNumber = (new Function( 'return (' + input + ')'))();
错误是:
SyntaxError:新函数()处的令牌无效或意外
占用空间为 136kb(压缩)的 Math.js 在我评估简单表达式时太大了。它可以通过限制功能来定制。
那么,您是否有一个可以评估算术表达式的小型打字稿文件/服务?当然,一元减号/加号应该可以正常工作。
解决方案
感谢@Mathyn,我发现了一段由@Boann 创建的优秀Java 代码。我将它迁移到 Typescript,你有它。
您还可以在下面找到 Karma 测试代码,因此您可以看到可能的情况:+、-、(一元)、*、^、/、大括号、(值)、sin、cos、tan、sqrt 等。
如何使用它?在 Angular 中,您可以通过依赖注入来访问它。否则,您可以创建一个对象。您可以通过布尔值(此处为“true”)指定以整数形式获取结果。
arithmeticExpressionEvaluator.evaluate('10 + 2 * 6') // shows 22.0
arithmeticExpressionEvaluator.evaluate('10 + 2 * 6', true) // shows 22 (integer only)
完整的 Typescript 源代码是:
export class ArithmeticExpressionEvaluator {
static INVALID_NUMBER = -1234567.654;
str: string;
pos = -1;
ch: string;
evaluate(expression: string): number {
return this.evaluateAll(expression, false);
}
evaluateAll(expression: string, resultIsInteger: boolean): number {
this.str = expression;
pos = -1;
const outcome = this.parse();
if (resultIsInteger) {
return Math.round(outcome);
}
return outcome;
}
nextChar() {
this.ch = (++this.pos < this.str.length) ? this.str.charAt(this.pos) : null;
}
eat(charToEat: string): boolean {
while (this.ch === ' ') {
this.nextChar();
}
if (this.ch === charToEat) {
this.nextChar();
return true;
}
return false;
}
parse(): number {
this.nextChar();
const x = this.parseExpression();
if (this.pos < this.str.length) {
return ArithmeticExpressionEvaluator.INVALID_NUMBER;
}
return x;
}
parseExpression(): number {
let x = this.parseTerm();
for (; ; ) {
if (this.eat('+')) { // addition
x += this.parseTerm();
} else if (this.eat('-')) { // subtraction
x -= this.parseTerm();
} else {
return x;
}
}
}
parseTerm(): number {
let x = this.parseFactor();
for (; ;) {
if (this.eat('*')) { // multiplication
x *= this.parseFactor();
} else if (this.eat('/')) { // division
x /= this.parseFactor();
} else {
return x;
}
}
}
parseFactor(): number {
if (this.eat('+')) { // unary plus
return this.parseFactor();
}
if (this.eat('-')) { // unary minus
return -this.parseFactor();
}
let x;
const startPos = this.pos;
if (this.eat('(')) { // parentheses
x = this.parseExpression();
this.eat(')');
} else if ((this.ch >= '0' && this.ch <= '9') || this.ch === '.') { // numbers
while ((this.ch >= '0' && this.ch <= '9') || this.ch === '.') {
this.nextChar();
}
x = parseFloat(this.str.substring(startPos, this.pos));
} else if (this.ch >= 'a' && this.ch <= 'z') { // functions
while (this.ch >= 'a' && this.ch <= 'z') {
this.nextChar();
}
const func = this.str.substring(startPos, this.pos);
x = this.parseFactor();
if (func === 'sqrt') {
x = Math.sqrt(x);
} else if (func === 'sin') {
x = Math.sin(this.degreesToRadians(x));
} else if (func === 'cos') {
x = Math.cos(this.degreesToRadians(x));
} else if (func === 'tan') {
x = Math.tan(this.degreesToRadians(x));
} else {
return ArithmeticExpressionEvaluator.INVALID_NUMBER;
}
} else {
return ArithmeticExpressionEvaluator.INVALID_NUMBER;
}
if (this.eat('^')) { // exponentiation
x = Math.pow(x, this.parseFactor());
}
return x;
}
degreesToRadians(degrees: number): number {
const pi = Math.PI;
return degrees * (pi / 180);
}
}
Karma 测试代码为:
import {ArithmeticExpressionEvaluator} from './arithmetic-expression-evaluator.service';
describe('Arithmetic Expression Evaluation', () => {
let arithmeticExpressionEvaluator: ArithmeticExpressionEvaluator;
beforeEach(() => {
arithmeticExpressionEvaluator = new ArithmeticExpressionEvaluator();
});
it('Arithmetic Expression Evaluation - double result', () => {
expect(arithmeticExpressionEvaluator.evaluate('10 + 2 * 6')).toBe(22.0);
expect(arithmeticExpressionEvaluator.evaluate('100 * 2 + 12')).toBe(212.0);
expect(arithmeticExpressionEvaluator.evaluate('100 * 2 + -12')).toBe(188.0);
expect(arithmeticExpressionEvaluator.evaluate('100 * (2) + -12')).toBe(188.0);
expect(arithmeticExpressionEvaluator.evaluate('-100 * 2 + 12')).toBe(-188.0);
expect(arithmeticExpressionEvaluator.evaluate('100 * 2 ^ 12')).toBe(409600.0);
expect(arithmeticExpressionEvaluator.evaluate('100 * ( 2 + 12 )')).toBe(1400.0);
expect(arithmeticExpressionEvaluator.evaluate('(100) * (( 2 ) + (12) )')).toBe(1400.0);
expect(arithmeticExpressionEvaluator.evaluate('100 * ( 2 + 12 ) / 14')).toBe(100.0);
});
it('Arithmetic Expression Evaluation - integer result', () => {
expect(arithmeticExpressionEvaluator.evaluateAll('10 + 2 * 6', true)).toBe(22);
expect(arithmeticExpressionEvaluator.evaluateAll('100 * 2 + 12' , true)).toBe(212);
expect(arithmeticExpressionEvaluator.evaluateAll('100 * 2 + -12', true)).toBe(188);
expect(arithmeticExpressionEvaluator.evaluateAll('100 * (2) + -12', true)).toBe(188);
expect(arithmeticExpressionEvaluator.evaluateAll('-100 * 2 + 12' , true)).toBe(-188);
expect(arithmeticExpressionEvaluator.evaluateAll('100 * 2 ^ 12', true)).toBe(409600);
expect(arithmeticExpressionEvaluator.evaluateAll('100 * ( 2 + 12 )', true)).toBe(1400);
expect(arithmeticExpressionEvaluator.evaluateAll('(100) * (( 2 ) + (12) )', true)).toBe(1400);
expect(arithmeticExpressionEvaluator.evaluateAll('100 * ( 2 + 12 ) / 14', true)).toBe(100);
});
});
推荐阅读
- git - 如何计算上游和本地分支之间的落后/提前提交?
- nlp - 提取非结构化文本组以供以后的 NLP 使用?
- swift - SwiftUI 对齐底部
- xml - 使用 AppleScript 将 XML 转换为 CSV,需要帮助调试
- node.js - 为什么 webpack-dev-server 不能在 React 应用程序中代理 API 调用?返回 404
- android - 未找到适用于该设备的兼容 APK
- python - Django 多对多字段
- javascript - 如何从不同的按钮多次调用一个函数?
- javascript - dailogflow中数组中的最大元素
- android - Android Firebase Junit4 模拟数据库。如何模拟数据库孩子?