[binary-tree] Merge the operation of modifying the value into the insert query process to improve performance. [benchmark] Enhance performance test cases and move existing performance tests from unit tests to the 'performance' section. Improve the benchmark test report. Refine the logic of the report generator.

This commit is contained in:
Revone 2023-11-02 21:38:10 +08:00
parent 9a87fe13b6
commit 4614773413
41 changed files with 346 additions and 557 deletions

View file

@ -916,3 +916,32 @@ optimal approach to data structure design.
</tbody>
</table>
## Benchmark
<div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>avl-tree</span></div>
<div class="content"><table><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>executed times</th><th>sample mean (secs)</th><th>sample deviation</th></tr><tr><td>add 1000 randomly</td><td>2.39</td><td>418.91</td><td>22</td><td>0.00</td><td>6.47e-5</td></tr><tr><td>delete 1000 randomly</td><td>0.06</td><td>1.77e+4</td><td>917</td><td>5.66e-5</td><td>1.90e-6</td></tr><tr><td>addMany 1000</td><td>3.32</td><td>301.33</td><td>17</td><td>0.00</td><td>5.90e-4</td></tr><tr><td>get 1000</td><td>55.03</td><td>18.17</td><td>1</td><td>0.06</td><td>0.00</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>binary-tree</span></div>
<div class="content"><table><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>executed times</th><th>sample mean (secs)</th><th>sample deviation</th></tr><tr><td>add 1000</td><td>66.49</td><td>15.04</td><td>3</td><td>0.07</td><td>0.00</td></tr><tr><td>delete 1000</td><td>24.04</td><td>41.60</td><td>1106</td><td>0.02</td><td>0.04</td></tr><tr><td>addMany 1000</td><td>9.05</td><td>110.44</td><td>6</td><td>0.01</td><td>2.88e-4</td></tr><tr><td>get 1000</td><td>32.12</td><td>31.13</td><td>2</td><td>0.03</td><td>6.75e-4</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>bst</span></div>
<div class="content"><table><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>executed times</th><th>sample mean (secs)</th><th>sample deviation</th></tr><tr><td>add 1000 randomly</td><td>2.27</td><td>440.62</td><td>23</td><td>0.00</td><td>1.42e-4</td></tr><tr><td>delete 1000 randomly</td><td>0.05</td><td>2.19e+4</td><td>1174</td><td>4.57e-5</td><td>1.97e-6</td></tr><tr><td>addMany 1000 balanced</td><td>2.93</td><td>341.38</td><td>19</td><td>0.00</td><td>1.13e-4</td></tr><tr><td>get 1000</td><td>62.85</td><td>15.91</td><td>1</td><td>0.06</td><td>0.01</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>heap</span></div>
<div class="content"><table><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>executed times</th><th>sample mean (secs)</th><th>sample deviation</th></tr><tr><td>add & 1000</td><td>0.35</td><td>2878.39</td><td>149</td><td>3.47e-4</td><td>1.93e-5</td></tr><tr><td>fib add & pop 1000</td><td>3.98</td><td>251.35</td><td>14</td><td>0.00</td><td>1.32e-4</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>doubly-linked-list</span></div>
<div class="content"><table><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>executed times</th><th>sample mean (secs)</th><th>sample deviation</th></tr><tr><td>unshift 1000000</td><td>193.19</td><td>5.18</td><td>1</td><td>0.19</td><td>0.04</td></tr><tr><td>unshift & shift 1000000</td><td>170.35</td><td>5.87</td><td>1</td><td>0.17</td><td>0.03</td></tr><tr><td>insertBefore 1000</td><td>0.03</td><td>3.43e+4</td><td>1888</td><td>2.91e-5</td><td>6.03e-6</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>singly-linked-list</span></div>
<div class="content"><table><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>executed times</th><th>sample mean (secs)</th><th>sample deviation</th></tr><tr><td>push & pop 1000</td><td>1.79</td><td>560.08</td><td>29</td><td>0.00</td><td>7.70e-5</td></tr><tr><td>insertBefore 1000</td><td>2.31</td><td>433.45</td><td>22</td><td>0.00</td><td>5.52e-5</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>max-priority-queue</span></div>
<div class="content"><table><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>executed times</th><th>sample mean (secs)</th><th>sample deviation</th></tr><tr><td>refill & poll 1000000</td><td>1859.40</td><td>0.54</td><td>1</td><td>1.86</td><td>0.03</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>deque</span></div>
<div class="content"><table><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>executed times</th><th>sample mean (secs)</th><th>sample deviation</th></tr><tr><td>push 1000000</td><td>215.00</td><td>4.65</td><td>1</td><td>0.22</td><td>0.01</td></tr><tr><td>shift 1000000</td><td>25.04</td><td>39.94</td><td>3</td><td>0.03</td><td>0.00</td></tr></table></div>
</div><div class="json-to-html-collapse clearfix 0">
<div class='collapsible level0' ><span class='json-to-html-label'>queue</span></div>
<div class="content"><table><tr><th>test name</th><th>time taken (ms)</th><th>executions per sec</th><th>executed times</th><th>sample mean (secs)</th><th>sample deviation</th></tr><tr><td>push 1000000</td><td>41.81</td><td>23.92</td><td>2</td><td>0.04</td><td>0.00</td></tr><tr><td>push & shift 1000000</td><td>79.17</td><td>12.63</td><td>1</td><td>0.08</td><td>8.70e-4</td></tr></table></div>
</div>

View file

@ -1,6 +1,6 @@
{
"name": "data-structure-typed",
"version": "1.41.3",
"version": "1.41.4",
"description": "Data Structures of Javascript & TypeScript. Binary Tree, BST, Graph, Heap, Priority Queue, Linked List, Queue, Deque, Stack, AVL Tree, Tree Multiset, Trie, Directed Graph, Undirected Graph, Singly Linked List, Doubly Linked List, Max Heap, Max Priority Queue, Min Heap, Min Priority Queue.",
"main": "dist/cjs/index.js",
"module": "dist/mjs/index.js",
@ -31,7 +31,7 @@
"reformat": "npm run reformat:src && npm run reformat:test",
"update:subs": "npm i avl-tree-typed binary-tree-typed bst-typed heap-typed --save-dev",
"install:all-subs": "npm i avl-tree-typed binary-tree-typed bst-typed deque-typed directed-graph-typed doubly-linked-list-typed graph-typed heap-typed linked-list-typed max-heap-typed max-priority-queue-typed min-heap-typed min-priority-queue-typed priority-queue-typed singly-linked-list-typed stack-typed tree-multiset-typed trie-typed undirected-graph-typed queue-typed --save-dev",
"test": "jest",
"test": "jest --runInBand",
"test:integration": "npm run update:subs && jest --config jest.integration.config.js",
"benchmark": "ts-node test/performance/reportor.ts",
"check:deps": "dependency-cruiser src",

View file

@ -179,7 +179,10 @@ export class BinaryTree<V = any, N extends BinaryTreeNode<V, N> = BinaryTreeNode
while (queue.size > 0) {
const cur = queue.shift();
if (cur) {
if (newNode && cur.key === newNode.key) return;
if (newNode && cur.key === newNode.key) {
cur.value = newNode.value;
return;
}
const inserted = this._addTo(newNode, cur);
if (inserted !== undefined) return inserted;
if (cur.left) queue.push(cur.left);
@ -201,16 +204,16 @@ export class BinaryTree<V = any, N extends BinaryTreeNode<V, N> = BinaryTreeNode
return;
}
const key = typeof keyOrNode === 'number' ? keyOrNode : keyOrNode ? keyOrNode.key : undefined;
const existNode = key !== undefined ? this.getNode(key, (node: N) => node.key) : undefined;
// const key = typeof keyOrNode === 'number' ? keyOrNode : keyOrNode ? keyOrNode.key : undefined;
// const existNode = key !== undefined ? this.getNode(key, (node: N) => node.key) : undefined;
if (this.root) {
if (existNode) {
existNode.value = value;
inserted = existNode;
} else {
inserted = _bfs(this.root, needInsert);
}
// if (existNode) {
// existNode.value = value;
// inserted = existNode;
// } else {
inserted = _bfs(this.root, needInsert);
// }
} else {
this._setRoot(needInsert);
if (needInsert !== null) {

View file

@ -1,4 +0,0 @@
'use strict';
Object.defineProperty(exports, '__esModule', {value: true});
exports.isDebugTest = void 0;
exports.isDebugTest = false;

View file

@ -0,0 +1,24 @@
import {AVLTree} from '../../../../src';
import * as Benchmark from 'benchmark';
import {magnitude, randomInt, randomIntArray} from '../../../utils';
const suite = new Benchmark.Suite();
const avl = new AVLTree<number>();
const {N_LOG_N} = magnitude;
suite
.add(`add ${N_LOG_N} randomly`, () => {
for (let i = 0; i < N_LOG_N; i++) avl.add(randomInt(0, N_LOG_N));
})
.add(`delete ${N_LOG_N} randomly`, () => {
for (let i = 0; i < N_LOG_N; i++) avl.delete(randomInt(0, N_LOG_N));
})
.add(`addMany ${N_LOG_N}`, () => {
const arr = randomIntArray(N_LOG_N);
avl.addMany(arr);
})
.add(`get ${N_LOG_N}`, () => {
for (let i = 0; i < N_LOG_N; i++) avl.get(randomInt(-N_LOG_N, N_LOG_N));
});
export {suite};

View file

@ -1,18 +1,25 @@
import {BinaryTree} from '../../../../src';
import * as Benchmark from 'benchmark';
import {magnitude, randomInt, randomIntArray} from '../../../utils';
export const suite = new Benchmark.Suite();
const bt = new BinaryTree<number>();
const suite = new Benchmark.Suite();
const biTree = new BinaryTree<number>();
const {N_LOG_N} = magnitude;
suite
.add('add 1000', () => {
for (let i = 0; i < 1000; i++) {
bt.add(i);
}
.add(`add ${N_LOG_N}`, () => {
for (let i = 0; i < N_LOG_N; i++) biTree.add(randomInt(-N_LOG_N, N_LOG_N));
})
.add('add & delete 1000', () => {
for (let i = 0; i < 1000; i++) {
bt.delete(i);
}
.add(`delete ${N_LOG_N}`, () => {
for (let i = 0; i < N_LOG_N; i++) biTree.delete(randomInt(-N_LOG_N, N_LOG_N));
})
.add(`addMany ${N_LOG_N}`, () => {
biTree.clear();
const arr = randomIntArray(N_LOG_N);
biTree.addMany(arr);
})
.add(`get ${N_LOG_N}`, () => {
for (let i = 0; i < N_LOG_N; i++) biTree.get(randomInt(-N_LOG_N, N_LOG_N));
});
export {suite};

View file

@ -1,18 +1,24 @@
import {BST} from '../../../../src';
import * as Benchmark from 'benchmark';
import {magnitude, randomInt, randomIntArray} from '../../../utils';
export const suite = new Benchmark.Suite();
const bt = new BST<number>();
const suite = new Benchmark.Suite();
const bst = new BST<number>();
const {N_LOG_N} = magnitude;
suite
.add('add 1000', () => {
for (let i = 0; i < 1000; i++) {
bt.add(i);
}
.add(`add ${N_LOG_N} randomly`, () => {
for (let i = 0; i < N_LOG_N; i++) bst.add(randomInt(0, N_LOG_N));
})
.add('add & delete 1000', () => {
for (let i = 0; i < 1000; i++) {
bt.delete(i);
}
.add(`delete ${N_LOG_N} randomly`, () => {
for (let i = 0; i < N_LOG_N; i++) bst.delete(randomInt(0, N_LOG_N));
})
.add(`addMany ${N_LOG_N} balanced`, () => {
const arr = randomIntArray(N_LOG_N);
bst.addMany(arr);
})
.add(`get ${N_LOG_N}`, () => {
for (let i = 0; i < N_LOG_N; i++) bst.get(randomInt(-N_LOG_N, N_LOG_N));
});
export {suite};

View file

@ -0,0 +1,30 @@
import {FibonacciHeap, Heap} from '../../../../src';
import * as Benchmark from 'benchmark';
import {magnitude} from '../../../utils';
const suite = new Benchmark.Suite();
const {N_LOG_N} = magnitude;
suite
.add(`add & ${N_LOG_N}`, () => {
const heap = new Heap<number>({comparator: (a, b) => b - a});
for (let i = 0; i < N_LOG_N; i++) {
heap.add(i);
}
for (let i = 0; i < N_LOG_N; i++) {
heap.pop();
}
})
.add(`fib add & pop ${N_LOG_N}`, () => {
const fbHeap = new FibonacciHeap<number>();
for (let i = 1; i <= N_LOG_N; i++) {
fbHeap.push(i);
}
for (let i = 1; i <= N_LOG_N; i++) {
fbHeap.pop();
}
});
export {suite};

View file

@ -0,0 +1,40 @@
import {DoublyLinkedList, DoublyLinkedListNode} from '../../../../src';
import * as Benchmark from 'benchmark';
import {magnitude} from '../../../utils';
const suite = new Benchmark.Suite();
const {LINEAR, N_LOG_N} = magnitude;
suite
.add(`unshift ${LINEAR}`, () => {
const list = new DoublyLinkedList<number>();
for (let i = 0; i < LINEAR; i++) {
list.unshift(i);
}
})
.add(`unshift & shift ${LINEAR}`, () => {
const list = new DoublyLinkedList<number>();
for (let i = 0; i < LINEAR; i++) {
list.unshift(i);
}
for (let i = 0; i < LINEAR; i++) {
list.shift();
}
})
.add(`insertBefore ${N_LOG_N}`, () => {
const doublyList = new DoublyLinkedList<number>();
let midNode: DoublyLinkedListNode | null = null;
const midIndex = Math.floor(N_LOG_N / 2);
for (let i = 0; i < N_LOG_N; i++) {
doublyList.push(i);
if (i === midIndex) {
midNode = doublyList.getNode(i);
} else if (i > midIndex && midNode) {
doublyList.insertBefore(midNode, i);
}
}
});
export {suite};

View file

@ -0,0 +1,34 @@
import {SinglyLinkedList, SinglyLinkedListNode} from '../../../../src';
import * as Benchmark from 'benchmark';
import {magnitude} from '../../../utils';
const suite = new Benchmark.Suite();
const {N_LOG_N} = magnitude;
suite
.add(`push & pop ${N_LOG_N}`, () => {
const list = new SinglyLinkedList<number>();
for (let i = 0; i < N_LOG_N; i++) {
list.push(i);
}
for (let i = 0; i < N_LOG_N; i++) {
list.pop();
}
})
.add(`insertBefore ${N_LOG_N}`, () => {
const singlyList = new SinglyLinkedList<number>();
let midSinglyNode: SinglyLinkedListNode | null = null;
const midIndex = Math.floor(N_LOG_N / 2);
for (let i = 0; i < N_LOG_N; i++) {
singlyList.push(i);
if (i === midIndex) {
midSinglyNode = singlyList.getNode(i);
} else if (i > midIndex && midSinglyNode) {
singlyList.insertBefore(midSinglyNode.value, i);
}
}
});
export {suite};

View file

@ -0,0 +1,19 @@
import {MaxPriorityQueue} from '../../../../src';
import * as Benchmark from 'benchmark';
import {magnitude} from '../../../utils';
const suite = new Benchmark.Suite();
const {LINEAR} = magnitude;
suite.add(`refill & poll ${LINEAR}`, () => {
const nodes = Array.from(
new Set<number>(Array.from(new Array(LINEAR), () => Math.floor(Math.random() * LINEAR * 100)))
);
const maxPQ = new MaxPriorityQueue<number>();
maxPQ.refill(nodes);
while (maxPQ.size > 0) {
maxPQ.poll();
}
});
export {suite};

View file

@ -1,18 +1,20 @@
import {Deque} from '../../../../src';
import * as Benchmark from 'benchmark';
import {magnitude} from '../../../utils';
export const suite = new Benchmark.Suite();
const {LINEAR} = magnitude;
suite
.add('push', () => {
.add(`push ${LINEAR}`, () => {
const deque = new Deque<number>();
for (let i = 0; i < 10; i++) {
for (let i = 0; i < LINEAR; i++) {
deque.push(i);
}
})
.add('shift', () => {
.add(`shift ${LINEAR}`, () => {
const deque = new Deque<number>();
for (let i = 0; i < 10; i++) {
for (let i = 0; i < LINEAR; i++) {
deque.push(i);
deque.shift();
}

View file

@ -1,20 +1,25 @@
import {Queue} from '../../../../src';
import * as Benchmark from 'benchmark';
import {magnitude} from '../../../utils';
export const suite = new Benchmark.Suite();
const suite = new Benchmark.Suite();
const {LINEAR} = magnitude;
suite
.add('push 1000000', () => {
.add(`push ${LINEAR}`, () => {
const queue = new Queue<number>();
for (let i = 0; i < 1000000; i++) {
for (let i = 0; i < LINEAR; i++) {
queue.push(i);
}
})
.add('push & shift 1000000', () => {
.add(`push & shift ${LINEAR}`, () => {
const queue = new Queue<number>();
for (let i = 0; i < 1000000; i++) {
for (let i = 0; i < LINEAR; i++) {
queue.push(i);
queue.shift();
}
});
export {suite};

View file

@ -2,7 +2,7 @@ import * as Benchmark from 'benchmark';
import * as path from 'path';
import * as fs from 'fs';
import * as fastGlob from 'fast-glob';
import {numberFix, render, Color} from './utils';
import {Color, numberFix, render} from '../utils';
import {PerformanceTest} from './types';
const reportDistPath = 'benchmark';
@ -15,7 +15,7 @@ let testFileCount = 0,
completedCount = 0;
const performanceTests: PerformanceTest[] = [];
const {GREEN, BOLD, END} = Color;
const {GREEN, BOLD, RED, END, YELLOW, CYAN, BG_YELLOW} = Color;
testFiles.forEach((file: string) => {
testFileCount++;
@ -37,6 +37,17 @@ const composeReport = () => {
<meta charset="UTF-8">
<title>performance of data-structure-typed</title>
<style>
*{
box-sizing: border-box;
}
#json-to-html {
padding: 0 10px 20px;
}
.json-to-html-label {
font-size: 2rem;
margin: 2rem 0 0 3px;
}
.content table {
width: 100%;
table-layout: fixed;
@ -64,9 +75,10 @@ const composeReport = () => {
</head>
<body>
<div id="json-to-html">`;
let htmlTables = '';
for (const r in report) {
if (report.hasOwnProperty(r)) {
html += render(report[r].testName, report[r].benchmarks, {
htmlTables += render(report[r].testName, report[r].benchmarks, {
plainHtml: true,
'<>': 'table',
html: [
@ -82,17 +94,54 @@ const composeReport = () => {
});
}
}
html += htmlTables;
html += `</div>
</body>
</html>`;
writeIntoMarkdown(htmlTables);
fs.writeFileSync(htmlFilePath, html);
console.log(`Performance ${BOLD}${GREEN}report${END} file generated`);
};
function writeIntoMarkdown(html: string) {
const parentDirectory = path.resolve(__dirname, '../..'); // The path to the parent directory
const markdownFilePath = path.join(parentDirectory, 'README.md'); // Path to README.md file
const textToInsert = html;
// Read the original README.md file
fs.readFile(markdownFilePath, 'utf8', (err, data) => {
if (err) {
console.error('Unable to read README.md file', err);
return;
}
// Find the location in the README.md file where you want to insert the text, for example under a specific tag
const insertMarker = '## Benchmark';
const index = data.indexOf(insertMarker);
if (index === -1) {
console.error('Unable to find insertion point');
return;
}
// insert text
const updatedMarkdown =
data.slice(0, index + insertMarker.length) + '\n' + textToInsert + data.slice(index + insertMarker.length);
// Try writing the modified content back to the README.md file
fs.writeFile(markdownFilePath, updatedMarkdown, 'utf8', err => {
if (err) {
console.error('Unable to write to README.md file', err);
} else {
console.log('The text has been successfully inserted into the README.md file!');
}
});
});
}
performanceTests.forEach(item => {
const {suite, testName, file} = item;
console.log(`testing file ${GREEN}${file}${END}`);
console.log(`${BG_YELLOW}Running in${END}: ${CYAN}${file}${END}`);
if (suite) {
suite
@ -103,16 +152,20 @@ performanceTests.forEach(item => {
'test name': benchmark.name,
'time taken (ms)': numberFix(benchmark.times.period * 1000, 2),
'executions per sec': numberFix(benchmark.hz, 2),
// 'executed times': numberFix(benchmark.count, 2),
'executed times': numberFix(benchmark.count, 0),
'sample mean (secs)': numberFix(benchmark.stats.mean, 2),
'sample deviation': numberFix(benchmark.stats.deviation, 2)
}));
report[testName].testName = testName;
const isDone = completedCount === performanceTests.length;
console.log(
`test files: ${GREEN}${testFileCount}${END}. suites: ${GREEN}${performanceTests.length}${END}. completed suites: ${GREEN}${completedCount}${END}`
`Files: ${GREEN}${testFileCount}${END} `,
`Suites: ${GREEN}${performanceTests.length}${END} `,
`Progress: ${isDone ? GREEN : YELLOW}${completedCount}${END}/${isDone ? GREEN : RED}${
performanceTests.length
}${END}`
);
if (completedCount === performanceTests.length) {
if (isDone) {
composeReport();
}
})

View file

@ -1,2 +1 @@
export * from './utils';
export * from './reportor';

View file

@ -1 +0,0 @@
export * from './json2html';

View file

@ -1,4 +0,0 @@
export * from './json2html';
export * from './number';
export * from './is';
export * from './console';

View file

@ -1,9 +0,0 @@
export function numberFix(num: number, decimalPlaces: number): string {
if (num > 10000 || num < 0.001) {
const [mantissa, exponent] = num.toExponential().split('e');
const formattedMantissa = Number(mantissa).toFixed(decimalPlaces);
return `${formattedMantissa}e${exponent}`;
} else {
return num.toFixed(decimalPlaces);
}
}

View file

@ -1,29 +0,0 @@
'use strict';
var __createBinding =
(this && this.__createBinding) ||
(Object.create
? function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ('get' in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = {
enumerable: true,
get: function () {
return m[k];
}
};
}
Object.defineProperty(o, k2, desc);
}
: function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
});
var __exportStar =
(this && this.__exportStar) ||
function (m, exports) {
for (var p in m)
if (p !== 'default' && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, '__esModule', {value: true});
__exportStar(require('./utils'), exports);

View file

@ -1,2 +0,0 @@
'use strict';
Object.defineProperty(exports, '__esModule', {value: true});

View file

@ -1,29 +0,0 @@
'use strict';
var __createBinding =
(this && this.__createBinding) ||
(Object.create
? function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ('get' in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = {
enumerable: true,
get: function () {
return m[k];
}
};
}
Object.defineProperty(o, k2, desc);
}
: function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
});
var __exportStar =
(this && this.__exportStar) ||
function (m, exports) {
for (var p in m)
if (p !== 'default' && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, '__esModule', {value: true});
__exportStar(require('./big-o'), exports);

View file

@ -1 +1,2 @@
export * from './big-o';
export * from './json2html';

View file

@ -1,5 +1,5 @@
import {RBTNColor, RBTreeNode, RedBlackTree, NIL} from '../../../../src';
import {getRandomInt} from '../../../utils';
import {NIL, RBTNColor, RBTreeNode, RedBlackTree} from '../../../../src';
import {randomInt} from '../../../utils';
import {isDebugTest} from '../../../config';
const isDebug = isDebugTest;
@ -428,8 +428,8 @@ describe('RedBlackTree', () => {
it('should fix the tree after insertion and deletion', () => {
for (let i = 0; i < 100; i++) {
tree.insert(getRandomInt(-100, 1000));
tree.delete(getRandomInt(-100, 1000));
tree.insert(randomInt(-100, 1000));
tree.delete(randomInt(-100, 1000));
}
});
});

View file

@ -1,5 +1,4 @@
import {DoublyLinkedList, DoublyLinkedListNode} from '../../../../src';
import {bigO, magnitude} from '../../../utils';
describe('DoublyLinkedListNode', () => {
it('should DoublyLinkedListNode', () => {
@ -399,25 +398,3 @@ describe('DoublyLinkedList Operation Test', () => {
expect(shiftedObj).toBe(obj1);
});
});
describe('DoublyLinkedList Performance Test', () => {
it('should the push and pop methods adhere to a time complexity of O(n) and executed correctly under large scale data', () => {
const list = new DoublyLinkedList<number>();
const startPushTime = performance.now();
for (let i = 0; i < magnitude.LINEAR; i++) {
list.unshift(i);
}
expect(performance.now() - startPushTime).toBeLessThan(bigO.LINEAR * 20);
expect(list.length).toBeGreaterThan(0);
const startPopTime = performance.now();
for (let i = 0; i < magnitude.LINEAR; i++) {
list.shift();
}
expect(performance.now() - startPopTime).toBeLessThan(bigO.LINEAR * 100);
expect(list.pop()).toBeUndefined();
expect(list.length).toBe(0);
});
});

View file

@ -1,35 +1,8 @@
import {DoublyLinkedList, DoublyLinkedListNode, SinglyLinkedList, SinglyLinkedListNode} from '../../../../src';
import {bigO, magnitude} from '../../../utils';
// import {DoublyLinkedList, DoublyLinkedListNode, SinglyLinkedList, SinglyLinkedListNode} from '../../../../src';
// import {bigO, magnitude} from '../../../utils';
describe('LinkedList Performance Test', () => {
it('should DoublyLinkedList insertBefore faster than SinglyLinkedList', () => {
const doublyList = new DoublyLinkedList<number>();
const startPushTime = performance.now();
let midNode: DoublyLinkedListNode | null = null;
const midIndex = Math.floor(magnitude.SQUARED / 2);
for (let i = 0; i < magnitude.SQUARED; i++) {
doublyList.push(i);
if (i === midIndex) {
midNode = doublyList.getNode(i);
} else if (i > midIndex && midNode) {
doublyList.insertBefore(midNode, i);
}
}
const doublyListPushCost = performance.now() - startPushTime;
const singlyList = new SinglyLinkedList<number>();
let midSinglyNode: SinglyLinkedListNode | null = null;
for (let i = 0; i < magnitude.SQUARED; i++) {
singlyList.push(i);
if (i === midIndex) {
midSinglyNode = singlyList.getNode(i);
} else if (i > midIndex && midSinglyNode) {
singlyList.insertBefore(midSinglyNode.value, i);
}
}
expect(doublyListPushCost).toBeLessThan(bigO.SQUARED * 10);
expect(1).toBe(1);
});
});

View file

@ -1,5 +1,4 @@
import {SinglyLinkedList, SinglyLinkedListNode} from '../../../../src';
import {bigO, magnitude} from '../../../utils';
describe('SinglyLinkedListNode', () => {
it('should SinglyLinkedList', () => {
@ -395,26 +394,6 @@ describe('SinglyLinkedList Operation Test', () => {
});
});
describe('SinglyLinkedList Performance Test', () => {
it('should the push and pop methods adhere to a time complexity of O(n) and executed correctly under large scale data', () => {
const list = new SinglyLinkedList<number>();
const startPushTime = performance.now();
for (let i = 0; i < magnitude.LINEAR; i++) {
list.push(i);
}
expect(performance.now() - startPushTime).toBeLessThan(bigO.LINEAR * 20);
const startPopTime = performance.now();
for (let i = 0; i < magnitude.LINEAR; i++) {
list.pop();
}
// expect(performance.now() - startPopTime).toBeLessThan(bigO.LINEAR);
expect(performance.now() - startPopTime).toBeLessThan(bigO.LINEAR * 400);
});
});
describe('SinglyLinkedList', () => {
let list: SinglyLinkedList<number>;

View file

@ -315,7 +315,7 @@ describe('Matrix2D', () => {
it('should correctly rotate a matrix', () => {
const radians = Math.PI / 4; // 45 degrees
const rotationMatrix = Matrix2D.rotate(radians);
console.log(JSON.stringify(rotationMatrix.m));
isDebug && console.log(JSON.stringify(rotationMatrix.m));
expect(rotationMatrix.m).toEqual([
[0.7071067811865476, -0.7071067811865475, 0],
[0.7071067811865475, 0.7071067811865476, 0],

View file

@ -1,5 +1,4 @@
import {MaxPriorityQueue} from '../../../../src';
import {bigO, magnitude} from '../../../utils';
describe('MaxPriorityQueue Operation Test', () => {
it('should add elements and maintain heap property', () => {
@ -72,34 +71,3 @@ describe('MaxPriorityQueue Operation Test', () => {
expect(maxPQ.poll()?.keyA).toBe(1);
});
});
describe('MaxPriorityQueue Performance Test', () => {
it('should the poll method adheres to a time complexity of O(log n) and executed correctly under large scale distinct data', () => {
const nodes = Array.from(
new Set<number>(Array.from(new Array(magnitude.LINEAR), () => Math.floor(Math.random() * magnitude.LINEAR * 100)))
);
expect(nodes.length).toBeGreaterThan(magnitude.LINEAR / 2);
const maxPQ = new MaxPriorityQueue<number>();
maxPQ.refill(nodes);
let prev = Number.MAX_SAFE_INTEGER;
const startTime = performance.now();
while (maxPQ.size > 0) {
const polled = maxPQ.poll();
if (polled) {
prev = polled;
}
}
const cost = performance.now() - startTime;
expect(cost).toBeLessThan(bigO.LINEAR * 30);
expect(prev).toBeGreaterThan(0);
});
it('should sorted.length to be the same as original data', () => {
// const magnitude = 1000;
// const maxPriorityQueue = new MaxPriorityQueue<number>({nodes: Array.from(new Array<number>(magnitude), () => Math.floor(Math.random() * magnitude))});
// const nodeCount = maxPriorityQueue.getNodes().length;
// const sorted = maxPriorityQueue.sort();
//
// expect(sorted.length).toBe(nodeCount); // TODO Plan to support sorting of duplicate elements.
});
});

View file

@ -1,5 +1,5 @@
import {PriorityQueue} from '../../../../src';
import {getRandomInt} from '../../../utils';
import {randomInt} from '../../../utils';
describe('PriorityQueue Operation Test', () => {
it('should PriorityQueue poll, pee, heapify, toArray work well', function () {
@ -44,7 +44,7 @@ describe('PriorityQueue Operation Test', () => {
describe('Priority Queue Performance Test', () => {
it('should numeric heap work well', function () {
const values = Array.from(new Array(10000), () => getRandomInt(1, 10000000));
const values = Array.from(new Array(10000), () => randomInt(1, 10000000));
const minPriorityQueue = new PriorityQueue<number>({comparator: (a, b) => a - b});
minPriorityQueue.refill(values);
const sorted = minPriorityQueue.sort();

View file

@ -1,5 +1,5 @@
import {LinkedListQueue, Queue} from '../../../../src';
import {bigO, magnitude} from '../../../utils';
import {bigO} from '../../../utils';
import {isDebugTest} from '../../../config';
const isDebug = isDebugTest;
@ -17,23 +17,6 @@ describe('Queue Operation Test', () => {
});
});
describe('Queue Performance Test', () => {
it('should numeric queue performance well', function () {
const queue = new Queue<number>();
for (let i = 0; i < magnitude.LINEAR; i++) {
queue.enqueue(i);
}
let last: number | undefined = 0;
const startTime = performance.now();
for (let i = 0; i < magnitude.LINEAR; i++) {
last = queue.dequeue();
}
expect(last).toBe(magnitude.LINEAR - 1);
expect(performance.now() - startTime).toBeLessThan(bigO.LINEAR * 100);
});
});
describe('Queue', () => {
let queue: Queue<number>;
@ -51,24 +34,8 @@ describe('Queue', () => {
expect(queue.peek()).toBe(1);
expect(queue.size).toBe(2);
});
// it('should shift elements from the front of the queue', () => {
// queue.push(1);
// queue.push(2);
// const shifted = queue.shift();
// expect(shifted).toBe(1);
// expect(queue.peek()).toBe(2);
// expect(queue.size).toBe(1);
// });
//
// it('should peek at the front of the queue', () => {
// queue.push(1);
// queue.push(2);
// expect(queue.peek()).toBe(1);
// });
// Add more test cases for other methods of Queue.
});
describe('Queue', () => {
let queue: Queue<number>;
@ -196,8 +163,6 @@ describe('LinkedListQueue', () => {
queue.enqueue('B');
expect(queue.peek()).toBe('A');
});
// Add more test cases for other methods of LinkedListQueue.
});
describe('Queue Performance Test', () => {
@ -224,7 +189,6 @@ describe('Queue Performance Test', () => {
for (let i = 0; i < dataSize; i++) {
queue2.shift();
}
console.log(`Array Performance Test: ${performance.now() - startTime2} ms`);
expect(performance.now() - startTime2).toBeLessThan(bigO.CUBED * 100);
});
@ -237,7 +201,7 @@ describe('Queue Performance Test', () => {
for (let i = 0; i < dataSize; i++) {
queue.dequeue();
}
console.log(`LinkedListQueue Performance Test: ${performance.now() - startTime} ms`);
// console.log(`LinkedListQueue Performance Test: ${performance.now() - startTime} ms`);
expect(performance.now() - startTime).toBeLessThan(bigO.LINEAR * 100);
});
});

5
test/utils/array.ts Normal file
View file

@ -0,0 +1,5 @@
import {randomInt} from './number';
export const randomIntArray = (length: number, min: number = -1000, max: number = 1000) => {
return new Array<number>(length).fill(0).map(() => randomInt(min, max));
};

View file

@ -1,222 +0,0 @@
'use strict';
Object.defineProperty(exports, '__esModule', {value: true});
exports.logBigOMetrics = exports.logBigOMetricsWrap = exports.bigO = exports.magnitude = void 0;
var config_1 = require('../config');
var isDebug = config_1.isDebugTest;
var orderReducedBy = 2; // reduction of bigO's order compared to the baseline bigO
exports.magnitude = {
CONSTANT: Math.floor(Number.MAX_SAFE_INTEGER / Math.pow(10, orderReducedBy)),
LOG_N: Math.pow(10, 9 - orderReducedBy),
LINEAR: Math.pow(10, 6 - orderReducedBy),
N_LOG_N: Math.pow(10, 5 - orderReducedBy),
SQUARED: Math.pow(10, 4 - orderReducedBy),
CUBED: Math.pow(10, 3 - orderReducedBy),
FACTORIAL: 20 - orderReducedBy
};
exports.bigO = {
CONSTANT: exports.magnitude.CONSTANT / 100000,
LOG_N: Math.log2(exports.magnitude.LOG_N) / 1000,
LINEAR: exports.magnitude.LINEAR / 1000,
N_LOG_N: (exports.magnitude.N_LOG_N * Math.log2(exports.magnitude.LOG_N)) / 1000,
SQUARED: Math.pow(exports.magnitude.SQUARED, 2) / 1000,
CUBED: Math.pow(exports.magnitude.SQUARED, 3) / 1000,
FACTORIAL: 10000
};
function findPotentialN(input) {
var longestArray = [];
var mostProperties = {};
function recurse(obj) {
if (Array.isArray(obj)) {
if (obj.length > longestArray.length) {
longestArray = obj;
}
} else if (typeof obj === 'object' && obj !== null) {
var keys = Object.keys(obj);
if (keys.length > Object.keys(mostProperties).length) {
mostProperties = obj;
}
keys.forEach(function (key) {
recurse(obj[key]);
});
}
}
if (Array.isArray(input)) {
input.forEach(function (item) {
recurse(item);
});
} else {
recurse(input);
}
// return [longestArray, mostProperties] : [any[], { [key: string]: any }];
return Math.max(longestArray.length, Object.keys(mostProperties).length);
}
function linearRegression(x, y) {
var n = x.length;
var sumX = x.reduce(function (acc, val) {
return acc + val;
}, 0);
var sumY = y.reduce(function (acc, val) {
return acc + val;
}, 0);
var sumXSquared = x.reduce(function (acc, val) {
return acc + Math.pow(val, 2);
}, 0);
var sumXY = x.reduce(function (acc, val, i) {
return acc + val * y[i];
}, 0);
var slope = (n * sumXY - sumX * sumY) / (n * sumXSquared - Math.pow(sumX, 2));
var intercept = (sumY - slope * sumX) / n;
var yHat = x.map(function (val) {
return slope * val + intercept;
});
var totalVariation = y
.map(function (val, i) {
return Math.pow(val - yHat[i], 2);
})
.reduce(function (acc, val) {
return acc + val;
}, 0);
var explainedVariation = y
.map(function (val) {
return Math.pow(val - sumY / n, 2);
})
.reduce(function (acc, val) {
return acc + val;
}, 0);
var rSquared = 1 - totalVariation / explainedVariation;
return {slope: slope, intercept: intercept, rSquared: rSquared};
}
function estimateBigO(runtimes, dataSizes) {
// Make sure the input runtimes and data sizes have the same length
if (runtimes.length !== dataSizes.length) {
return 'Lengths of input arrays do not match';
}
// Create an array to store the computational complexity of each data point
var complexities = [];
// Traverse different possible complexities
var complexitiesToCheck = [
'O(1)',
'O(log n)',
'O(n)',
'O(n log n)',
'O(n^2)' // squared time complexity
];
var _loop_1 = function (complexity) {
// Calculate data points for fitting
var fittedData = dataSizes.map(function (size) {
if (complexity === 'O(1)') {
return 1; // constant time complexity
} else if (complexity === 'O(log n)') {
return Math.log(size);
} else if (complexity === 'O(n)') {
return size;
} else if (complexity === 'O(n log n)') {
return size * Math.log(size);
} else if (complexity === 'O(n^2)') {
return Math.pow(size, 2);
} else {
return Math.pow(size, 10);
}
});
// Fit the data points using linear regression analysis
var regressionResult = linearRegression(fittedData, runtimes);
// Check the R-squared value of the fit. It is usually considered a valid fit if it is greater than 0.9.
if (regressionResult.rSquared >= 0.9) {
complexities.push(complexity);
}
};
for (var _i = 0, complexitiesToCheck_1 = complexitiesToCheck; _i < complexitiesToCheck_1.length; _i++) {
var complexity = complexitiesToCheck_1[_i];
_loop_1(complexity);
}
// If there is no valid fitting result, return "cannot estimate", otherwise return the estimated time complexity
if (complexities.length === 0) {
return 'Unable to estimate';
} else {
return complexities.join(' or ');
}
}
var methodLogs = new Map();
function logBigOMetricsWrap(fn, args, fnName) {
var startTime = performance.now();
var result = fn(args);
var endTime = performance.now();
var runTime = endTime - startTime;
var methodName = ''.concat(fnName);
if (!methodLogs.has(methodName)) {
methodLogs.set(methodName, []);
}
var methodLog = methodLogs.get(methodName);
var maxDataSize = args.length === 1 && typeof args[0] === 'number' ? args[0] : findPotentialN(args);
if (methodLog) {
methodLog.push([runTime, maxDataSize]);
if (methodLog.length >= 20) {
isDebug && console.log('triggered', methodName, methodLog);
var bigO_1 = estimateBigO(
methodLog.map(function (_a) {
var runTime = _a[0];
return runTime;
}),
methodLog.map(function (_a) {
var runTime = _a[0];
return runTime;
})
);
isDebug && console.log('Estimated Big O: '.concat(bigO_1));
methodLogs.delete(methodName);
}
}
return result;
}
exports.logBigOMetricsWrap = logBigOMetricsWrap;
function logBigOMetrics(target, propertyKey, descriptor) {
var originalMethod = descriptor.value;
descriptor.value = function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
var startTime = performance.now();
var result = originalMethod.apply(this, args);
var endTime = performance.now();
var runTime = endTime - startTime;
var methodName = ''.concat(target.constructor.name, '.').concat(propertyKey);
if (!methodLogs.has(methodName)) {
methodLogs.set(methodName, []);
}
var methodLog = methodLogs.get(methodName);
var maxDataSize = args.length === 1 && typeof args[0] === 'number' ? args[0] : findPotentialN(args);
if (methodLog) {
methodLog.push([runTime, maxDataSize]);
if (methodLog.length >= 20) {
isDebug && console.log('triggered', methodName, methodLog);
var bigO_2 = estimateBigO(
methodLog.map(function (_a) {
var runTime = _a[0];
return runTime;
}),
methodLog.map(function (_a) {
var runTime = _a[0];
return runTime;
})
);
isDebug && console.log('Estimated Big O: '.concat(bigO_2));
methodLogs.delete(methodName);
}
}
return result;
};
return descriptor;
}
exports.logBigOMetrics = logBigOMetrics;

View file

@ -2,15 +2,15 @@ import {AnyFunction} from '../types';
import {isDebugTest} from '../config';
const isDebug = isDebugTest;
const orderReducedBy = 2; // reduction of bigO's order compared to the baseline bigO
const orderReducedBy = 1; // reduction of bigO's order compared to the baseline bigO
export const magnitude = {
CONSTANT: Math.floor(Number.MAX_SAFE_INTEGER / Math.pow(10, orderReducedBy)),
LOG_N: Math.pow(10, 9 - orderReducedBy),
LINEAR: Math.pow(10, 6 - orderReducedBy),
N_LOG_N: Math.pow(10, 5 - orderReducedBy),
SQUARED: Math.pow(10, 4 - orderReducedBy),
CUBED: Math.pow(10, 3 - orderReducedBy),
CONSTANT: Math.pow(10, 9),
LOG_N: Math.pow(10, 8 - orderReducedBy),
LINEAR: Math.pow(10, 7 - orderReducedBy),
N_LOG_N: Math.pow(10, 4 - orderReducedBy),
SQUARED: Math.pow(10, 3 - orderReducedBy),
CUBED: Math.pow(10, 2 - orderReducedBy),
FACTORIAL: 20 - orderReducedBy
};

View file

@ -1,30 +0,0 @@
'use strict';
var __createBinding =
(this && this.__createBinding) ||
(Object.create
? function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ('get' in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = {
enumerable: true,
get: function () {
return m[k];
}
};
}
Object.defineProperty(o, k2, desc);
}
: function (o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
});
var __exportStar =
(this && this.__exportStar) ||
function (m, exports) {
for (var p in m)
if (p !== 'default' && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, '__esModule', {value: true});
__exportStar(require('./number'), exports);
__exportStar(require('./big-o'), exports);

View file

@ -1,2 +1,6 @@
export * from './number';
export * from './array';
export * from './big-o';
export * from './json2html';
export * from './is';
export * from './console';

View file

@ -104,6 +104,7 @@ function drawTable(arr: any[]): string {
function drawRow(headers: string[], rowObj: any): string {
return '<td>' + headers.map(header => rowObj[header]).join('</td><td>') + '</td>';
}
const cols = Object.keys(arr[0]);
const content = arr.map(rowObj => drawRow(cols, rowObj));
const headingHtml = '<tr><th>' + cols.join('</th><th>') + '</th></tr>';

View file

@ -1,14 +0,0 @@
'use strict';
Object.defineProperty(exports, '__esModule', {value: true});
exports.getMSB = exports.getRandomInt = void 0;
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
exports.getRandomInt = getRandomInt;
var getMSB = function (value) {
if (value <= 0) {
return 0;
}
return 1 << (31 - Math.clz32(value));
};
exports.getMSB = getMSB;

View file

@ -1,3 +1,13 @@
export function getRandomInt(min: number, max: number) {
export function randomInt(min: number, max: number) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
export function numberFix(num: number, decimalPlaces: number): string {
if (num > 10000 || num < 0.001) {
const [mantissa, exponent] = num.toExponential().split('e');
const formattedMantissa = Number(mantissa).toFixed(decimalPlaces);
return `${formattedMantissa}e${exponent}`;
} else {
return num.toFixed(decimalPlaces);
}
}