Skip to content

Commit

Permalink
feat: replace math.js with self developed algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
hhow09 committed Jan 22, 2025
1 parent 1cb3f49 commit 7f2c5bc
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 153 deletions.
7 changes: 7 additions & 0 deletions backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,10 @@ npm run start
```bash
npm run test
```

## Limitations on calculation
- All whitespace is ignored, therefore `1 + 2 3` will consider as `1 + 23`
- negative sign in expression is not supported: e.g. `5 * -3` will return error.
- large number will be converted to exponential notation: e.g. `999999999999 * 999999999999` will return `9.99999999998e+23`
- default threshold of exponent is `20`
- ref: https://mikemcl.github.io/decimal.js/#precision
1 change: 1 addition & 0 deletions backend/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
transform: {
"^.+.tsx?$": ["ts-jest",{}],
},
testMatch: ['**/*.spec.ts'],
setupFiles: ["dotenv/config"]
};

102 changes: 1 addition & 101 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
"@types/body-parser": "^1.19.5",
"@types/express": "^5.0.0",
"@types/node": "^22.10.7",
"decimal.js": "^10.4.3",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"mathjs": "^14.0.1",
"mongodb": "^6.12.0",
"pino": "^9.6.0",
"socket.io": "^4.8.1"
Expand Down
32 changes: 3 additions & 29 deletions backend/src/command-service.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { Logger } from "pino";
import { evaluate as evaluateMathjs } from 'mathjs';
import { CommandAndResult } from "./entities/command-result.entity";
import { IRepository } from "./repositories";
import { isValidCommand, evaluate } from "./math/evaluate";

export interface ICommandService {
evaluateAndSave(clientId: string, expression: string): Promise<string>;
getHistory(clientId: string): Promise<CommandAndResult[]>;
}

class CommandService implements ICommandService {
private operators = new Set(['+', '-', '*', '/']);
private repository: IRepository;
private logger: Logger;
constructor(logger: Logger, repository: IRepository) {
Expand All @@ -31,35 +30,10 @@ class CommandService implements ICommandService {

// evaluate evaluate a mathematical expression
private evaluate(expression: string): string {
if (!this.isValidCommand(expression)) {
if (!isValidCommand(expression)) {
throw new Error('Invalid command');
}
return evaluateMathjs(expression).toString();
}

// isValidCommand checks if the command is valid
private isValidCommand(s: string): boolean {
s = s.replace(/ /g,''); // remove all spaces
if (s.length === 0) {
return false;
}
// allowed characters: 0-9, +, -, *, /, .
const regex = /^[\d+\-*/.]+$/;
if (!regex.test(s)) {
return false;
}
// operators cannot be adjacent to each other
for (let i = 0; i < s.length - 1; i++) {
if (this.operators.has(s[i]) && this.operators.has(s[i + 1])) {
return false;
}
}

// operators cannot be at the beginning or end of the string
if (this.operators.has(s[0]) || this.operators.has(s[s.length - 1])) {
return false;
}
return true;
return evaluate(expression);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,4 @@
import CommandService from './command-service';
import { pino } from 'pino';

const mockRepository = {
saveCommand: jest.fn(),
getLatest: jest.fn(),
};
import { evaluate } from './evaluate';

type TestCase = {
command: string;
Expand All @@ -13,39 +7,51 @@ type TestCase = {
};

describe('evaluate', () => {
const logger = pino();
describe.each<TestCase>([
{ command: '', hasError: true },
{ command: ' ', hasError: true },
{ command: '1', expected: '1' },
{ command: '1+', hasError: true },
{ command: '1 */ 2', hasError: true },
{ command: '2 + + 1', hasError: true },

{ command: '1 + 1', expected: '2' },
{ command: '123 * 23 + 45 - 6', expected: '2868' },
{ command: '10 * 5 / 2', expected: '25' },
{ command: '10.5 * 5 / 2.5', expected: '21' },

{ command: '1a2b3c', hasError: true },
{ command: '1 + 2 = 3', hasError: true },
{ command: '(123 * 23) + 45 - 6', hasError: true },

// // does not support negative numbers in expression
{ command: '5 * -3', hasError: true },

// // basic
{ command: '1', expected: '1' },
{ command: '1 + 1', expected: '2' },
{ command: '123 * 23 + 45 - 6', expected: '2868' },
{ command: '1 + 259 - 68*77', expected: '-4976' },
{ command: '10 * 5 / 2', expected: '25' },
{ command: '10.54 * 7 / 3', expected: '24.593333333333334' },
{ command: '10.5 * 5 / 2.5', expected: '21' },
{ command: '.5 + 1', expected: '1.5' },


// Zero cases
{ command: '0 * 5', expected: '0' },
{ command: '0 / 1', expected: '0' },
{ command: '1 / 0', expected: 'Infinity' },
{ command: '0 / 0', expected: 'NaN' },

// decimal default precision is 20.
// ref: https://mikemcl.github.io/decimal.js/#precision
{ command: '10.54 * 7 / 3', expected: '24.593333333333333333' },
{ command: '5 * 7 / 3 * 3', expected: '35' },
{ command: '1 + 2 * 3 / 4', expected: '2.5' },

// test edge cases
{ command: '999999999999 * 999999999999', expected: '9.99999999998e+23' },

])('.evaluate($command)', ({ command, hasError, expected }) => {
test(`returns ${hasError}`, async () => {
const clientId = 'whatever';
const commandService = new CommandService(logger, mockRepository);
test(`returns ${hasError}`, () => {
if (hasError) {
await expect(commandService.evaluateAndSave(clientId, command)).rejects.toThrow('Invalid command');
expect(() => evaluate(command)).toThrow('Invalid command');
} else {
const res = await commandService.evaluateAndSave(clientId, command);
const res = evaluate(command);
expect(res).toBe(expected);
expect(mockRepository.saveCommand).toHaveBeenCalledWith(clientId, command, expected);
}
});
});
Expand Down
Loading

0 comments on commit 7f2c5bc

Please sign in to comment.