@jacksontian/equation-resolver 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/nodejs.yml +31 -0
- package/README.md +114 -0
- package/bin/cli.js +64 -0
- package/eslint.config.js +63 -0
- package/lib/evaluator.js +308 -0
- package/lib/fraction.js +123 -0
- package/lib/index.js +6 -0
- package/lib/lexer.js +115 -0
- package/lib/parser.js +118 -0
- package/lib/semantic-checker.js +56 -0
- package/package.json +34 -0
- package/test/evaluator.test.js +230 -0
- package/test/fraction.test.js +435 -0
- package/test/lexer.test.js +418 -0
- package/test/parser.test.js +264 -0
- package/test/semantic-checker.test.js +45 -0
package/lib/lexer.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const TOKEN_TYPES = {
|
|
2
|
+
NUMBER: 'NUMBER',
|
|
3
|
+
VARIABLE: 'VARIABLE',
|
|
4
|
+
PLUS: 'PLUS', MINUS: 'MINUS', MULTIPLY: 'MULTIPLY', DIVIDE: 'DIVIDE',
|
|
5
|
+
LPAREN: 'LPAREN',
|
|
6
|
+
RPAREN: 'RPAREN',
|
|
7
|
+
EQUALS: 'EQUALS',
|
|
8
|
+
SEMICOLON: 'SEMICOLON',
|
|
9
|
+
EOF: 'EOF',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export class Token {
|
|
13
|
+
constructor(type, value) {
|
|
14
|
+
this.type = type;
|
|
15
|
+
this.value = value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// 词法分析器
|
|
20
|
+
export class Lexer {
|
|
21
|
+
constructor(input) {
|
|
22
|
+
this.input = input;
|
|
23
|
+
this.pos = 0;
|
|
24
|
+
this.currentChar = this.input[this.pos] || null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
advance() {
|
|
28
|
+
this.pos++;
|
|
29
|
+
this.currentChar = this.pos < this.input.length ? this.input[this.pos] : null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
skipWhitespace() {
|
|
33
|
+
while (this.currentChar === ' ') {
|
|
34
|
+
this.advance();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
number() {
|
|
39
|
+
let result = '';
|
|
40
|
+
while (this.currentChar && /[0-9.]/.test(this.currentChar)) {
|
|
41
|
+
result += this.currentChar;
|
|
42
|
+
this.advance();
|
|
43
|
+
}
|
|
44
|
+
return new Token(TOKEN_TYPES.NUMBER, result);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
identifier() {
|
|
48
|
+
// 只支持单字符变量
|
|
49
|
+
const result = this.currentChar;
|
|
50
|
+
this.advance();
|
|
51
|
+
return new Token(TOKEN_TYPES.VARIABLE, result);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getNextToken() {
|
|
55
|
+
while (this.currentChar !== null) {
|
|
56
|
+
if (this.currentChar === ' ') {
|
|
57
|
+
this.skipWhitespace();
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (/[0-9]/.test(this.currentChar)) {
|
|
62
|
+
return this.number();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (/[a-z]/i.test(this.currentChar)) {
|
|
66
|
+
return this.identifier();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (this.currentChar === '+') {
|
|
70
|
+
this.advance();
|
|
71
|
+
return new Token(TOKEN_TYPES.PLUS, '+');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (this.currentChar === '-') {
|
|
75
|
+
this.advance();
|
|
76
|
+
return new Token(TOKEN_TYPES.MINUS, '-');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (this.currentChar === '*') {
|
|
80
|
+
this.advance();
|
|
81
|
+
return new Token(TOKEN_TYPES.MULTIPLY, '*');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (this.currentChar === '/') {
|
|
85
|
+
this.advance();
|
|
86
|
+
return new Token(TOKEN_TYPES.DIVIDE, '/');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (this.currentChar === '(') {
|
|
90
|
+
this.advance();
|
|
91
|
+
return new Token(TOKEN_TYPES.LPAREN, '(');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (this.currentChar === ')') {
|
|
95
|
+
this.advance();
|
|
96
|
+
return new Token(TOKEN_TYPES.RPAREN, ')');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (this.currentChar === '=') {
|
|
100
|
+
this.advance();
|
|
101
|
+
return new Token(TOKEN_TYPES.EQUALS, '=');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (this.currentChar === ';') {
|
|
105
|
+
this.advance();
|
|
106
|
+
return new Token(TOKEN_TYPES.SEMICOLON, ';');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
throw new Error(`Unexpected character: ${this.currentChar}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return new Token(TOKEN_TYPES.EOF, null);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
package/lib/parser.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// 语法分析器
|
|
2
|
+
export class Parser {
|
|
3
|
+
constructor(lexer) {
|
|
4
|
+
this.lexer = lexer;
|
|
5
|
+
this.currentToken = this.lexer.getNextToken();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
eat(tokenType) {
|
|
9
|
+
if (this.currentToken.type === tokenType) {
|
|
10
|
+
this.currentToken = this.lexer.getNextToken();
|
|
11
|
+
} else {
|
|
12
|
+
throw new Error(`Expected ${tokenType},but got ${this.currentToken.type}`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 方程: expression = expression
|
|
17
|
+
equation() {
|
|
18
|
+
const left = this.expression();
|
|
19
|
+
this.eat('EQUALS');
|
|
20
|
+
const right = this.expression();
|
|
21
|
+
return { type: 'EQUATION', left, right };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// 表达式: term ((PLUS | MINUS) term)*
|
|
25
|
+
expression() {
|
|
26
|
+
let node = this.term();
|
|
27
|
+
|
|
28
|
+
while (this.currentToken.type === 'PLUS' || this.currentToken.type === 'MINUS') {
|
|
29
|
+
const op = this.currentToken.type === 'PLUS' ? '+' : '-';
|
|
30
|
+
this.eat(this.currentToken.type);
|
|
31
|
+
node = { type: 'BINARY_OP', op, left: node, right: this.term() };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return node;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 项: factor ((MULTIPLY | DIVIDE) factor)*
|
|
38
|
+
term() {
|
|
39
|
+
let node = this.factor();
|
|
40
|
+
|
|
41
|
+
while (this.currentToken.type === 'MULTIPLY' || this.currentToken.type === 'DIVIDE') {
|
|
42
|
+
const op = this.currentToken.type === 'MULTIPLY' ? '*' : '/';
|
|
43
|
+
this.eat(this.currentToken.type);
|
|
44
|
+
node = { type: 'BINARY_OP', op, left: node, right: this.factor() };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return node;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// 因子: NUMBER | VARIABLE | (expression) | unaryOp factor
|
|
51
|
+
factor() {
|
|
52
|
+
const token = this.currentToken;
|
|
53
|
+
let node;
|
|
54
|
+
|
|
55
|
+
if (token.type === 'NUMBER') {
|
|
56
|
+
this.eat('NUMBER');
|
|
57
|
+
node = { type: 'NUMBER', value: token.value };
|
|
58
|
+
|
|
59
|
+
// 处理隐式乘法: 2x, 2(, 2)
|
|
60
|
+
// 但要注意:如果是在除法后面(通过上下文判断),数字+变量应该作为一个整体
|
|
61
|
+
if (this.currentToken.type === 'VARIABLE' || this.currentToken.type === 'LPAREN') {
|
|
62
|
+
return { type: 'BINARY_OP', op: '*', left: node, right: this.factor() };
|
|
63
|
+
}
|
|
64
|
+
return node;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (token.type === 'VARIABLE') {
|
|
68
|
+
this.eat('VARIABLE');
|
|
69
|
+
node = { type: 'VARIABLE', name: token.value };
|
|
70
|
+
|
|
71
|
+
// 处理隐式乘法: xy, x(, x2
|
|
72
|
+
if (this.currentToken.type === 'VARIABLE' ||
|
|
73
|
+
this.currentToken.type === 'LPAREN' ||
|
|
74
|
+
this.currentToken.type === 'NUMBER') {
|
|
75
|
+
return { type: 'BINARY_OP', op: '*', left: node, right: this.factor() };
|
|
76
|
+
}
|
|
77
|
+
return node;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (token.type === 'LPAREN') {
|
|
81
|
+
this.eat('LPAREN');
|
|
82
|
+
node = this.expression();
|
|
83
|
+
this.eat('RPAREN');
|
|
84
|
+
|
|
85
|
+
// 处理隐式乘法: (expr)x, (expr)2, (expr)(
|
|
86
|
+
if (this.currentToken.type === 'VARIABLE' ||
|
|
87
|
+
this.currentToken.type === 'NUMBER' ||
|
|
88
|
+
this.currentToken.type === 'LPAREN') {
|
|
89
|
+
return { type: 'BINARY_OP', op: '*', left: node, right: this.factor() };
|
|
90
|
+
}
|
|
91
|
+
return node;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
throw new Error(`Unexpected token: ${token.type}, expected NUMBER, VARIABLE, LPAREN`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// 方程组: equation (SEMICOLON equation)*
|
|
98
|
+
equationList() {
|
|
99
|
+
const equations = [];
|
|
100
|
+
equations.push(this.equation());
|
|
101
|
+
|
|
102
|
+
while (this.currentToken.type === 'SEMICOLON') {
|
|
103
|
+
this.eat('SEMICOLON');
|
|
104
|
+
// 如果分号后是 EOF,说明分号是末尾的可选分号,忽略它
|
|
105
|
+
if (this.currentToken.type === 'EOF') {
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
equations.push(this.equation());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return { type: 'EQUATION_SYSTEM', equations };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
parse() {
|
|
115
|
+
return this.equationList();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// 语义检查器
|
|
2
|
+
export class SemanticChecker {
|
|
3
|
+
constructor(ast) {
|
|
4
|
+
this.ast = ast;
|
|
5
|
+
this.variables = new Set();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
visitEquationSystem(ast) {
|
|
9
|
+
if (ast.type !== 'EQUATION_SYSTEM') {
|
|
10
|
+
throw new Error('Invalid AST type');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
ast.equations.forEach(equation => {
|
|
14
|
+
this.visitEquation(equation);
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
visitEquation(ast) {
|
|
19
|
+
if (ast.type !== 'EQUATION') {
|
|
20
|
+
throw new Error('Invalid AST type');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const leftCtx = {
|
|
24
|
+
variables: new Set(),
|
|
25
|
+
};
|
|
26
|
+
this.visitExpression(ast.left, leftCtx);
|
|
27
|
+
const rightCtx = {
|
|
28
|
+
variables: new Set(),
|
|
29
|
+
};
|
|
30
|
+
this.visitExpression(ast.right, rightCtx);
|
|
31
|
+
// 检查是否没有变量
|
|
32
|
+
if (leftCtx.variables.size === 0 && rightCtx.variables.size === 0) {
|
|
33
|
+
throw new Error('Equation does not contain any variables');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
visitExpression(ast, ctx) {
|
|
38
|
+
if (ast.type === 'VARIABLE') {
|
|
39
|
+
ctx.variables.add(ast.name);
|
|
40
|
+
this.variables.add(ast.name);
|
|
41
|
+
} else if (ast.type === 'BINARY_OP') {
|
|
42
|
+
this.visitExpression(ast.left, ctx);
|
|
43
|
+
this.visitExpression(ast.right, ctx);
|
|
44
|
+
} else if (ast.type === 'NUMBER') {
|
|
45
|
+
// noop
|
|
46
|
+
} else {
|
|
47
|
+
console.log(ast);
|
|
48
|
+
throw new Error('Invalid AST type');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 执行所有语义检查
|
|
53
|
+
check() {
|
|
54
|
+
this.visitEquationSystem(this.ast);
|
|
55
|
+
}
|
|
56
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jacksontian/equation-resolver",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "解方程程序",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"solve": "./bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "node --test",
|
|
12
|
+
"test": "node --test test/*.test.js",
|
|
13
|
+
"test:cov": "c8 --reporter=html --reporter=text npm run test",
|
|
14
|
+
"dev:cov": "c8 --reporter=html --reporter=text npm run dev",
|
|
15
|
+
"lint": "eslint .",
|
|
16
|
+
"lint:fix": "eslint . --fix"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"equation",
|
|
20
|
+
"solver"
|
|
21
|
+
],
|
|
22
|
+
"author": "Jackson Tian",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/JacksonTian/equation-resolver.git"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/JacksonTian/equation-resolver",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@eslint/js": "^9.39.1",
|
|
31
|
+
"c8": "^8.0.1",
|
|
32
|
+
"eslint": "^9.39.1"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert';
|
|
3
|
+
import { Evaluator } from '../lib/evaluator.js';
|
|
4
|
+
import { Lexer } from '../lib/lexer.js';
|
|
5
|
+
import { Parser } from '../lib/parser.js';
|
|
6
|
+
import { SemanticChecker } from '../lib/semantic-checker.js';
|
|
7
|
+
|
|
8
|
+
function solve(input) {
|
|
9
|
+
const lexer = new Lexer(input);
|
|
10
|
+
const parser = new Parser(lexer);
|
|
11
|
+
const ast = parser.parse();
|
|
12
|
+
const checker = new SemanticChecker(ast);
|
|
13
|
+
checker.check();
|
|
14
|
+
const evaluator = new Evaluator(ast);
|
|
15
|
+
return evaluator.solve();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('solve', () => {
|
|
19
|
+
describe('basic linear equation', () => {
|
|
20
|
+
it('single variable simple multiplication should be solved', () => {
|
|
21
|
+
const result = solve('2x = 4');
|
|
22
|
+
assert.strictEqual(result.x, 2);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('addition', () => {
|
|
26
|
+
const result = solve('3x + 2 = 8');
|
|
27
|
+
assert.strictEqual(result.x, 2);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('subtraction', () => {
|
|
31
|
+
const result = solve('5x - 3 = 12');
|
|
32
|
+
assert.strictEqual(result.x, 3);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('subtraction and multiplication', () => {
|
|
36
|
+
const result = solve('2x - 1 = 5');
|
|
37
|
+
assert.strictEqual(result.x, 3);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('division related', () => {
|
|
42
|
+
it('implicit multiplication by division', () => {
|
|
43
|
+
const result = solve('8 / 2x = 2');
|
|
44
|
+
assert.strictEqual(result.x, 2);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('implicit multiplication by division 2', () => {
|
|
48
|
+
const result = solve('10 / 5x = 1');
|
|
49
|
+
assert.strictEqual(result.x, 2);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('implicit multiplication by division 3', () => {
|
|
53
|
+
const result = solve('12 / 3x = 2');
|
|
54
|
+
assert.strictEqual(result.x, 2);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('parentheses and implicit multiplication', () => {
|
|
59
|
+
it('parentheses and implicit multiplication', () => {
|
|
60
|
+
const result = solve('2(3y-4)=4y-7(4-y)');
|
|
61
|
+
assert.strictEqual(result.y, 4);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('explicit multiplication', () => {
|
|
65
|
+
const result = solve('2*(3y-4)=4y-7(4-y)');
|
|
66
|
+
assert.strictEqual(result.y, 4);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('parentheses expression', () => {
|
|
70
|
+
const result = solve('3(x+2) = 15');
|
|
71
|
+
assert.strictEqual(result.x, 3);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('parentheses subtraction', () => {
|
|
75
|
+
const result = solve('2(x-1) = 6');
|
|
76
|
+
assert.strictEqual(result.x, 4);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe('complex equations', () => {
|
|
81
|
+
it('complex fraction equation', () => {
|
|
82
|
+
const result = solve('(x+3.5)/0.9=15*(x+3.5)-125');
|
|
83
|
+
assert.strictEqual(result.x, 5.5);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('mixed expression', () => {
|
|
87
|
+
const result = solve('2x + 3(x-1) = 10');
|
|
88
|
+
assert.strictEqual(result.x, 2.6);
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('decimal', () => {
|
|
93
|
+
it('decimal coefficient', () => {
|
|
94
|
+
const result = solve('0.5x = 2');
|
|
95
|
+
assert.strictEqual(result.x, 4);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('decimal addition of constant', () => {
|
|
99
|
+
const result = solve('1.5x + 0.5 = 5');
|
|
100
|
+
assert.strictEqual(result.x, 3);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// describe('负数', () => {
|
|
105
|
+
// it('负变量', () => {
|
|
106
|
+
// const result = solve('-x = 5');
|
|
107
|
+
// assert.strictEqual(result.x, -5);
|
|
108
|
+
// });
|
|
109
|
+
|
|
110
|
+
// it('负数结果', () => {
|
|
111
|
+
// const result = solve('2x - 10 = -4');
|
|
112
|
+
// assert.strictEqual(result.x, 3);
|
|
113
|
+
// });
|
|
114
|
+
// });
|
|
115
|
+
|
|
116
|
+
describe('error cases', () => {
|
|
117
|
+
it('identity equation', () => {
|
|
118
|
+
assert.throws(
|
|
119
|
+
() => solve('2x = 2x'),
|
|
120
|
+
(error) => {
|
|
121
|
+
assert.strictEqual(error.message, 'Equation has infinite solutions');
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('contradictory equation', () => {
|
|
128
|
+
assert.throws(
|
|
129
|
+
() => solve('2x = 2x + 1'),
|
|
130
|
+
(error) => {
|
|
131
|
+
assert.strictEqual(error.message, 'Equation system has no solution');
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('binary linear equation system', () => {
|
|
139
|
+
it('simple binary linear equation system', () => {
|
|
140
|
+
const result = solve('x + y = 5; x - y = 1');
|
|
141
|
+
assert.strictEqual(result.x, 3);
|
|
142
|
+
assert.strictEqual(result.y, 2);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('binary linear equation system with coefficients', () => {
|
|
146
|
+
const result = solve('2x + 3y = 7; 3x - 2y = 4');
|
|
147
|
+
assert.strictEqual(result.x, 2);
|
|
148
|
+
assert.strictEqual(result.y, 1);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('binary linear equation system with negative numbers', () => {
|
|
152
|
+
const result = solve('x - y = 3; 2x + y = 0');
|
|
153
|
+
assert.strictEqual(result.x, 1);
|
|
154
|
+
assert.strictEqual(result.y, -2);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it('binary linear equation system with decimals', () => {
|
|
158
|
+
const result = solve('0.5x + y = 2; x - 0.5y = 3');
|
|
159
|
+
assert.strictEqual(result.x, 3.2);
|
|
160
|
+
assert.strictEqual(result.y, 0.4);
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
describe('three linear equation system', () => {
|
|
165
|
+
it('simple three linear equation system', () => {
|
|
166
|
+
const result = solve('x + y + z = 6; x - y + z = 2; x + y - z = 0');
|
|
167
|
+
assert.strictEqual(result.x, 1);
|
|
168
|
+
assert.strictEqual(result.y, 2);
|
|
169
|
+
assert.strictEqual(result.z, 3);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('three linear equation system with coefficients', () => {
|
|
173
|
+
const result = solve('2x + y - z = 8; x - 2y + 3z = 1; 3x + 2y + z = 9');
|
|
174
|
+
assert.strictEqual(result.x, 3.625);
|
|
175
|
+
assert.strictEqual(result.y, -0.375);
|
|
176
|
+
assert.strictEqual(result.z, -1.125);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe('equation system with parentheses', () => {
|
|
181
|
+
it('parentheses expression', () => {
|
|
182
|
+
const result = solve('2(x + y) = 6; x - y = 1');
|
|
183
|
+
assert.strictEqual(result.x, 2);
|
|
184
|
+
assert.strictEqual(result.y, 1);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('complex parentheses expression', () => {
|
|
188
|
+
const result = solve('3(x - 1) + 2y = 5; 2x + (y + 1) = 4');
|
|
189
|
+
assert.strictEqual(result.x, -2);
|
|
190
|
+
assert.strictEqual(result.y, 7);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe('different variable names', () => {
|
|
195
|
+
it('different variable names', () => {
|
|
196
|
+
const result = solve('a + b = 5; a - b = 1');
|
|
197
|
+
assert.strictEqual(result.a, 3);
|
|
198
|
+
assert.strictEqual(result.b, 2);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('mixed variable names', () => {
|
|
202
|
+
const result = solve('x + y = 5; x + z = 6; y + z = 7');
|
|
203
|
+
assert.strictEqual(result.x, 2);
|
|
204
|
+
assert.strictEqual(result.y, 3);
|
|
205
|
+
assert.strictEqual(result.z, 4);
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('equation system error cases', () => {
|
|
210
|
+
it('equation count is less than variable count', () => {
|
|
211
|
+
assert.throws(
|
|
212
|
+
() => solve('x + y = 5'),
|
|
213
|
+
(error) => {
|
|
214
|
+
assert.strictEqual(error.message, 'Equation count (1) is less than variable count (2)');
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('equation system has no solution', () => {
|
|
221
|
+
assert.throws(
|
|
222
|
+
() => solve('x + y = 5; x + y = 10'),
|
|
223
|
+
(error) => {
|
|
224
|
+
assert.strictEqual(error.message, 'Equation system has no solution');
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
});
|