Skip to content

Commit 22001ff

Browse files
committed
✨ feat: implement '@algorithm.ts/calculate'
1 parent b30be1f commit 22001ff

File tree

10 files changed

+486
-1
lines changed

10 files changed

+486
-1
lines changed

Diff for: README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -47,14 +47,15 @@
4747

4848

4949
A monorepo contains some of common algorithms and data structures written in
50-
Typescript.
50+
Typescript. (no third-party dependencies)
5151

5252

5353
## Overview
5454

5555
Package | Description
5656
:----------------------------------:|:--------------------------
5757
[@algorithm.ts/binary-index-tree][] | Binary Index Tree.
58+
[@algorithm.ts/calculate][] | A tiny calculator for integer arithmetics.
5859
[@algorithm.ts/circular-queue][] | Circular queue.
5960
[@algorithm.ts/dijkstra][] | Dijkstra algorithm optimized with [@algorithm.ts/priority-queue][]. #ShortestPath.
6061
[@algorithm.ts/dinic][] | Dinic algorithm. #MaxFlow, #NetworkFlow.
@@ -79,6 +80,7 @@ algorithm.ts is [MIT licensed](https://github.com/guanghechen/algorithm.ts/blob/
7980

8081
[homepage]: https://github.com/guanghechen/algorithm.ts
8182
[@algorithm.ts/binary-index-tree]: ./packages/binary-index-tree
83+
[@algorithm.ts/calculate]: ./packages/calculate
8284
[@algorithm.ts/circular-queue]: ./packages/circular-queue
8385
[@algorithm.ts/dijkstra]: ./packages/dijkstra
8486
[@algorithm.ts/dinic]: ./packages/dinic

Diff for: packages/calculate/README.md

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<header>
2+
<h1 align="center">
3+
<a href="https://github.com/guanghechen/algorithm.ts/tree/main/packages/calculate#readme">@algorithm.ts/calculate</a>
4+
</h1>
5+
<div align="center">
6+
<a href="https://www.npmjs.com/package/@algorithm.ts/calculate">
7+
<img
8+
alt="Npm Version"
9+
src="https://img.shields.io/npm/v/@algorithm.ts/calculate.svg"
10+
/>
11+
</a>
12+
<a href="https://www.npmjs.com/package/@algorithm.ts/calculate">
13+
<img
14+
alt="Npm Download"
15+
src="https://img.shields.io/npm/dm/@algorithm.ts/calculate.svg"
16+
/>
17+
</a>
18+
<a href="https://www.npmjs.com/package/@algorithm.ts/calculate">
19+
<img
20+
alt="Npm License"
21+
src="https://img.shields.io/npm/l/@algorithm.ts/calculate.svg"
22+
/>
23+
</a>
24+
<a href="#install">
25+
<img
26+
alt="Module Formats: cjs, esm"
27+
src="https://img.shields.io/badge/module_formats-cjs%2C%20esm-green.svg"
28+
/>
29+
</a>
30+
<a href="https://github.com/nodejs/node">
31+
<img
32+
alt="Node.js Version"
33+
src="https://img.shields.io/node/v/@algorithm.ts/calculate"
34+
/>
35+
</a>
36+
<a href="https://github.com/facebook/jest">
37+
<img
38+
alt="Tested with Jest"
39+
src="https://img.shields.io/badge/tested_with-jest-9c465e.svg"
40+
/>
41+
</a>
42+
<a href="https://github.com/prettier/prettier">
43+
<img
44+
alt="Code Style: prettier"
45+
src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square"
46+
/>
47+
</a>
48+
</div>
49+
</header>
50+
<br/>
51+
52+
53+
A tiny calculator for integer arithmetics such as `+-*/()`.
54+
55+
56+
## Install
57+
58+
* npm
59+
60+
```bash
61+
npm install --save @algorithm.ts/calculate
62+
```
63+
64+
* yarn
65+
66+
```bash
67+
yarn add @algorithm.ts/calculate
68+
```
69+
70+
* deno
71+
72+
```typescript
73+
import { createDLX } from 'https://raw.githubusercontent.com/guanghechen/algorithm.ts/main/packages/calculate/src/index.ts'
74+
```
75+
76+
77+
## Usage
78+
79+
* Basic
80+
81+
```typescript
82+
import calculate from '@algorithm.ts/calculate'
83+
84+
calculate('-2+1') // => -1
85+
calculate('-2*3 + 2*5*3/6') // => -1
86+
calculate('(1+(4+5+2)-3)+(6+8)') // => 23
87+
```
88+
89+
* Illegal inputs
90+
91+
```typescript
92+
import calculate from '@algorithm.ts/calculate'
93+
94+
calculate('-2++1') // => NaN
95+
calculate('-2*/23') // => NaN
96+
calculate('1+(4+5+2))') // => NaN
97+
calculate('1+(4+5+2') // => NaN
98+
```
99+
100+
* A solution of https://leetcode.com/problems/basic-calculator/
101+
102+
```typescript
103+
export { calculate } from '@algorithm.ts/calculate'
104+
```
105+
106+
## Related
107+
108+
* [编译原理-语法制导翻译实现计算器][calculate]
109+
110+
111+
[homepage]: https://github.com/guanghechen/algorithm.ts/tree/main/packages/calculate#readme
112+
[calculate]: https://me.guanghechen.com/post/fundamentals-of-compiling/exercise/

Diff for: packages/calculate/__test__/calculate.spec.ts

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import calculate from '../src'
2+
3+
describe('calculate', function () {
4+
const data = [
5+
{
6+
input: '-2+1',
7+
answer: -1,
8+
},
9+
{
10+
input: '2 + 2 * 3',
11+
answer: 8,
12+
},
13+
{
14+
input: '(2 + 2) * 3',
15+
answer: 12,
16+
},
17+
{
18+
input: '2 * 2 + 3',
19+
answer: 7,
20+
},
21+
{
22+
input: '(1+(4+5+2)-3)+(6+8)',
23+
answer: 23,
24+
},
25+
{
26+
input: '-2*3 + 2*5*3/6',
27+
answer: -1,
28+
},
29+
{
30+
input: '+2-3',
31+
answer: -1,
32+
},
33+
{
34+
input: '-2*/2',
35+
answer: Number.NaN,
36+
},
37+
{
38+
input: '1+(4+5+2))',
39+
answer: Number.NaN,
40+
},
41+
{
42+
input: '1+(4+5+2',
43+
answer: Number.NaN,
44+
},
45+
{
46+
input: '-2++1',
47+
answer: Number.NaN,
48+
},
49+
]
50+
51+
for (const kase of data) {
52+
// eslint-disable-next-line jest/valid-title
53+
test(kase.input, function () {
54+
expect(calculate(kase.input)).toEqual(kase.answer)
55+
})
56+
}
57+
})

Diff for: packages/calculate/package.json

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"name": "@algorithm.ts/calculate",
3+
"version": "1.0.16",
4+
"description": "A tiny calculator for integer arithmetics such as '+-*/()'",
5+
"author": {
6+
"name": "guanghechen",
7+
"url": "https://github.com/guanghechen/"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/guanghechen/algorithm.ts.git",
12+
"directory": "packages/calculate"
13+
},
14+
"homepage": "https://github.com/guanghechen/algorithm.ts/tree/main/packages/calculate#readme",
15+
"keywords": [
16+
"calculate",
17+
"calculator",
18+
"integer arithmetic"
19+
],
20+
"main": "lib/cjs/index.js",
21+
"module": "lib/esm/index.js",
22+
"types": "lib/types/index.d.ts",
23+
"source": "src/index.ts",
24+
"license": "MIT",
25+
"engines": {
26+
"node": ">= 14.15.0"
27+
},
28+
"files": [
29+
"lib/",
30+
"!lib/**/*.js.map",
31+
"!lib/**/*.d.ts.map",
32+
"package.json",
33+
"CHANGELOG.md",
34+
"LICENSE",
35+
"README.md"
36+
],
37+
"scripts": {
38+
"build": "cross-env NODE_ENV=production rollup -c ../../rollup.config.js",
39+
"prebuild": "rimraf lib/ && tsc -p tsconfig.src.json --emitDeclarationOnly",
40+
"prepublishOnly": "cross-env ROLLUP_SHOULD_SOURCEMAP=false yarn build",
41+
"test": "cross-env TS_NODE_FILES=true jest --config ../../jest.config.js --rootDir ."
42+
}
43+
}

Diff for: packages/calculate/src/calculate.ts

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import { TokenSymbol, idx, ll1Table, sddTable } from './constant'
2+
3+
export function parseInteger(s: string, start: number): [number, number] {
4+
let result = 0
5+
let i: number = start
6+
for (; i < s.length; ++i) {
7+
const c = s[i]
8+
if (!/\d/.test(c)) break
9+
result = result * 10 + Number(c)
10+
}
11+
return [i, result]
12+
}
13+
14+
export function calculate(
15+
rawExpression: string,
16+
parseNumber: (
17+
s: string,
18+
start: number,
19+
) => [nextIndex: number, value: number] = parseInteger,
20+
): number {
21+
const expression = rawExpression.replace(/[\s]+/g, '')
22+
let cur = 0
23+
const result: number = dfs(idx('A'), 0, 0)
24+
return cur === expression.length ? result : Number.NaN
25+
26+
function dfs(id: number, syn: number, inh: number): number {
27+
if (cur === expression.length) {
28+
// Only D and E could be parsed as \varepsilon
29+
if (id === TokenSymbol.D || id === TokenSymbol.E) return inh
30+
return Number.NaN
31+
}
32+
33+
const id0 = idx(expression[cur])
34+
35+
// Unrecognized symbol.
36+
if (id0 === undefined) return Number.NaN
37+
38+
// Matched an operator.
39+
if (id === id0) {
40+
// Matched digits.
41+
if (id0 === TokenSymbol.DIGIT) {
42+
const [nextCur, value] = parseNumber(expression, cur)
43+
44+
// No valid digit found.
45+
if (cur === nextCur) return Number.NaN
46+
47+
cur = nextCur
48+
return value
49+
}
50+
51+
cur += 1
52+
return syn
53+
}
54+
55+
// Syntax error.
56+
if (id > 0) return Number.NaN
57+
58+
const ssdId = ll1Table[-id][id0]
59+
if (ssdId < 0) return Number.NaN
60+
61+
const tokens: ReadonlyArray<number> = sddTable[ssdId]
62+
const syn0: number = tokens.length > 0 ? dfs(tokens[0], 0, 0) : 0
63+
if (Number.isNaN(syn0)) return Number.NaN
64+
65+
switch (ssdId) {
66+
// 0: A --> BD
67+
case 0:
68+
return dfs(tokens[1], 0, syn0)
69+
70+
// 1: A --> +BD
71+
case 1: {
72+
const val1: number = dfs(tokens[1], 0, 0)
73+
return dfs(tokens[2], 0, val1)
74+
}
75+
76+
// 2: A --> -BD
77+
case 2: {
78+
const val1: number = dfs(tokens[1], 0, 0)
79+
return dfs(tokens[2], 0, -val1)
80+
}
81+
82+
// 3: B --> CE
83+
case 3:
84+
return dfs(tokens[1], 0, syn0)
85+
86+
// 4: C --> digit
87+
case 4:
88+
return syn0
89+
90+
// 5: C --> (A)
91+
case 5: {
92+
const result: number = dfs(tokens[1], 0, 0)
93+
const result2: number = dfs(tokens[2], 0, 0)
94+
return Number.isNaN(result2) ? Number.NaN : result
95+
}
96+
97+
// 6: D --> +BD
98+
case 6: {
99+
const val1: number = dfs(tokens[1], 0, 0)
100+
return dfs(tokens[2], 0, inh + val1)
101+
}
102+
103+
// 7: D --> -BD
104+
case 7: {
105+
const val1: number = dfs(tokens[1], 0, 0)
106+
return dfs(tokens[2], 0, inh - val1)
107+
}
108+
109+
// 8: D --> \varepsilon
110+
case 8:
111+
return inh
112+
113+
// 9: E --> *CE
114+
case 9: {
115+
const val1: number = dfs(tokens[1], 0, 0)
116+
return dfs(tokens[2], 0, inh * val1)
117+
}
118+
119+
// 10: E --> /CE
120+
case 10: {
121+
const val1: number = dfs(tokens[1], 0, 0)
122+
return dfs(tokens[2], 0, inh / val1)
123+
}
124+
125+
// 11: E --> \varepsilon
126+
case 11:
127+
return inh
128+
129+
// Here is not reachable.
130+
default:
131+
return Number.NaN
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)