Skip to content

Commit

Permalink
Merge pull request #658 from ignlg/next
Browse files Browse the repository at this point in the history
Improve performance, upgrade packages, and bump to v2.6
ignlg authored Dec 28, 2024
2 parents a71e93f + c43aed3 commit c6bce4c
Showing 28 changed files with 3,956 additions and 6,712 deletions.
51 changes: 45 additions & 6 deletions .github/workflows/npm-test.yml
Original file line number Diff line number Diff line change
@@ -10,23 +10,62 @@ on:
branches: ["master", "next"]

jobs:
build:
lint:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [16.x, 18.x, 20.x]
node-version: [lts/*, current]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm install --global yarn
- run: yarn
# - run: npm run build --if-present
- run: yarn --immutable
- run: npm run lint
- run: npm run test

test:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [lts/*, current]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- name: Install yarn
run: npm install --global yarn
- name: Install dependencies
run: yarn --immutable
- name: Run tests
run: npm run test

build:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [lts/*, current]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/

steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm install --global yarn
- run: yarn --immutable
- run: npm run build
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -43,6 +43,11 @@ heap vs array: push + top(50) of 100

## Changelog

### 2.6

- Improves performance of remove and sorting methods.
- Improves tests and documentation.

### 2.5

- Improves the `limit` property to support negative, Infinity, and NaN values. They will be set as `0` and the heap will not be limited.
@@ -331,9 +336,13 @@ To do:

<https://ignlg.github.io/heap-js/>

## Contributing
## Sponsor

We are looking for sponsors to help us maintain and improve **Heap.js**. If you're interested in supporting this project, please get in touch with us.

## Collaborate

Development of **Heap.js** happens in the open on GitHub, and I am grateful to the community for contributing bug fixes and improvements.
Would you like to contribute to **Heap.js**? Feel free to submit a pull request, open an issue, or reach out to the maintainers. We welcome your feedback and ideas!

### Dev setup

@@ -355,4 +364,4 @@ npm run benchmarks

### License

Heap.js is [BSD licensed](LICENSE).
Heap.js is [BSD 3-Clause Licensed](LICENSE).
280 changes: 123 additions & 157 deletions dist/heap-js.es5.js

Large diffs are not rendered by default.

280 changes: 123 additions & 157 deletions dist/heap-js.umd.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/assets/hierarchy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
window.hierarchyData = "eJyrVirKzy8pVrKKjtVRKkpNy0lNLsnMzytWsqqurQUAmx4Kpg=="
18 changes: 18 additions & 0 deletions docs/assets/icons.js
1 change: 1 addition & 0 deletions docs/assets/icons.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 5 additions & 3 deletions docs/assets/main.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/assets/navigation.js
2 changes: 1 addition & 1 deletion docs/assets/search.js

Large diffs are not rendered by default.

2,605 changes: 1,424 additions & 1,181 deletions docs/assets/style.css

Large diffs are not rendered by default.

1,884 changes: 267 additions & 1,617 deletions docs/classes/Heap.html

Large diffs are not rendered by default.

1,729 changes: 234 additions & 1,495 deletions docs/classes/HeapAsync.html

Large diffs are not rendered by default.

59 changes: 1 addition & 58 deletions docs/functions/toInt.html

Large diffs are not rendered by default.

224 changes: 71 additions & 153 deletions docs/index.html

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions docs/media/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
BSD 3-Clause License

Copyright (c) 2017, Ignacio Lago
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.

* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Binary file added docs/media/heap-js.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
78 changes: 1 addition & 77 deletions docs/modules.html

Large diffs are not rendered by default.

70 changes: 1 addition & 69 deletions docs/types/AsyncComparator.html

Large diffs are not rendered by default.

70 changes: 1 addition & 69 deletions docs/types/AsyncIsEqual.html

Large diffs are not rendered by default.

70 changes: 1 addition & 69 deletions docs/types/Comparator.html

Large diffs are not rendered by default.

70 changes: 1 addition & 69 deletions docs/types/IsEqual.html

Large diffs are not rendered by default.

48 changes: 24 additions & 24 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "heap-js",
"version": "2.5.0",
"version": "2.6.0",
"description": "Efficient Binary heap (priority queue, binary tree) data structure for JavaScript / TypeScript. Includes JavaScript methods, Python's heapq module methods, and Java's PriorityQueue methods.",
"keywords": [
"heap",
@@ -19,7 +19,7 @@
"license": "BSD-3-Clause",
"repository": {
"type": "git",
"url": "git@github.com:ignlg/heap-js.git"
"url": "git+ssh://git@github.com/ignlg/heap-js.git"
},
"files": [
"dist"
@@ -69,32 +69,32 @@
}
},
"devDependencies": {
"@rollup/plugin-commonjs": "^25.0.4",
"@rollup/plugin-node-resolve": "^15.2.1",
"@types/benchmark": "^2.1.2",
"@types/jest": "^29.5.3",
"@types/node": "^20.5.2",
"@typescript-eslint/eslint-plugin": "6.4.1",
"@typescript-eslint/parser": "^6.4.1",
"@rollup/plugin-commonjs": "^28.0.2",
"@rollup/plugin-node-resolve": "^16.0.0",
"@types/benchmark": "^2.1.5",
"@types/jest": "^29.5.14",
"@types/node": "^22.10.2",
"@typescript-eslint/eslint-plugin": "8.18.2",
"@typescript-eslint/parser": "^8.18.2",
"benchmark": "^2.1.4",
"coveralls": "^3.1.1",
"cross-env": "^7.0.3",
"eslint": "8.47.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
"husky": "^8.0.3",
"jest": "^29.6.3",
"lint-staged": "^14.0.1",
"eslint": "<9.0.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"husky": "^9.1.7",
"jest": "^29.7.0",
"lint-staged": "^15.3.0",
"lodash.camelcase": "^4.3.0",
"prettier": "^3.0.2",
"prettier-eslint": "^15.0.1",
"rimraf": "^5.0.1",
"rollup": "^3.28.1",
"prettier": "^3.4.2",
"prettier-eslint": "^16.3.0",
"rimraf": "^6.0.1",
"rollup": "^4.29.1",
"rollup-plugin-sourcemaps": "^0.6.3",
"ts-jest": "^29.1.1",
"ts-node": "^10.9.1",
"tsc-watch": "^6.0.4",
"typedoc": "^0.24.8",
"typescript": "^5.1.6"
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"tsc-watch": "^6.2.1",
"typedoc": "^0.27.6",
"typescript": "^5.7.2"
}
}
2 changes: 1 addition & 1 deletion rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ import commonjs from '@rollup/plugin-commonjs';
import resolve from '@rollup/plugin-node-resolve';
import sourceMaps from 'rollup-plugin-sourcemaps';

import pkg from './package.json' assert { type: 'json' };
import pkg from './package.json' with { type: 'json' };

const libraryName = 'Heap';

77 changes: 36 additions & 41 deletions src/Heap.ts
Original file line number Diff line number Diff line change
@@ -614,24 +614,28 @@ export class Heap<T> implements Iterable<T> {
* @return {Boolean} True if the heap was modified
*/
remove(o?: T, callbackFn: IsEqual<T> = Heap.defaultIsEqual): boolean {
if (this.length > 0) {
if (o === undefined) {
this.pop();
return true;
} else {
const idx = this.indexOf(o, callbackFn);
if (idx >= 0) {
if (idx === 0) {
this.pop();
} else if (idx === this.length - 1) {
this.heapArray.pop();
} else {
this.heapArray.splice(idx, 1, this.heapArray.pop() as T);
this._sortNodeUp(idx);
this._sortNodeDown(idx);
}
return true;
if (!this.heapArray.length) return false;
if (o === undefined) {
this.pop();
return true;
}
const queue = [0];
while (queue.length) {
const idx = queue.shift() as number;
if (callbackFn(this.heapArray[idx], o)) {
if (idx === 0) {
this.pop();
} else if (idx === this.heapArray.length - 1) {
this.heapArray.pop();
} else {
this.heapArray.splice(idx, 1, this.heapArray.pop() as T);
this._sortNodeUp(idx);
this._sortNodeDown(idx);
}
return true;
} else if (this.compare(this.heapArray[idx], o) <= 0) {
const children = Heap.getChildrenIndexOf(idx).filter((c) => c < this.heapArray.length);
queue.push(...children);
}
}
return false;
@@ -808,26 +812,20 @@ export class Heap<T> implements Iterable<T> {
* @param {Number} i Index of the node
*/
_sortNodeDown(i: number): void {
let moveIt = i < this.heapArray.length - 1;
const self = this.heapArray[i];

const getPotentialParent = (best: number, j: number) => {
if (this.heapArray.length > j && this.compare(this.heapArray[j], this.heapArray[best]) < 0) {
best = j;
const { length } = this.heapArray;
while (true) {
const left = 2 * i + 1;
const right = left + 1;
let best = i;
if (left < length && this.compare(this.heapArray[left], this.heapArray[best]) < 0) {
best = left;
}
return best;
};

while (moveIt) {
const childrenIdx = Heap.getChildrenIndexOf(i);
const bestChildIndex = childrenIdx.reduce(getPotentialParent, childrenIdx[0]);
const bestChild = this.heapArray[bestChildIndex];
if (typeof bestChild !== 'undefined' && this.compare(self, bestChild) > 0) {
this._moveNode(i, bestChildIndex);
i = bestChildIndex;
} else {
moveIt = false;
if (right < length && this.compare(this.heapArray[right], this.heapArray[best]) < 0) {
best = right;
}
if (best === i) break;
this._moveNode(i, best);
i = best;
}
}

@@ -836,15 +834,12 @@ export class Heap<T> implements Iterable<T> {
* @param {Number} i Index of the node
*/
_sortNodeUp(i: number): void {
let moveIt = i > 0;
while (moveIt) {
while (i > 0) {
const pi = Heap.getParentIndexOf(i);
if (pi >= 0 && this.compare(this.heapArray[pi], this.heapArray[i]) > 0) {
if (this.compare(this.heapArray[i], this.heapArray[pi]) < 0) {
this._moveNode(i, pi);
i = pi;
} else {
moveIt = false;
}
} else break;
}
}

86 changes: 36 additions & 50 deletions src/HeapAsync.ts
Original file line number Diff line number Diff line change
@@ -537,30 +537,28 @@ export class HeapAsync<T> implements Iterable<Promise<T>> {
* @return {Boolean} True if the heap was modified
*/
async remove(o?: T, fn: AsyncIsEqual<T> = HeapAsync.defaultIsEqual): Promise<boolean> {
if (this.length > 0) {
if (o === undefined) {
await this.pop();
if (!this.heapArray.length) return false;
if (o === undefined) {
await this.pop();
return true;
}
const queue = [0];
while (queue.length) {
const idx = queue.shift() as number;
if (await fn(this.heapArray[idx], o)) {
if (idx === 0) {
await this.pop();
} else if (idx === this.heapArray.length - 1) {
this.heapArray.pop();
} else {
this.heapArray.splice(idx, 1, this.heapArray.pop() as T);
await this._sortNodeUp(idx);
await this._sortNodeDown(idx);
}
return true;
} else {
let idx = -1;
for (let i = 0; i < this.heapArray.length; ++i) {
if (await fn(this.heapArray[i], o)) {
idx = i;
break;
}
}
if (idx >= 0) {
if (idx === 0) {
await this.pop();
} else if (idx === this.length - 1) {
this.heapArray.pop();
} else {
this.heapArray.splice(idx, 1, this.heapArray.pop() as T);
await this._sortNodeUp(idx);
await this._sortNodeDown(idx);
}
return true;
}
const children = HeapAsync.getChildrenIndexOf(idx).filter((c) => c < this.heapArray.length);
queue.push(...children);
}
}
return false;
@@ -737,46 +735,34 @@ export class HeapAsync<T> implements Iterable<Promise<T>> {
* @param {Number} i Index of the node
*/
async _sortNodeDown(i: number): Promise<void> {
let moveIt = i < this.heapArray.length - 1;
const self = this.heapArray[i];

const getPotentialParent = async (best: number, j: number) => {
if (this.heapArray.length > j && (await this.compare(this.heapArray[j], this.heapArray[best])) < 0) {
best = j;
const { length } = this.heapArray;
do {
const left = 2 * i + 1;
const right = left + 1;
let best = i;
if (left < length && (await this.compare(this.heapArray[left], this.heapArray[best])) < 0) {
best = left;
}
return best;
};

while (moveIt) {
const childrenIdx = HeapAsync.getChildrenIndexOf(i);
let bestChildIndex = childrenIdx[0];
for (let j = 1; j < childrenIdx.length; ++j) {
bestChildIndex = await getPotentialParent(bestChildIndex, childrenIdx[j]);
if (right < length && (await this.compare(this.heapArray[right], this.heapArray[best])) < 0) {
best = right;
}
const bestChild = this.heapArray[bestChildIndex];
if (typeof bestChild !== 'undefined' && (await this.compare(self, bestChild)) > 0) {
this._moveNode(i, bestChildIndex);
i = bestChildIndex;
} else {
moveIt = false;
}
}
if (best === i) break;
this._moveNode(i, best);
i = best;
} while (true);
}

/**
* Move a node up the tree (to the root) to find a place where the heap is sorted.
* @param {Number} i Index of the node
*/
async _sortNodeUp(i: number): Promise<void> {
let moveIt = i > 0;
while (moveIt) {
while (i > 0) {
const pi = HeapAsync.getParentIndexOf(i);
if (pi >= 0 && (await this.compare(this.heapArray[pi], this.heapArray[i])) > 0) {
if ((await this.compare(this.heapArray[i], this.heapArray[pi])) < 0) {
this._moveNode(i, pi);
i = pi;
} else {
moveIt = false;
}
} else break;
}
}

7 changes: 7 additions & 0 deletions tests/heap-async/heap-async-private-methods.test.ts
Original file line number Diff line number Diff line change
@@ -118,6 +118,13 @@ describe('HeapAsync private', function () {
expect(await heap._topN_heap(slice)).toEqual(top.slice(0, slice));
}
);
it('should return empty array when N is 0', async function () {
await heap.init(someValues);
expect(await heap._topN_heap(0)).toEqual([]);
});
it('should return empty array when heap is empty', async function () {
expect(await heap._topN_heap(5)).toEqual([]);
});
});
describe('#_bottomN(N)', function () {
it.each([1, 6, 12, someValues.length])(
2,903 changes: 1,491 additions & 1,412 deletions yarn.lock

Large diffs are not rendered by default.

0 comments on commit c6bce4c

Please sign in to comment.