├── .eslintrc ├── .github └── workflows │ └── npm-grunt.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── index.d.ts ├── index.js ├── package.json ├── src ├── heap.d.ts ├── heap.js ├── maxHeap.d.ts ├── maxHeap.js ├── minHeap.d.ts └── minHeap.js └── test ├── heap.test.js ├── maxHeap.test.js ├── minHeap.test.js └── readme.ts /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "max-len": ["error", { "code": 100, "ignoreComments": true }], 4 | "comma-dangle": ["error", { 5 | "functions": "ignore" 6 | }], 7 | "no-underscore-dangle": 0, 8 | "class-methods-use-this": 0, 9 | "no-restricted-syntax": 0 10 | }, 11 | "env": { 12 | "mocha": true, 13 | "node": true 14 | }, 15 | "extends": ["airbnb-base"] 16 | } 17 | -------------------------------------------------------------------------------- /.github/workflows/npm-grunt.yml: -------------------------------------------------------------------------------- 1 | name: NodeJS with Grunt 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x, 16.x, 18.x] 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | 25 | - name: Build 26 | run: | 27 | npm install 28 | grunt build 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .DS_Store 4 | .idea/ 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | .travis.yml 3 | .gitignore 4 | .eslintrc 5 | Gruntfile.js 6 | coverage/ 7 | node_modules/ 8 | test/ 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ## [4.3.3] - 2024-01-07 9 | ### Fixed 10 | - default compare function for MinHeap 11 | 12 | ## [4.3.2] - 2023-06-19 13 | ### Fixed 14 | - ts types. 15 | 16 | ## [4.3.1] - 2023-01-08 17 | ### Fixed 18 | - lint config. 19 | 20 | ## [4.3.0] - 2023-01-08 21 | ### Added 22 | - `toArray` to convert the heap into an array without sorting. 23 | 24 | ## [4.2.2] - 2022-12-24 25 | ### Fixed 26 | - add iterable for ts definitions. 27 | 28 | ## [4.2.1] - 2022-12-23 29 | ### Fixed 30 | - typo in readme. 31 | 32 | ## [4.2.0] - 2022-12-23 33 | ### Added 34 | - `Symbol.iterator` to iterate on heaps pop. 35 | 36 | ### Fixed 37 | - `.fix()` to also fix heap leaf value in addition to nodes positions. 38 | 39 | ## [4.1.2] - 2022-09-04 40 | ### Fixed 41 | - Optimize `.fix()` to run in O(n) runtime instead of O(n*log(n)). 42 | 43 | ## [4.1.1] - 2022-08-15 44 | ### Fixed 45 | - add types to package.json 46 | 47 | ## [4.1.0] - 2022-05-30 48 | ### Added 49 | - push, pop & top as alias methods for insert, extractRoot & root 50 | 51 | ## [4.0.2] - 2022-03-13 52 | ### Fixed 53 | - ts types (again). 54 | 55 | ## [4.0.1] - 2022-03-09 56 | ### Fixed 57 | - ts types. 58 | 59 | ## [4.0.0] - 2022-02-21 60 | ### Changed 61 | - better code, better world. 62 | 63 | ## [3.2.0] - 2021-08-05 64 | ### Added 65 | - CustomHeap to allow constructing a heap with a custom comparator callback. 66 | 67 | ## [3.1.1] - 2021-06-20 68 | 69 | ### Fixed 70 | - index.d.ts 71 | 72 | ## [3.1.0] - 2021-06-15 73 | 74 | ### Added 75 | - typescript. 76 | 77 | ## [3.0.1] - 2021-03-28 78 | 79 | ### Fixed 80 | - Readme 81 | 82 | ## [3.0.0] - 2020-01-17 83 | 84 | ### Changed 85 | - simplified heap nodes. preserves numbers and strings, and use object literal for key:value. 86 | - `.heapify` static function now heapify the input list as well as returning a heap insatnce. 87 | 88 | ### Added 89 | - `.fix()` to fix positions of nodes in the heap. 90 | - `.isValid` to validate heap nodes are in right positions. 91 | - `.isHeapified` static function to valida if a given list is heapified. 92 | 93 | ### Fixed 94 | - jsdoc 95 | - README 96 | 97 | ## [2.0.0] - 2020-04-06 98 | ### Changed 99 | - remove none-standard method `.serialize()`. 100 | 101 | ### Fixed 102 | - return inserted node in Min/Max Heap. 103 | - README 104 | - jsdoc 105 | 106 | ## [1.2.0] - 2020-03-07 107 | ### Added 108 | - `.leaf()` to get the max node in a MinHeap or the min node in a MaxHeap. 109 | 110 | ## [1.1.2] - 2020-03-06 111 | ### Fixed 112 | - params naming. 113 | 114 | ## [1.1.1] - 2019-12-24 115 | ### Fixed 116 | - add a table of content to readme 117 | 118 | ## [1.1.0] - 2019-12-16 119 | ### Added 120 | `.serialize()` to convert a heap to a list of serialized nodes. 121 | 122 | ### Fixed 123 | - improve README. 124 | 125 | ## [1.0.1] - 2019-12-16 126 | ### Fixed 127 | - Readme & Description. 128 | 129 | ## [1.0.0] - 2019-12-15 130 | ### Added 131 | - initial release 132 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) => { 2 | grunt.initConfig({ 3 | eslint: { 4 | src: ['src/*.js', 'test/*.test.js'] 5 | }, 6 | mochaTest: { 7 | files: ['test/*.test.js'] 8 | }, 9 | mocha_istanbul: { 10 | coverage: { 11 | src: 'test', 12 | options: { 13 | mask: '*.test.js' 14 | } 15 | } 16 | } 17 | }); 18 | 19 | grunt.loadNpmTasks('grunt-eslint'); 20 | grunt.loadNpmTasks('grunt-mocha-test'); 21 | grunt.loadNpmTasks('grunt-mocha-istanbul'); 22 | 23 | grunt.registerTask('lint', ['eslint']); 24 | grunt.registerTask('test', ['mochaTest']); 25 | grunt.registerTask('coverage', ['mocha_istanbul']); 26 | grunt.registerTask('build', ['lint', 'coverage']); 27 | }; 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Eyas Ranjous 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @datastructures-js/heap 2 | [![npm](https://img.shields.io/npm/v/@datastructures-js/heap.svg)](https://www.npmjs.com/package/@datastructures-js/heap) 3 | [![npm](https://img.shields.io/npm/dm/@datastructures-js/heap.svg)](https://www.npmjs.com/package/@datastructures-js/heap) [![npm](https://img.shields.io/badge/node-%3E=%206.0-blue.svg)](https://www.npmjs.com/package/@datastructures-js/heap) 4 | 5 | A javascript implementation for Heap data structure. Heap base class allows creating heaps using a custom compare function, and MinHeap/MaxHeap classes extend it for use cases that do not require complex comparison like primitive values and known comparison object prop. 6 | 7 | 8 | 9 | # contents 10 | * [Install](#install) 11 | * [require](#require) 12 | * [import](#import) 13 | * [API](#api) 14 | * [constructor](#constructor) 15 | * [insert (push)](#insert-push) 16 | * [extractRoot (pop)](#extractroot-pop) 17 | * [root (top)](#root-top) 18 | * [leaf](#leaf) 19 | * [size](#size) 20 | * [sort](#sort) 21 | * [isValid](#isvalid) 22 | * [fix](#fix) 23 | * [clone](#clone) 24 | * [clear](#clear) 25 | * [heapify](#heapify) 26 | * [isHeapified](#isheapified) 27 | * [Symbol.iterator](#symboliterator) 28 | * [toArray](#toarray) 29 | * [Build](#build) 30 | * [License](#license) 31 | 32 | 33 | ## install 34 | ```sh 35 | npm install --save @datastructures-js/heap 36 | ``` 37 | 38 | ## require 39 | ```js 40 | const { Heap, MinHeap, MaxHeap } = require('@datastructures-js/heap'); 41 | ``` 42 | 43 | ## import 44 | ```js 45 | import { 46 | Heap, 47 | MinHeap, 48 | MaxHeap, 49 | ICompare, 50 | IGetCompareValue, 51 | } from '@datastructures-js/heap'; 52 | ``` 53 | 54 | ## API 55 | 56 | ### constructor 57 | 58 | #### Heap 59 | constructor requires a compare function that tells the heap when to swap values. Function works similar to javascript sort callback, bigger than 0, means, swap elements. 60 | 61 | ##### TS 62 | ```ts 63 | interface ICar { 64 | year: number; 65 | price: number; 66 | } 67 | 68 | const compareCars: ICompare = (a: ICar, b: ICar) => { 69 | if (a.year > b.year) { 70 | return -1; 71 | } 72 | if (a.year < b.year) { 73 | // prioratize newest cars 74 | return 1; 75 | } 76 | // with least price 77 | return a.price < b.price ? -1 : 1; 78 | }; 79 | 80 | const carsHeap = new Heap(compareCars); 81 | ``` 82 | 83 | ##### JS 84 | ```js 85 | const compareCars = (a, b) => { 86 | if (a.year > b.year) { 87 | return -1; 88 | } 89 | if (a.year < b.year) { 90 | // prioratize newest cars 91 | return 1; 92 | } 93 | // with least price 94 | return a.price < b.price ? -1 : 1; 95 | }; 96 | 97 | const carsHeap = new Heap(compareCars); 98 | ``` 99 | 100 | #### MinHeap, MaxHeap 101 | constructor does not require a compare function and it's useful when working with primitive values like numbers, it can also be used with objects by passing a callback that indicates what object prop will be used in comparison. 102 | 103 | ##### TS 104 | ```ts 105 | const numbersHeap = new MinHeap(); 106 | 107 | interface IBid { 108 | id: number; 109 | value: number; 110 | } 111 | const getBidCompareValue: IGetCompareValue = (bid: IBid) => bid.value; 112 | const bidsHeap = new MaxHeap(getBidCompareValue); 113 | ``` 114 | 115 | ##### JS 116 | ```js 117 | const numbersHeap = new MinHeap(); 118 | const bidsHeap = new MaxHeap((bid) => bid.value); 119 | ``` 120 | 121 | ### insert (push) 122 | inserts a value in a correct position into the heap in O(log(n)) runtime. 123 | 124 | ```js 125 | const cars = [ 126 | { year: 2013, price: 35000 }, 127 | { year: 2010, price: 2000 }, 128 | { year: 2013, price: 30000 }, 129 | { year: 2017, price: 50000 }, 130 | { year: 2013, price: 25000 }, 131 | { year: 2015, price: 40000 }, 132 | { year: 2022, price: 70000 } 133 | ]; 134 | cars.forEach((car) => carsHeap.insert(car)); 135 | 136 | const numbers = [3, -2, 5, 0, -1, -5, 4]; 137 | numbers.forEach((num) => numbersHeap.push(num)); 138 | 139 | const bids = [ 140 | { id: 1, value: 1000 }, 141 | { id: 2, value: 20000 }, 142 | { id: 3, value: 1000 }, 143 | { id: 4, value: 1500 }, 144 | { id: 5, value: 12000 }, 145 | { id: 6, value: 4000 }, 146 | { id: 7, value: 8000 } 147 | ]; 148 | bids.forEach((bid) => bidsHeap.insert(bid)); 149 | ``` 150 | 151 | ### extractRoot (pop) 152 | removes and returns the root (top) value of the heap in O(log(n)) runtime. 153 | 154 | ```js 155 | while (!carsHeap.isEmpty()) { 156 | console.log(carsHeap.extractRoot()); 157 | } 158 | /* 159 | { year: 2022, price: 70000 } 160 | { year: 2017, price: 50000 } 161 | { year: 2015, price: 40000 } 162 | { year: 2013, price: 25000 } 163 | { year: 2013, price: 30000 } 164 | { year: 2013, price: 35000 } 165 | { year: 2010, price: 2000 } 166 | */ 167 | 168 | while (!numbersHeap.isEmpty()) { 169 | console.log(numbersHeap.pop()); 170 | } 171 | /* 172 | -5 173 | -2 174 | -1 175 | 0 176 | 3 177 | 4 178 | 5 179 | */ 180 | 181 | while (!bidsHeap.isEmpty()) { 182 | console.log(bidsHeap.extractRoot()); 183 | } 184 | /* 185 | { id: 2, value: 20000 } 186 | { id: 5, value: 12000 } 187 | { id: 7, value: 8000 } 188 | { id: 6, value: 4000 } 189 | { id: 4, value: 1500 } 190 | { id: 3, value: 1000 } 191 | { id: 1, value: 1000 } 192 | */ 193 | ``` 194 | 195 | ### root (top) 196 | returns the root node without removing it. 197 | 198 | ```js 199 | // reload values 200 | cars.forEach((car) => carsHeap.insert(car)); 201 | numbers.forEach((num) => numbersHeap.insert(num)); 202 | bids.forEach((bid) => bidsHeap.insert(bid)); 203 | 204 | console.log(carsHeap.root()); // { year: 2022, price: 70000 } 205 | console.log(numbersHeap.top()); // -5 206 | console.log(bidsHeap.top()); // { id: 2, value: 20000 } 207 | ``` 208 | 209 | ### leaf 210 | returns a leaf node in the heap. 211 | 212 | ```js 213 | console.log(carsHeap.leaf()); // { year: 2010, price: 2000 } 214 | console.log(numbersHeap.leaft()); // 5 215 | console.log(bidsHeap.leaf()); // { id: 1, value: 1000 } 216 | ``` 217 | 218 | ### size 219 | returns the number of nodes in the heap. 220 | 221 | ```js 222 | console.log(carsHeap.size()); // 7 223 | console.log(numbersHeap.size()); // 7 224 | console.log(bidsHeap.size()); // 7 225 | ``` 226 | 227 | ### sort 228 | returns a list of sorted values in O(n*log(n)) runtime, based on the comparison logic, and in reverse order. In MaxHeap it returns the list of sorted values in ascending order, and in descending order in MinHeap. sort mutates the node positions in the heap, to prevent that, you can sort a clone of the heap. 229 | 230 | ```js 231 | console.log(carsHeap.sort()); 232 | /* 233 | [ 234 | { year: 2010, price: 2000 }, 235 | { year: 2013, price: 35000 }, 236 | { year: 2013, price: 30000 }, 237 | { year: 2013, price: 25000 }, 238 | { year: 2015, price: 40000 }, 239 | { year: 2017, price: 50000 }, 240 | { year: 2022, price: 70000 } 241 | ] 242 | */ 243 | 244 | console.log(numbersHeap.sort()); 245 | // [5, 4, 3, 0, -1, -2, -5] 246 | 247 | console.log(bidsHeap.sort()); 248 | /* 249 | [ 250 | { id: 1, value: 1000 }, 251 | { id: 3, value: 1000 }, 252 | { id: 4, value: 1500 }, 253 | { id: 6, value: 4000 }, 254 | { id: 7, value: 8000 }, 255 | { id: 5, value: 12000 }, 256 | { id: 2, value: 20000 } 257 | ] 258 | */ 259 | ``` 260 | 261 | ### isValid 262 | checks if the heap is valid (all nodes are positioned correctly) in log(n) runtime. 263 | 264 | ```js 265 | // after sorting the heaps directly, node positions are mutated 266 | console.log(carsHeap.isValid()); // false 267 | console.log(numbersHeap.isValid()); // false 268 | console.log(bidsHeap.isValid()); // false 269 | ``` 270 | 271 | ### fix 272 | fixes the heap by making the necessary swaps between nodes in O(n) runtime. 273 | 274 | ```js 275 | console.log(carsHeap.fix().isValid()); // true 276 | 277 | console.log(numbersHeap.fix().isValid()); // true 278 | 279 | console.log(bidsHeap.fix().isValid()); // true 280 | ``` 281 | 282 | ### clone 283 | creates a shallow copy of the heap. 284 | 285 | ```js 286 | console.log(carsHeap.clone().sort()); 287 | /* 288 | [ 289 | { year: 2010, price: 2000 }, 290 | { year: 2013, price: 35000 }, 291 | { year: 2013, price: 30000 }, 292 | { year: 2013, price: 25000 }, 293 | { year: 2015, price: 40000 }, 294 | { year: 2017, price: 50000 }, 295 | { year: 2022, price: 70000 } 296 | ] 297 | */ 298 | 299 | console.log(numbersHeap.clone().sort()); 300 | // [5, 4, 3, 0, -1, -2, -5] 301 | 302 | console.log(bidsHeap.clone().sort()); 303 | /* 304 | [ 305 | { id: 1, value: 1000 }, 306 | { id: 3, value: 1000 }, 307 | { id: 4, value: 1500 }, 308 | { id: 6, value: 4000 }, 309 | { id: 7, value: 8000 }, 310 | { id: 5, value: 12000 }, 311 | { id: 2, value: 20000 } 312 | ] 313 | */ 314 | 315 | // original heaps not mutated 316 | console.log(carsHeap.isValid()); // true 317 | console.log(numbersHeap.isValid()); // true 318 | console.log(bidsHeap.isValid()); // true 319 | ``` 320 | 321 | ### clear 322 | clears the heap. 323 | 324 | ```js 325 | carsHeap.clear(); 326 | numbersHeap.clear(); 327 | bidsHeap.clear(); 328 | 329 | console.log(carsHeap.size()); // 0 330 | console.log(numbersHeap.size()); // 0 331 | console.log(bidsHeap.size()); // 0 332 | ``` 333 | 334 | ### heapify 335 | converts a list of values into a heap without using an additional space in O(n) runtime. 336 | 337 | ##### TS 338 | ```ts 339 | const heapifiedCars = Heap.heapify(cars, compareCars); 340 | console.log(heapifiedCars.isValid()); // true 341 | // list is heapified 342 | console.log(cars); 343 | /* 344 | [ 345 | { year: 2022, price: 70000 }, 346 | { year: 2013, price: 25000 }, 347 | { year: 2017, price: 50000 }, 348 | { year: 2010, price: 2000 }, 349 | { year: 2013, price: 30000 }, 350 | { year: 2013, price: 35000 }, 351 | { year: 2015, price: 40000 } 352 | ] 353 | */ 354 | 355 | const heapifiedNumbers = MinHeap.heapify(numbers); 356 | console.log(heapifiedNumbers.isValid()); // true 357 | console.log(numbers); 358 | // [-5, -1, -2, 3, 0, 5, 4] 359 | 360 | const heapifiedBids = MaxHeap.heapify(bids, (bid) => bid.value); 361 | console.log(heapifiedBids.isValid()); // true 362 | console.log(bids); 363 | /* 364 | [ 365 | { id: 2, value: 20000 }, 366 | { id: 5, value: 12000 }, 367 | { id: 7, value: 8000 }, 368 | { id: 1, value: 1000 }, 369 | { id: 4, value: 1500 }, 370 | { id: 3, value: 1000 }, 371 | { id: 6, value: 4000 } 372 | ] 373 | */ 374 | ``` 375 | 376 | ##### JS 377 | ```ts 378 | const heapifiedCars = Heap.heapify(cars, compareCars); 379 | console.log(heapifiedCars.isValid()); // true 380 | console.log(heapifiedCars.leaf()); // { year: 2010, price: 2000 } 381 | 382 | // original list is heapified 383 | console.log(cars); 384 | /* 385 | [ 386 | { year: 2022, price: 70000 }, 387 | { year: 2013, price: 25000 }, 388 | { year: 2017, price: 50000 }, 389 | { year: 2010, price: 2000 }, 390 | { year: 2013, price: 30000 }, 391 | { year: 2013, price: 35000 }, 392 | { year: 2015, price: 40000 } 393 | ] 394 | */ 395 | 396 | const heapifiedNumbers = MinHeap.heapify(numbers); 397 | console.log(heapifiedNumbers.isValid()); // true 398 | console.log(heapifiedNumbers.leaf()); // 5 399 | console.log(numbers); 400 | // [-5, -1, -2, 3, 0, 5, 4] 401 | 402 | const heapifiedBids = MaxHeap.heapify(bids, (bid) => bid.value); 403 | console.log(heapifiedBids.isValid()); // true 404 | console.log(heapifiedBids.leaf()); // { id: 1, value: 1000 } 405 | console.log(bids); 406 | /* 407 | [ 408 | { id: 2, value: 20000 }, 409 | { id: 5, value: 12000 }, 410 | { id: 7, value: 8000 }, 411 | { id: 1, value: 1000 }, 412 | { id: 4, value: 1500 }, 413 | { id: 3, value: 1000 }, 414 | { id: 6, value: 4000 } 415 | ] 416 | */ 417 | ``` 418 | 419 | ### isHeapified 420 | Checks if a given list is heapified. 421 | 422 | #### TS 423 | ```ts 424 | console.log(Heap.isHeapified(cars, compareCars)); // true 425 | console.log(MinHeap.isHeapified(numbers)); // true 426 | console.log(MaxHeap.isHeapified(bids, (bid) => bid.value)); // true 427 | ``` 428 | 429 | #### JS 430 | ```js 431 | console.log(Heap.isHeapified(cars, compareCars)); // true 432 | console.log(MinHeap.isHeapified(numbers)); // true 433 | console.log(MaxHeap.isHeapified(bids, (bid) => bid.value)); // true 434 | ``` 435 | 436 | ### Symbol.iterator 437 | The heaps implement a Symbol.iterator that makes them iterable on `pop`. 438 | ```js 439 | console.log([...carsHeap]); 440 | /* 441 | [ 442 | { year: 2022, price: 70000 }, 443 | { year: 2017, price: 50000 }, 444 | { year: 2015, price: 40000 }, 445 | { year: 2013, price: 25000 }, 446 | { year: 2013, price: 30000 }, 447 | { year: 2013, price: 35000 }, 448 | { year: 2010, price: 2000 } 449 | ] 450 | */ 451 | console.log(carsHeap.size()); // 0 452 | 453 | console.log([...numbersHeap]); // [5, -5, -2, -1, 0, 3, 4] 454 | console.log(numbersHeap.size()); // 0 455 | 456 | for (const bid of bidsHeap) { 457 | console.log(bid); 458 | } 459 | /* 460 | { id: 2, value: 20000 } 461 | { id: 5, value: 12000 } 462 | { id: 7, value: 8000 } 463 | { id: 6, value: 4000 } 464 | { id: 4, value: 1500 } 465 | { id: 1, value: 1000 } 466 | { id: 3, value: 1000 } 467 | */ 468 | console.log(bidsHeap.size()); // 0 469 | ``` 470 | 471 | ### toArray 472 | 473 | Converts the heap to a cloned array without sorting. 474 | 475 | ```js 476 | console.log(carsHeap.toArray()); 477 | /* 478 | [ 479 | { year: 2022, price: 70000 }, 480 | { year: 2017, price: 50000 }, 481 | { year: 2015, price: 40000 }, 482 | { year: 2013, price: 25000 }, 483 | { year: 2013, price: 30000 }, 484 | { year: 2013, price: 35000 }, 485 | { year: 2010, price: 2000 } 486 | ] 487 | */ 488 | 489 | 490 | console.log(numbersHeap.toArray()); // [5, -5, -2, -1, 0, 3, 4] 491 | 492 | console.log(bidsHeap.toArray()); 493 | 494 | /* 495 | [ 496 | { id: 2, value: 20000 }, 497 | { id: 5, value: 12000 }, 498 | { id: 7, value: 8000 }, 499 | { id: 6, value: 4000 }, 500 | { id: 4, value: 1500 }, 501 | { id: 1, value: 1000 }, 502 | { id: 3, value: 1000 } 503 | ] 504 | */ 505 | 506 | ``` 507 | 508 | ## Build 509 | 510 | ``` 511 | grunt build 512 | ``` 513 | 514 | ## License 515 | The MIT License. Full License is [here](https://github.com/datastructures-js/heap/blob/master/LICENSE) 516 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { Heap, ICompare } from './src/heap'; 2 | import { MinHeap } from './src/minHeap'; 3 | import { MaxHeap, IGetCompareValue } from './src/maxHeap'; 4 | 5 | export { Heap } 6 | export { ICompare } 7 | export { IGetCompareValue } 8 | export { MinHeap } 9 | export { MaxHeap } 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { Heap } = require('./src/heap'); 2 | const { MinHeap } = require('./src/minHeap'); 3 | const { MaxHeap } = require('./src/maxHeap'); 4 | 5 | exports.Heap = Heap; 6 | exports.MinHeap = MinHeap; 7 | exports.MaxHeap = MaxHeap; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@datastructures-js/heap", 3 | "version": "4.3.4", 4 | "description": "Min/Max Heap & Heap Sort implementation in javascript", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "scripts": { 8 | "test": "grunt test" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/datastructures-js/heap.git" 13 | }, 14 | "keywords": [ 15 | "heap", 16 | "min heap", 17 | "min heap data structure", 18 | "max heap", 19 | "max heap data structure", 20 | "heap js", 21 | "heap data structure", 22 | "heap es6", 23 | "heap sort", 24 | "heapify" 25 | ], 26 | "author": "Eyas Ranjous ", 27 | "license": "MIT", 28 | "bugs": { 29 | "url": "https://github.com/datastructures-js/heap/issues" 30 | }, 31 | "homepage": "https://github.com/datastructures-js/heap#readme", 32 | "devDependencies": { 33 | "chai": "^4.2.0", 34 | "eslint": "^6.7.2", 35 | "eslint-config-airbnb-base": "^14.0.0", 36 | "eslint-plugin-import": "^2.19.1", 37 | "grunt": "^1.0.4", 38 | "grunt-eslint": "^22.0.0", 39 | "grunt-mocha-istanbul": "^5.0.2", 40 | "grunt-mocha-test": "^0.13.3", 41 | "istanbul": "^0.4.5", 42 | "mocha": "^6.2.2" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/heap.d.ts: -------------------------------------------------------------------------------- 1 | export interface ICompare { 2 | (a: T, b: T): number; 3 | } 4 | 5 | export class Heap implements Iterable { 6 | constructor(comparator: ICompare, values?: T[], leaf?: T); 7 | toArray(): T[]; 8 | [Symbol.iterator](): Iterator; 9 | insert(value: T): Heap; 10 | push(value: T): Heap; 11 | extractRoot(): T | null; 12 | pop(): T | null; 13 | sort(): T[]; 14 | fix(): Heap; 15 | isValid(): boolean; 16 | clone(): Heap; 17 | root(): T | null; 18 | top(): T | null; 19 | leaf(): T | null; 20 | size(): number; 21 | isEmpty(): boolean; 22 | clear(): void; 23 | static heapify(values: T[], comparator: ICompare): Heap; 24 | static isHeapified(values: T[], comparator: ICompare): boolean; 25 | } 26 | -------------------------------------------------------------------------------- /src/heap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license MIT 3 | * @copyright 2020 Eyas Ranjous 4 | * 5 | * @class 6 | */ 7 | class Heap { 8 | /** 9 | * @param {function} compare 10 | * @param {array} [_values] 11 | * @param {number|string|object} [_leaf] 12 | */ 13 | constructor(compare, _values, _leaf) { 14 | if (typeof compare !== 'function') { 15 | throw new Error('Heap constructor expects a compare function'); 16 | } 17 | this._compare = compare; 18 | this._nodes = Array.isArray(_values) ? _values : []; 19 | this._leaf = _leaf || null; 20 | } 21 | 22 | /** 23 | * Converts the heap to a cloned array without sorting. 24 | * @public 25 | * @returns {Array} 26 | */ 27 | toArray() { 28 | return Array.from(this._nodes); 29 | } 30 | 31 | /** 32 | * Checks if a parent has a left child 33 | * @private 34 | */ 35 | _hasLeftChild(parentIndex) { 36 | const leftChildIndex = (parentIndex * 2) + 1; 37 | return leftChildIndex < this.size(); 38 | } 39 | 40 | /** 41 | * Checks if a parent has a right child 42 | * @private 43 | */ 44 | _hasRightChild(parentIndex) { 45 | const rightChildIndex = (parentIndex * 2) + 2; 46 | return rightChildIndex < this.size(); 47 | } 48 | 49 | /** 50 | * Compares two nodes 51 | * @private 52 | */ 53 | _compareAt(i, j) { 54 | return this._compare(this._nodes[i], this._nodes[j]); 55 | } 56 | 57 | /** 58 | * Swaps two nodes in the heap 59 | * @private 60 | */ 61 | _swap(i, j) { 62 | const temp = this._nodes[i]; 63 | this._nodes[i] = this._nodes[j]; 64 | this._nodes[j] = temp; 65 | } 66 | 67 | /** 68 | * Checks if parent and child should be swapped 69 | * @private 70 | */ 71 | _shouldSwap(parentIndex, childIndex) { 72 | if (parentIndex < 0 || parentIndex >= this.size()) { 73 | return false; 74 | } 75 | 76 | if (childIndex < 0 || childIndex >= this.size()) { 77 | return false; 78 | } 79 | 80 | return this._compareAt(parentIndex, childIndex) > 0; 81 | } 82 | 83 | /** 84 | * Compares children of a parent 85 | * @private 86 | */ 87 | _compareChildrenOf(parentIndex) { 88 | if (!this._hasLeftChild(parentIndex) && !this._hasRightChild(parentIndex)) { 89 | return -1; 90 | } 91 | 92 | const leftChildIndex = (parentIndex * 2) + 1; 93 | const rightChildIndex = (parentIndex * 2) + 2; 94 | 95 | if (!this._hasLeftChild(parentIndex)) { 96 | return rightChildIndex; 97 | } 98 | 99 | if (!this._hasRightChild(parentIndex)) { 100 | return leftChildIndex; 101 | } 102 | 103 | const compare = this._compareAt(leftChildIndex, rightChildIndex); 104 | return compare > 0 ? rightChildIndex : leftChildIndex; 105 | } 106 | 107 | /** 108 | * Compares two children before a position 109 | * @private 110 | */ 111 | _compareChildrenBefore(index, leftChildIndex, rightChildIndex) { 112 | const compare = this._compareAt(rightChildIndex, leftChildIndex); 113 | 114 | if (compare <= 0 && rightChildIndex < index) { 115 | return rightChildIndex; 116 | } 117 | 118 | return leftChildIndex; 119 | } 120 | 121 | /** 122 | * Recursively bubbles up a node if it's in a wrong position 123 | * @private 124 | */ 125 | _heapifyUp(startIndex) { 126 | let childIndex = startIndex; 127 | let parentIndex = Math.floor((childIndex - 1) / 2); 128 | 129 | while (this._shouldSwap(parentIndex, childIndex)) { 130 | this._swap(parentIndex, childIndex); 131 | childIndex = parentIndex; 132 | parentIndex = Math.floor((childIndex - 1) / 2); 133 | } 134 | } 135 | 136 | /** 137 | * Recursively bubbles down a node if it's in a wrong position 138 | * @private 139 | */ 140 | _heapifyDown(startIndex) { 141 | let parentIndex = startIndex; 142 | let childIndex = this._compareChildrenOf(parentIndex); 143 | 144 | while (this._shouldSwap(parentIndex, childIndex)) { 145 | this._swap(parentIndex, childIndex); 146 | parentIndex = childIndex; 147 | childIndex = this._compareChildrenOf(parentIndex); 148 | } 149 | } 150 | 151 | /** 152 | * Recursively bubbles down a node before a given index 153 | * @private 154 | */ 155 | _heapifyDownUntil(index) { 156 | let parentIndex = 0; 157 | let leftChildIndex = 1; 158 | let rightChildIndex = 2; 159 | let childIndex; 160 | 161 | while (leftChildIndex < index) { 162 | childIndex = this._compareChildrenBefore( 163 | index, 164 | leftChildIndex, 165 | rightChildIndex 166 | ); 167 | 168 | if (this._shouldSwap(parentIndex, childIndex)) { 169 | this._swap(parentIndex, childIndex); 170 | } 171 | 172 | parentIndex = childIndex; 173 | leftChildIndex = (parentIndex * 2) + 1; 174 | rightChildIndex = (parentIndex * 2) + 2; 175 | } 176 | } 177 | 178 | /** 179 | * Inserts a new value into the heap 180 | * @public 181 | * @param {number|string|object} value 182 | * @returns {Heap} 183 | */ 184 | insert(value) { 185 | this._nodes.push(value); 186 | this._heapifyUp(this.size() - 1); 187 | if (this._leaf === null || this._compare(value, this._leaf) > 0) { 188 | this._leaf = value; 189 | } 190 | return this; 191 | } 192 | 193 | /** 194 | * Inserts a new value into the heap 195 | * @public 196 | * @param {number|string|object} value 197 | * @returns {Heap} 198 | */ 199 | push(value) { 200 | return this.insert(value); 201 | } 202 | 203 | /** 204 | * Removes and returns the root node in the heap 205 | * @public 206 | * @returns {number|string|object} 207 | */ 208 | extractRoot() { 209 | if (this.isEmpty()) { 210 | return null; 211 | } 212 | 213 | const root = this.root(); 214 | this._nodes[0] = this._nodes[this.size() - 1]; 215 | this._nodes.pop(); 216 | this._heapifyDown(0); 217 | 218 | if (root === this._leaf) { 219 | this._leaf = null; 220 | } 221 | 222 | return root; 223 | } 224 | 225 | /** 226 | * Removes and returns the root node in the heap 227 | * @public 228 | * @returns {number|string|object} 229 | */ 230 | pop() { 231 | return this.extractRoot(); 232 | } 233 | 234 | /** 235 | * Applies heap sort and return the values sorted by priority 236 | * @public 237 | * @returns {array} 238 | */ 239 | sort() { 240 | for (let i = this.size() - 1; i > 0; i -= 1) { 241 | this._swap(0, i); 242 | this._heapifyDownUntil(i); 243 | } 244 | return this._nodes; 245 | } 246 | 247 | /** 248 | * Fixes node positions in the heap 249 | * @public 250 | * @returns {Heap} 251 | */ 252 | fix() { 253 | // fix node positions 254 | for (let i = Math.floor(this.size() / 2) - 1; i >= 0; i -= 1) { 255 | this._heapifyDown(i); 256 | } 257 | 258 | // fix leaf value 259 | for (let i = Math.floor(this.size() / 2); i < this.size(); i += 1) { 260 | const value = this._nodes[i]; 261 | if (this._leaf === null || this._compare(value, this._leaf) > 0) { 262 | this._leaf = value; 263 | } 264 | } 265 | 266 | return this; 267 | } 268 | 269 | /** 270 | * Verifies that all heap nodes are in the right position 271 | * @public 272 | * @returns {boolean} 273 | */ 274 | isValid() { 275 | const isValidRecursive = (parentIndex) => { 276 | let isValidLeft = true; 277 | let isValidRight = true; 278 | 279 | if (this._hasLeftChild(parentIndex)) { 280 | const leftChildIndex = (parentIndex * 2) + 1; 281 | if (this._compareAt(parentIndex, leftChildIndex) > 0) { 282 | return false; 283 | } 284 | isValidLeft = isValidRecursive(leftChildIndex); 285 | } 286 | 287 | if (this._hasRightChild(parentIndex)) { 288 | const rightChildIndex = (parentIndex * 2) + 2; 289 | if (this._compareAt(parentIndex, rightChildIndex) > 0) { 290 | return false; 291 | } 292 | isValidRight = isValidRecursive(rightChildIndex); 293 | } 294 | 295 | return isValidLeft && isValidRight; 296 | }; 297 | 298 | return isValidRecursive(0); 299 | } 300 | 301 | /** 302 | * Returns a shallow copy of the heap 303 | * @public 304 | * @returns {Heap} 305 | */ 306 | clone() { 307 | return new Heap(this._compare, this._nodes.slice(), this._leaf); 308 | } 309 | 310 | /** 311 | * Returns the root node in the heap 312 | * @public 313 | * @returns {number|string|object} 314 | */ 315 | root() { 316 | if (this.isEmpty()) { 317 | return null; 318 | } 319 | 320 | return this._nodes[0]; 321 | } 322 | 323 | /** 324 | * Returns the root node in the heap 325 | * @public 326 | * @returns {number|string|object} 327 | */ 328 | top() { 329 | return this.root(); 330 | } 331 | 332 | /** 333 | * Returns a leaf node in the heap 334 | * @public 335 | * @returns {number|string|object} 336 | */ 337 | leaf() { 338 | return this._leaf; 339 | } 340 | 341 | /** 342 | * Returns the number of nodes in the heap 343 | * @public 344 | * @returns {number} 345 | */ 346 | size() { 347 | return this._nodes.length; 348 | } 349 | 350 | /** 351 | * Checks if the heap is empty 352 | * @public 353 | * @returns {boolean} 354 | */ 355 | isEmpty() { 356 | return this.size() === 0; 357 | } 358 | 359 | /** 360 | * Clears the heap 361 | * @public 362 | */ 363 | clear() { 364 | this._nodes = []; 365 | this._leaf = null; 366 | } 367 | 368 | /** 369 | * Implements an iterable on the heap 370 | * @public 371 | */ 372 | [Symbol.iterator]() { 373 | let size = this.size(); 374 | return { 375 | next: () => { 376 | size -= 1; 377 | return { 378 | value: this.pop(), 379 | done: size === -1 380 | }; 381 | } 382 | }; 383 | } 384 | 385 | /** 386 | * Builds a heap from a array of values 387 | * @public 388 | * @static 389 | * @param {array} values 390 | * @param {function} compare 391 | * @returns {Heap} 392 | */ 393 | static heapify(values, compare) { 394 | if (!Array.isArray(values)) { 395 | throw new Error('Heap.heapify expects an array of values'); 396 | } 397 | 398 | if (typeof compare !== 'function') { 399 | throw new Error('Heap.heapify expects a compare function'); 400 | } 401 | 402 | return new Heap(compare, values).fix(); 403 | } 404 | 405 | /** 406 | * Checks if a list of values is a valid heap 407 | * @public 408 | * @static 409 | * @param {array} values 410 | * @param {function} compare 411 | * @returns {boolean} 412 | */ 413 | static isHeapified(values, compare) { 414 | return new Heap(compare, values).isValid(); 415 | } 416 | } 417 | 418 | exports.Heap = Heap; 419 | -------------------------------------------------------------------------------- /src/maxHeap.d.ts: -------------------------------------------------------------------------------- 1 | import { Heap } from "./heap"; 2 | 3 | export interface IGetCompareValue { 4 | (value: T): number | string; 5 | } 6 | 7 | export class MaxHeap implements Iterable { 8 | constructor(getCompareValue?: IGetCompareValue, _heap?: Heap); 9 | toArray(): T[]; 10 | [Symbol.iterator](): Iterator; 11 | insert(value: T): MaxHeap; 12 | push(value: T): MaxHeap; 13 | extractRoot(): T | null; 14 | pop(): T | null; 15 | sort(): T[]; 16 | fix(): MaxHeap; 17 | isValid(): boolean; 18 | clone(): MaxHeap; 19 | root(): T | null; 20 | top(): T | null; 21 | leaf(): T | null; 22 | size(): number; 23 | isEmpty(): boolean; 24 | clear(): void; 25 | static heapify( 26 | values: T[], 27 | getCompareValue?: IGetCompareValue 28 | ): MaxHeap; 29 | static isHeapified( 30 | values: T[], 31 | getCompareValue?: IGetCompareValue 32 | ): boolean; 33 | } 34 | -------------------------------------------------------------------------------- /src/maxHeap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license MIT 3 | * @copyright 2020 Eyas Ranjous 4 | */ 5 | 6 | const { Heap } = require('./heap'); 7 | 8 | const getMaxCompare = (getCompareValue) => (a, b) => { 9 | const aVal = typeof getCompareValue === 'function' ? getCompareValue(a) : a; 10 | const bVal = typeof getCompareValue === 'function' ? getCompareValue(b) : b; 11 | return aVal < bVal ? 1 : -1; 12 | }; 13 | 14 | /** 15 | * @class MaxHeap 16 | * @extends Heap 17 | */ 18 | class MaxHeap { 19 | /** 20 | * @param {function} [getCompareValue] 21 | * @param {Heap} [_heap] 22 | */ 23 | constructor(getCompareValue, _heap) { 24 | this._getCompareValue = getCompareValue; 25 | this._heap = _heap || new Heap(getMaxCompare(getCompareValue)); 26 | } 27 | 28 | /** 29 | * Inserts a new value into the heap 30 | * @public 31 | * @param {number|string|object} value 32 | * @returns {MaxHeap} 33 | */ 34 | insert(value) { 35 | return this._heap.insert(value); 36 | } 37 | 38 | /** 39 | * Inserts a new value into the heap 40 | * @public 41 | * @param {number|string|object} value 42 | * @returns {Heap} 43 | */ 44 | push(value) { 45 | return this.insert(value); 46 | } 47 | 48 | /** 49 | * Removes and returns the root node in the heap 50 | * @public 51 | * @returns {number|string|object} 52 | */ 53 | extractRoot() { 54 | return this._heap.extractRoot(); 55 | } 56 | 57 | /** 58 | * Removes and returns the root node in the heap 59 | * @public 60 | * @returns {number|string|object} 61 | */ 62 | pop() { 63 | return this.extractRoot(); 64 | } 65 | 66 | /** 67 | * Applies heap sort and return the values sorted by priority 68 | * @public 69 | * @returns {array} 70 | */ 71 | sort() { 72 | return this._heap.sort(); 73 | } 74 | 75 | /** 76 | * Converts the heap to a cloned array without sorting. 77 | * @public 78 | * @returns {Array} 79 | */ 80 | toArray() { 81 | return Array.from(this._heap._nodes); 82 | } 83 | 84 | /** 85 | * Fixes node positions in the heap 86 | * @public 87 | * @returns {MaxHeap} 88 | */ 89 | fix() { 90 | return this._heap.fix(); 91 | } 92 | 93 | /** 94 | * Verifies that all heap nodes are in the right position 95 | * @public 96 | * @returns {boolean} 97 | */ 98 | isValid() { 99 | return this._heap.isValid(); 100 | } 101 | 102 | /** 103 | * Returns the root node in the heap 104 | * @public 105 | * @returns {number|string|object} 106 | */ 107 | root() { 108 | return this._heap.root(); 109 | } 110 | 111 | /** 112 | * Returns the root node in the heap 113 | * @public 114 | * @returns {number|string|object} 115 | */ 116 | top() { 117 | return this.root(); 118 | } 119 | 120 | /** 121 | * Returns a leaf node in the heap 122 | * @public 123 | * @returns {number|string|object} 124 | */ 125 | leaf() { 126 | return this._heap.leaf(); 127 | } 128 | 129 | /** 130 | * Returns the number of nodes in the heap 131 | * @public 132 | * @returns {number} 133 | */ 134 | size() { 135 | return this._heap.size(); 136 | } 137 | 138 | /** 139 | * Checks if the heap is empty 140 | * @public 141 | * @returns {boolean} 142 | */ 143 | isEmpty() { 144 | return this._heap.isEmpty(); 145 | } 146 | 147 | /** 148 | * Clears the heap 149 | * @public 150 | */ 151 | clear() { 152 | this._heap.clear(); 153 | } 154 | 155 | /** 156 | * Returns a shallow copy of the MaxHeap 157 | * @public 158 | * @returns {MaxHeap} 159 | */ 160 | clone() { 161 | return new MaxHeap(this._getCompareValue, this._heap.clone()); 162 | } 163 | 164 | /** 165 | * Implements an iterable on the heap 166 | * @public 167 | */ 168 | [Symbol.iterator]() { 169 | let size = this.size(); 170 | return { 171 | next: () => { 172 | size -= 1; 173 | return { 174 | value: this.pop(), 175 | done: size === -1 176 | }; 177 | } 178 | }; 179 | } 180 | 181 | /** 182 | * Builds a MaxHeap from an array 183 | * @public 184 | * @static 185 | * @param {array} values 186 | * @param {function} [getCompareValue] 187 | * @returns {MaxHeap} 188 | */ 189 | static heapify(values, getCompareValue) { 190 | if (!Array.isArray(values)) { 191 | throw new Error('MaxHeap.heapify expects an array'); 192 | } 193 | const heap = new Heap(getMaxCompare(getCompareValue), values); 194 | return new MaxHeap(getCompareValue, heap).fix(); 195 | } 196 | 197 | /** 198 | * Checks if a list of values is a valid max heap 199 | * @public 200 | * @static 201 | * @param {array} values 202 | * @param {function} [getCompareValue] 203 | * @returns {boolean} 204 | */ 205 | static isHeapified(values, getCompareValue) { 206 | const heap = new Heap(getMaxCompare(getCompareValue), values); 207 | return new MaxHeap(getCompareValue, heap).isValid(); 208 | } 209 | } 210 | 211 | exports.MaxHeap = MaxHeap; 212 | -------------------------------------------------------------------------------- /src/minHeap.d.ts: -------------------------------------------------------------------------------- 1 | import { Heap } from "./heap"; 2 | import { IGetCompareValue } from "./maxHeap"; 3 | 4 | export class MinHeap implements Iterable { 5 | constructor(getCompareValue?: IGetCompareValue, _heap?: Heap); 6 | toArray(): T[]; 7 | [Symbol.iterator](): Iterator; 8 | insert(value: T): MinHeap; 9 | push(value: T): MinHeap; 10 | extractRoot(): T | null; 11 | pop(): T | null; 12 | sort(): T[]; 13 | fix(): MinHeap; 14 | isValid(): boolean; 15 | clone(): MinHeap; 16 | root(): T | null; 17 | top(): T | null; 18 | leaf(): T | null; 19 | size(): number; 20 | isEmpty(): boolean; 21 | clear(): void; 22 | static heapify( 23 | values: T[], 24 | getCompareValue?: IGetCompareValue 25 | ): MinHeap; 26 | static isHeapified( 27 | values: T[], 28 | getCompareValue?: IGetCompareValue 29 | ): boolean; 30 | } 31 | -------------------------------------------------------------------------------- /src/minHeap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license MIT 3 | * @copyright 2020 Eyas Ranjous 4 | */ 5 | 6 | const { Heap } = require('./heap'); 7 | 8 | const getMinCompare = (getCompareValue) => (a, b) => { 9 | const aVal = typeof getCompareValue === 'function' ? getCompareValue(a) : a; 10 | const bVal = typeof getCompareValue === 'function' ? getCompareValue(b) : b; 11 | return aVal <= bVal ? -1 : 1; 12 | }; 13 | 14 | /** 15 | * @class MinHeap 16 | * @extends Heap 17 | */ 18 | class MinHeap { 19 | /** 20 | * @param {function} [getCompareValue] 21 | * @param {Heap} [_heap] 22 | */ 23 | constructor(getCompareValue, _heap) { 24 | this._getCompareValue = getCompareValue; 25 | this._heap = _heap || new Heap(getMinCompare(getCompareValue)); 26 | } 27 | 28 | /** 29 | * Converts the heap to a cloned array without sorting. 30 | * @public 31 | * @returns {Array} 32 | */ 33 | toArray() { 34 | return Array.from(this._heap._nodes); 35 | } 36 | 37 | /** 38 | * Inserts a new value into the heap 39 | * @public 40 | * @param {number|string|object} value 41 | * @returns {MinHeap} 42 | */ 43 | insert(value) { 44 | return this._heap.insert(value); 45 | } 46 | 47 | /** 48 | * Inserts a new value into the heap 49 | * @public 50 | * @param {number|string|object} value 51 | * @returns {Heap} 52 | */ 53 | push(value) { 54 | return this.insert(value); 55 | } 56 | 57 | /** 58 | * Removes and returns the root node in the heap 59 | * @public 60 | * @returns {number|string|object} 61 | */ 62 | extractRoot() { 63 | return this._heap.extractRoot(); 64 | } 65 | 66 | /** 67 | * Removes and returns the root node in the heap 68 | * @public 69 | * @returns {number|string|object} 70 | */ 71 | pop() { 72 | return this.extractRoot(); 73 | } 74 | 75 | /** 76 | * Applies heap sort and return the values sorted by priority 77 | * @public 78 | * @returns {array} 79 | */ 80 | sort() { 81 | return this._heap.sort(); 82 | } 83 | 84 | /** 85 | * Fixes node positions in the heap 86 | * @public 87 | * @returns {MinHeap} 88 | */ 89 | fix() { 90 | return this._heap.fix(); 91 | } 92 | 93 | /** 94 | * Verifies that all heap nodes are in the right position 95 | * @public 96 | * @returns {boolean} 97 | */ 98 | isValid() { 99 | return this._heap.isValid(); 100 | } 101 | 102 | /** 103 | * Returns the root node in the heap 104 | * @public 105 | * @returns {number|string|object} 106 | */ 107 | root() { 108 | return this._heap.root(); 109 | } 110 | 111 | /** 112 | * Returns the root node in the heap 113 | * @public 114 | * @returns {number|string|object} 115 | */ 116 | top() { 117 | return this.root(); 118 | } 119 | 120 | /** 121 | * Returns a leaf node in the heap 122 | * @public 123 | * @returns {number|string|object} 124 | */ 125 | leaf() { 126 | return this._heap.leaf(); 127 | } 128 | 129 | /** 130 | * Returns the number of nodes in the heap 131 | * @public 132 | * @returns {number} 133 | */ 134 | size() { 135 | return this._heap.size(); 136 | } 137 | 138 | /** 139 | * Checks if the heap is empty 140 | * @public 141 | * @returns {boolean} 142 | */ 143 | isEmpty() { 144 | return this._heap.isEmpty(); 145 | } 146 | 147 | /** 148 | * Clears the heap 149 | * @public 150 | */ 151 | clear() { 152 | this._heap.clear(); 153 | } 154 | 155 | /** 156 | * Returns a shallow copy of the MinHeap 157 | * @public 158 | * @returns {MinHeap} 159 | */ 160 | clone() { 161 | return new MinHeap(this._getCompareValue, this._heap.clone()); 162 | } 163 | 164 | /** 165 | * Implements an iterable on the heap 166 | * @public 167 | */ 168 | [Symbol.iterator]() { 169 | let size = this.size(); 170 | return { 171 | next: () => { 172 | size -= 1; 173 | return { 174 | value: this.pop(), 175 | done: size === -1 176 | }; 177 | } 178 | }; 179 | } 180 | 181 | /** 182 | * Builds a MinHeap from an array 183 | * @public 184 | * @static 185 | * @param {array} values 186 | * @param {function} [getCompareValue] 187 | * @returns {MinHeap} 188 | */ 189 | static heapify(values, getCompareValue) { 190 | if (!Array.isArray(values)) { 191 | throw new Error('MinHeap.heapify expects an array'); 192 | } 193 | const heap = new Heap(getMinCompare(getCompareValue), values); 194 | return new MinHeap(getCompareValue, heap).fix(); 195 | } 196 | 197 | /** 198 | * Checks if a list of values is a valid min heap 199 | * @public 200 | * @static 201 | * @param {array} values 202 | * @param {function} [getCompareValue] 203 | * @returns {boolean} 204 | */ 205 | static isHeapified(values, getCompareValue) { 206 | const heap = new Heap(getMinCompare(getCompareValue), values); 207 | return new MinHeap(getCompareValue, heap).isValid(); 208 | } 209 | } 210 | 211 | exports.MinHeap = MinHeap; 212 | -------------------------------------------------------------------------------- /test/heap.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { Heap } = require('../src/heap'); 3 | 4 | describe('Heap', () => { 5 | const numComparator = (a, b) => a.id - b.id; 6 | const numValues = [ 7 | { id: 50 }, 8 | { id: 80 }, 9 | { id: 30 }, 10 | { id: 90 }, 11 | { id: 60 }, 12 | { id: 40 }, 13 | { id: 20 } 14 | ]; 15 | 16 | const charComparator = (a, b) => ( 17 | a.id < b.id ? 1 : -1 18 | ); 19 | const charValues = [ 20 | { id: 'm' }, 21 | { id: 'x' }, 22 | { id: 'f' }, 23 | { id: 'b' }, 24 | { id: 'z' }, 25 | { id: 'k' }, 26 | { id: 'c' } 27 | ]; 28 | 29 | describe('min heap', () => { 30 | const heap = new Heap(numComparator); 31 | 32 | it('insert values', () => { 33 | numValues.forEach((value) => heap.insert(value)); 34 | }); 35 | 36 | it('sort in descending order', () => { 37 | expect(heap.sort()).to.eql(numValues.slice().sort((a, b) => b.id - a.id)); 38 | }); 39 | 40 | it('fix position after sort', () => { 41 | expect(heap.isValid()).to.equal(false); 42 | expect(heap.fix().isValid()).to.equal(true); 43 | expect(heap.leaf()).to.eql({ id: 90 }); 44 | }); 45 | 46 | it('gets root value', () => { 47 | expect(heap.top()).to.eql({ id: 20 }); 48 | }); 49 | 50 | it('gets leaf value', () => { 51 | expect(heap.leaf()).to.eql({ id: 90 }); 52 | }); 53 | 54 | it('gets heap size', () => { 55 | expect(heap.size()).to.equal(7); 56 | }); 57 | 58 | it('checks if heap is empty', () => { 59 | expect(heap.isEmpty()).to.equal(false); 60 | }); 61 | 62 | it('clone the heap', () => { 63 | expect(heap.clone().sort()) 64 | .to.eql(numValues.slice().sort((a, b) => b.id - a.id)); 65 | expect(heap.isValid()).to.equal(true); 66 | }); 67 | 68 | it('extract root value', () => { 69 | expect(heap.extractRoot()).to.deep.equal({ id: 20 }); 70 | expect(heap.extractRoot()).to.deep.equal({ id: 30 }); 71 | expect(heap.extractRoot()).to.deep.equal({ id: 40 }); 72 | expect(heap.extractRoot()).to.deep.equal({ id: 50 }); 73 | expect(heap.extractRoot()).to.deep.equal({ id: 60 }); 74 | expect(heap.extractRoot()).to.deep.equal({ id: 80 }); 75 | expect(heap.extractRoot()).to.deep.equal({ id: 90 }); 76 | expect(heap.isEmpty()).to.equal(true); 77 | }); 78 | }); 79 | 80 | describe('max heap', () => { 81 | const heap = new Heap(charComparator); 82 | 83 | it('push values', () => { 84 | charValues.forEach((value) => heap.push(value)); 85 | }); 86 | 87 | it('sort in ascending order', () => { 88 | expect(heap.sort()) 89 | .to.eql(charValues.slice().sort((a, b) => ( 90 | a.id > b.id ? 1 : -1 91 | ))); 92 | }); 93 | 94 | it('fix position after sort', () => { 95 | expect(heap.isValid()).to.equal(false); 96 | expect(heap.fix().isValid()).to.equal(true); 97 | expect(heap.leaf()).to.eql({ id: 'b' }); 98 | }); 99 | 100 | it('gets root value', () => { 101 | expect(heap.root()).to.eql({ id: 'z' }); 102 | }); 103 | 104 | it('gets leaf value', () => { 105 | expect(heap.leaf()).to.eql({ id: 'b' }); 106 | }); 107 | 108 | it('gets heap size', () => { 109 | expect(heap.size()).to.equal(7); 110 | }); 111 | 112 | it('checks if heap is empty', () => { 113 | expect(heap.isEmpty()).to.equal(false); 114 | }); 115 | 116 | it('clone the heap', () => { 117 | expect(heap.clone().sort()) 118 | .to.eql(charValues.slice().sort((a, b) => ( 119 | a.id > b.id ? 1 : -1 120 | ))); 121 | expect(heap.isValid()).to.equal(true); 122 | }); 123 | 124 | it('pop root value', () => { 125 | expect(heap.pop()).to.deep.equal({ id: 'z' }); 126 | expect(heap.pop()).to.deep.equal({ id: 'x' }); 127 | expect(heap.pop()).to.deep.equal({ id: 'm' }); 128 | expect(heap.pop()).to.deep.equal({ id: 'k' }); 129 | expect(heap.pop()).to.deep.equal({ id: 'f' }); 130 | expect(heap.pop()).to.deep.equal({ id: 'c' }); 131 | expect(heap.pop()).to.deep.equal({ id: 'b' }); 132 | expect(heap.isEmpty()).to.equal(true); 133 | }); 134 | }); 135 | 136 | describe('heapify', () => { 137 | it('buids min heap from array', () => { 138 | const heap = Heap.heapify(numValues, numComparator); 139 | expect(heap.isValid()).to.equal(true); 140 | expect(heap.leaf()).to.eql({ id: 90 }); 141 | }); 142 | 143 | it('builds max heap from array', () => { 144 | const heap = Heap.heapify(charValues, charComparator); 145 | expect(heap.isValid()).to.equal(true); 146 | expect(heap.leaf()).to.eql({ id: 'b' }); 147 | }); 148 | }); 149 | 150 | describe('isHeapified', () => { 151 | it('checks if a list is min-heapified', () => { 152 | const heapifiedValues = [ 153 | { id: 20 }, 154 | { id: 60 }, 155 | { id: 30 }, 156 | { id: 90 }, 157 | { id: 80 }, 158 | { id: 50 }, 159 | { id: 40 } 160 | ]; 161 | expect(Heap.isHeapified(heapifiedValues, numComparator)).to.equal(true); 162 | }); 163 | 164 | it('checks if a list is max-heapified', () => { 165 | const heapifiedValues = [ 166 | { id: 'z' }, 167 | { id: 'x' }, 168 | { id: 'k' }, 169 | { id: 'b' }, 170 | { id: 'm' }, 171 | { id: 'f' }, 172 | { id: 'c' } 173 | ]; 174 | expect(Heap.isHeapified(heapifiedValues, charComparator)).to.equal(true); 175 | }); 176 | }); 177 | 178 | describe('iterator', () => { 179 | it('allows iterating on heap elements', () => { 180 | const testArr = [20, 30, 40, 50, 80, 90]; 181 | const h1 = Heap.heapify(testArr.slice(), (a, b) => a - b); 182 | expect([...h1]).to.eql(testArr); 183 | const h2 = Heap.heapify(testArr.slice(), (a, b) => a - b); 184 | const res = []; 185 | for (const n of h2) { 186 | res.push(n); 187 | } 188 | expect(res).to.eql(testArr); 189 | }); 190 | }); 191 | describe('toArray', () => { 192 | it('Converts the heap to a cloned array.', () => { 193 | const testArr = [20, 30, 40, 50, 80, 90].sort((a, b) => a - b); 194 | const h1 = Heap.heapify(testArr.slice(), (a, b) => a - b); 195 | expect(h1.toArray().sort((a, b) => a - b)).to.eql(testArr); 196 | }); 197 | }); 198 | }); 199 | -------------------------------------------------------------------------------- /test/maxHeap.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { MaxHeap } = require('../src/maxHeap'); 3 | 4 | describe('MaxHeap', () => { 5 | describe('primitive values heap', () => { 6 | const values = ['m', 'x', 'f', 'b', 'z', 'k', 'c']; 7 | const heap = new MaxHeap(); 8 | 9 | it('insert values', () => { 10 | values.forEach((value) => heap.insert(value)); 11 | }); 12 | 13 | it('sort in ascending order', () => { 14 | expect(heap.sort()).to.eql(values.slice().sort((a, b) => ( 15 | a > b ? 1 : -1 16 | ))); 17 | }); 18 | 19 | it('fix position after sort', () => { 20 | expect(heap.isValid()).to.equal(false); 21 | expect(heap.fix().isValid()).to.equal(true); 22 | }); 23 | 24 | it('gets root value', () => { 25 | expect(heap.root()).to.eql('z'); 26 | }); 27 | 28 | it('gets leaf value', () => { 29 | expect(heap.leaf()).to.eql('b'); 30 | }); 31 | 32 | it('gets heap size', () => { 33 | expect(heap.size()).to.equal(7); 34 | }); 35 | 36 | it('checks if heap is empty', () => { 37 | expect(heap.isEmpty()).to.equal(false); 38 | }); 39 | 40 | it('clone the heap', () => { 41 | expect(heap.clone().sort()).to.eql(values.slice().sort((a, b) => ( 42 | a > b ? 1 : -1 43 | ))); 44 | expect(heap.isValid()).to.equal(true); 45 | }); 46 | 47 | it('extract root value', () => { 48 | expect(heap.extractRoot()).to.deep.equal('z'); 49 | expect(heap.extractRoot()).to.deep.equal('x'); 50 | expect(heap.extractRoot()).to.deep.equal('m'); 51 | expect(heap.extractRoot()).to.deep.equal('k'); 52 | expect(heap.extractRoot()).to.deep.equal('f'); 53 | expect(heap.extractRoot()).to.deep.equal('c'); 54 | expect(heap.extractRoot()).to.deep.equal('b'); 55 | expect(heap.isEmpty()).to.equal(true); 56 | }); 57 | }); 58 | 59 | describe('object values heap', () => { 60 | const values = [ 61 | { id: 'm' }, 62 | { id: 'x' }, 63 | { id: 'f' }, 64 | { id: 'b' }, 65 | { id: 'z' }, 66 | { id: 'k' }, 67 | { id: 'c' } 68 | ]; 69 | const heap = new MaxHeap((value) => value.id); 70 | 71 | it('push values', () => { 72 | values.forEach((value) => heap.push(value)); 73 | }); 74 | 75 | it('sort in ascending order', () => { 76 | expect(heap.sort()).to.eql(values.slice().sort((a, b) => ( 77 | a.id > b.id ? 1 : -1 78 | ))); 79 | }); 80 | 81 | it('fix position after sort', () => { 82 | expect(heap.isValid()).to.equal(false); 83 | expect(heap.fix().isValid()).to.equal(true); 84 | }); 85 | 86 | it('gets root value', () => { 87 | expect(heap.top()).to.eql({ id: 'z' }); 88 | }); 89 | 90 | it('gets leaf value', () => { 91 | expect(heap.leaf()).to.eql({ id: 'b' }); 92 | }); 93 | 94 | it('gets heap size', () => { 95 | expect(heap.size()).to.equal(7); 96 | }); 97 | 98 | it('checks if heap is empty', () => { 99 | expect(heap.isEmpty()).to.equal(false); 100 | }); 101 | 102 | it('clone the heap', () => { 103 | expect(heap.clone().sort()).to.eql(values.slice().sort((a, b) => ( 104 | a.id > b.id ? 1 : -1 105 | ))); 106 | expect(heap.isValid()).to.equal(true); 107 | }); 108 | 109 | it('pop root value', () => { 110 | expect(heap.pop()).to.deep.equal({ id: 'z' }); 111 | expect(heap.pop()).to.deep.equal({ id: 'x' }); 112 | expect(heap.pop()).to.deep.equal({ id: 'm' }); 113 | expect(heap.pop()).to.deep.equal({ id: 'k' }); 114 | expect(heap.pop()).to.deep.equal({ id: 'f' }); 115 | expect(heap.pop()).to.deep.equal({ id: 'c' }); 116 | expect(heap.pop()).to.deep.equal({ id: 'b' }); 117 | expect(heap.isEmpty()).to.equal(true); 118 | }); 119 | }); 120 | 121 | describe('isValid', () => { 122 | it('consider heap with duplicates as valid', () => { 123 | const maxHeap = new MaxHeap((x) => x.value); 124 | 125 | maxHeap.insert({ value: 2268 }); 126 | maxHeap.insert({ value: 2268 }); 127 | expect(maxHeap.isValid()).to.equal(true); 128 | 129 | const maxHeap2 = new MaxHeap(); 130 | maxHeap2.insert(22); 131 | maxHeap2.insert(22); 132 | expect(maxHeap2.isValid()).to.equal(true); 133 | }); 134 | }); 135 | 136 | describe('iterator', () => { 137 | it('allows iterating on heap elements', () => { 138 | const testArr = [90, 80, 50, 40, 30, 20]; 139 | const h1 = MaxHeap.heapify(testArr.slice()); 140 | expect([...h1]).to.eql(testArr); 141 | const h2 = MaxHeap.heapify(testArr.slice()); 142 | const res = []; 143 | for (const n of h2) { 144 | res.push(n); 145 | } 146 | expect(res).to.eql(testArr); 147 | }); 148 | }); 149 | describe('toArray', () => { 150 | it('Converts the heap to a cloned array.', () => { 151 | const testArr = [90, 80, 50, 40, 30, 20].sort((a, b) => -a + b); 152 | const h1 = MaxHeap.heapify(testArr.slice()); 153 | expect(h1.toArray().sort((a, b) => -a + b)).to.eql(testArr); 154 | }); 155 | }); 156 | }); 157 | -------------------------------------------------------------------------------- /test/minHeap.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { MinHeap } = require('../src/minHeap'); 3 | 4 | describe('MinHeap', () => { 5 | describe('primitive values heap', () => { 6 | const values = [50, 80, 30, 90, 60, 40, 20]; 7 | const heap = new MinHeap(); 8 | 9 | it('insert values', () => { 10 | values.forEach((value) => heap.insert(value)); 11 | }); 12 | 13 | it('sort in descending order', () => { 14 | expect(heap.sort()).to.eql(values.slice().sort((a, b) => b - a)); 15 | }); 16 | 17 | it('fix position after sort', () => { 18 | expect(heap.isValid()).to.equal(false); 19 | expect(heap.fix().isValid()).to.equal(true); 20 | }); 21 | 22 | it('gets root value', () => { 23 | expect(heap.root()).to.eql(20); 24 | }); 25 | 26 | it('gets leaf value', () => { 27 | expect(heap.leaf()).to.eql(90); 28 | }); 29 | 30 | it('gets heap size', () => { 31 | expect(heap.size()).to.equal(7); 32 | }); 33 | 34 | it('checks if heap is empty', () => { 35 | expect(heap.isEmpty()).to.equal(false); 36 | }); 37 | 38 | it('clone the heap', () => { 39 | expect(heap.clone().sort()).to.eql(values.slice().sort((a, b) => b - a)); 40 | expect(heap.isValid()).to.equal(true); 41 | }); 42 | 43 | it('extract root value', () => { 44 | expect(heap.extractRoot()).to.deep.equal(20); 45 | expect(heap.extractRoot()).to.deep.equal(30); 46 | expect(heap.extractRoot()).to.deep.equal(40); 47 | expect(heap.extractRoot()).to.deep.equal(50); 48 | expect(heap.extractRoot()).to.deep.equal(60); 49 | expect(heap.extractRoot()).to.deep.equal(80); 50 | expect(heap.extractRoot()).to.deep.equal(90); 51 | expect(heap.isEmpty()).to.equal(true); 52 | }); 53 | }); 54 | 55 | describe('object values heap', () => { 56 | const values = [ 57 | { id: 50 }, 58 | { id: 80 }, 59 | { id: 30 }, 60 | { id: 90 }, 61 | { id: 60 }, 62 | { id: 40 }, 63 | { id: 20 } 64 | ]; 65 | const heap = new MinHeap((value) => value.id); 66 | 67 | it('push values', () => { 68 | values.forEach((value) => heap.push(value)); 69 | }); 70 | 71 | it('sort in descending order', () => { 72 | expect(heap.sort()).to.eql(values.slice().sort((a, b) => b.id - a.id)); 73 | }); 74 | 75 | it('fix position after sort', () => { 76 | expect(heap.isValid()).to.equal(false); 77 | expect(heap.fix().isValid()).to.equal(true); 78 | }); 79 | 80 | it('gets root value', () => { 81 | expect(heap.top()).to.eql({ id: 20 }); 82 | }); 83 | 84 | it('gets leaf value', () => { 85 | expect(heap.leaf()).to.eql({ id: 90 }); 86 | }); 87 | 88 | it('gets heap size', () => { 89 | expect(heap.size()).to.equal(7); 90 | }); 91 | 92 | it('checks if heap is empty', () => { 93 | expect(heap.isEmpty()).to.equal(false); 94 | }); 95 | 96 | it('clone the heap', () => { 97 | expect(heap.clone().sort()) 98 | .to.eql(values.slice().sort((a, b) => b.id - a.id)); 99 | expect(heap.isValid()).to.equal(true); 100 | }); 101 | 102 | it('pop root value', () => { 103 | expect(heap.pop()).to.deep.equal({ id: 20 }); 104 | expect(heap.pop()).to.deep.equal({ id: 30 }); 105 | expect(heap.pop()).to.deep.equal({ id: 40 }); 106 | expect(heap.pop()).to.deep.equal({ id: 50 }); 107 | expect(heap.pop()).to.deep.equal({ id: 60 }); 108 | expect(heap.pop()).to.deep.equal({ id: 80 }); 109 | expect(heap.pop()).to.deep.equal({ id: 90 }); 110 | expect(heap.isEmpty()).to.equal(true); 111 | }); 112 | }); 113 | 114 | describe('isValid', () => { 115 | it('consider heap with duplicates as valid', () => { 116 | const minHeap = new MinHeap((x) => x.value); 117 | 118 | minHeap.insert({ value: 2268 }); 119 | minHeap.insert({ value: 2268 }); 120 | expect(minHeap.isValid()).to.equal(true); 121 | 122 | const minHeap2 = new MinHeap(); 123 | minHeap2.insert(22); 124 | minHeap2.insert(22); 125 | expect(minHeap2.isValid()).to.equal(true); 126 | }); 127 | }); 128 | 129 | describe('iterator', () => { 130 | it('allows iterating on heap elements', () => { 131 | const testArr = [20, 30, 40, 50, 80, 90]; 132 | const h1 = MinHeap.heapify(testArr.slice()); 133 | expect([...h1]).to.eql(testArr); 134 | const h2 = MinHeap.heapify(testArr.slice()); 135 | const res = []; 136 | for (const n of h2) { 137 | res.push(n); 138 | } 139 | expect(res).to.eql(testArr); 140 | }); 141 | }); 142 | 143 | describe('toArray', () => { 144 | it('Converts the heap to a cloned array', () => { 145 | const testArr = [20, 30, 40, 50, 80, 90].sort((a, b) => a - b); 146 | const h1 = MinHeap.heapify(testArr.slice()); 147 | expect(h1.toArray().sort((a, b) => a - b)).to.eql(testArr); 148 | }); 149 | }); 150 | }); 151 | -------------------------------------------------------------------------------- /test/readme.ts: -------------------------------------------------------------------------------- 1 | import { Heap, MinHeap, MaxHeap, ICompare, IGetCompareValue } from '../index'; 2 | 3 | interface ICar { 4 | year: number; 5 | price: number; 6 | } 7 | 8 | const compareCars: ICompare = (a: ICar, b: ICar) => { 9 | if (a.year > b.year) { 10 | return -1; 11 | } 12 | if (a.year < b.year) { 13 | // prioratize newest cars 14 | return 1; 15 | } 16 | // with least price 17 | return a.price < b.price ? -1 : 1; 18 | }; 19 | 20 | const carsHeap = new Heap(compareCars); 21 | 22 | const cars = [ 23 | { year: 2013, price: 35000 }, 24 | { year: 2010, price: 2000 }, 25 | { year: 2013, price: 30000 }, 26 | { year: 2017, price: 50000 }, 27 | { year: 2013, price: 25000 }, 28 | { year: 2015, price: 40000 }, 29 | { year: 2022, price: 70000 } 30 | ]; 31 | cars.forEach((car) => carsHeap.insert(car)); 32 | 33 | while (!carsHeap.isEmpty()) { 34 | console.log(carsHeap.extractRoot()); 35 | } 36 | 37 | const numbersHeap = new MinHeap(); 38 | 39 | interface IBid { 40 | id: number; 41 | value: number; 42 | } 43 | const getBidCompareValue: IGetCompareValue = (bid: IBid) => bid.value; 44 | const bidsHeap = new MaxHeap(getBidCompareValue); 45 | 46 | const numbers = [3, -2, 5, 0, -1, -5, 4]; 47 | numbers.forEach((num) => numbersHeap.insert(num)); 48 | 49 | const bids = [ 50 | { id: 1, value: 1000 }, 51 | { id: 2, value: 20000 }, 52 | { id: 3, value: 1000 }, 53 | { id: 4, value: 1500 }, 54 | { id: 5, value: 12000 }, 55 | { id: 6, value: 4000 }, 56 | { id: 7, value: 8000 } 57 | ]; 58 | bids.forEach((bid) => bidsHeap.insert(bid)); 59 | 60 | while (!numbersHeap.isEmpty()) { 61 | console.log(numbersHeap.extractRoot()); 62 | } 63 | 64 | while (!bidsHeap.isEmpty()) { 65 | console.log(bidsHeap.extractRoot()); 66 | } 67 | 68 | cars.forEach((car) => carsHeap.insert(car)); 69 | numbers.forEach((num) => numbersHeap.insert(num)); 70 | bids.forEach((bid) => bidsHeap.insert(bid)); 71 | 72 | console.log(carsHeap.root()); 73 | console.log(numbersHeap.root()); 74 | console.log(bidsHeap.root()); 75 | 76 | console.log(carsHeap.leaf()); 77 | console.log(numbersHeap.leaf()); 78 | console.log(bidsHeap.leaf()); 79 | 80 | console.log(carsHeap.size()); // 7 81 | console.log(numbersHeap.size()); // 7 82 | console.log(bidsHeap.size()); // 7 83 | 84 | console.log(carsHeap.sort()); 85 | console.log(numbersHeap.sort()); 86 | console.log(bidsHeap.sort()); 87 | 88 | console.log(carsHeap.isValid()); 89 | console.log(numbersHeap.isValid()); 90 | console.log(bidsHeap.isValid()); 91 | 92 | const heapifiedCars = Heap.heapify(cars, compareCars); 93 | console.log(heapifiedCars.isValid()); // true 94 | console.log(cars); 95 | 96 | const heapifiedNumbers = MinHeap.heapify(numbers); 97 | console.log(heapifiedNumbers.isValid()); // true 98 | console.log(numbers); 99 | 100 | const heapifiedBids = MaxHeap.heapify(bids, (bid) => bid.value); 101 | console.log(heapifiedBids.isValid()); // true 102 | console.log(bids); 103 | 104 | console.log(Heap.isHeapified(cars, compareCars)); // true 105 | console.log(MinHeap.isHeapified(numbers)); // true 106 | console.log(MaxHeap.isHeapified(bids, (bid) => bid.value)); // true 107 | --------------------------------------------------------------------------------