├── .eslintrc ├── .github └── workflows │ └── npm-grunt.yml ├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── DoublyLinkedList.md ├── Gruntfile.js ├── LICENSE ├── LinkedList.md ├── README.md ├── index.d.ts ├── index.js ├── package.json ├── src ├── doublyLinkedList.d.ts ├── doublyLinkedList.js ├── doublyLinkedListNode.d.ts ├── doublyLinkedListNode.js ├── linkedList.d.ts ├── linkedList.js ├── linkedListNode.d.ts └── linkedListNode.js └── test ├── doublyLinkedList.test.js └── linkedList.test.js /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "max-len": ["error", { "code": 120, "ignoreComments": true }], 4 | "comma-dangle": ["error", { 5 | "functions": "ignore" 6 | }], 7 | "no-underscore-dangle": [ 8 | "error", 9 | { "allowAfterThis": true } 10 | ] 11 | }, 12 | "env": { 13 | "mocha": true, 14 | "node": true 15 | }, 16 | "extends": ["airbnb-base"] 17 | } 18 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | .travis.yml 3 | .gitignore 4 | .eslintrc 5 | Gruntfile.js 6 | coverage/ 7 | node_modules/ 8 | test/ 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "9" 5 | - "10" 6 | - "11" 7 | - "12" 8 | install: 9 | - npm install -g grunt-cli 10 | - npm install 11 | script: 12 | - grunt build 13 | -------------------------------------------------------------------------------- /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 | ## [6.1.3] - 2024-07-14 9 | ### Fixed 10 | - jsdocs 11 | 12 | ## [6.1.2] - 2024-07-13 13 | ### Fixed 14 | - LinkedList `removeEach` edge case to disconnect removed nodes from the remaining nodes in the list. 15 | - LinkedList & DoublyLinkedList ts types, remove the necessity to cast to the custom node type in functions. 16 | 17 | ## [6.1.1] - 2023-09-16 18 | ### Fixed 19 | - `toArray` fix ts return type. 20 | 21 | ## [6.1.0] - 2023-05-28 22 | ### Added 23 | - `insertBefore` to add a node before an existing node in the DoublyLinkedList. 24 | - `insertAfter` to add a node after an existing node in the DoublyLinkedList. 25 | 26 | ## [6.0.0] - 2023-03-20 27 | ### Changed 28 | - Allow inserting node types into the linked list. 29 | 30 | ## [5.2.5] - 2023-01-09 31 | ### Fixed 32 | - a bug in `removeEach`. 33 | 34 | ## [5.2.4] - 2022-09-04 35 | ### Fixed 36 | - typo in LinkedListNode. 37 | 38 | ## [5.2.3] - 2022-08-15 39 | ### Fixed 40 | - add types to package.json 41 | 42 | ## [5.2.2] - 2022-06-19 43 | ### Fixed 44 | - readme. 45 | 46 | ## [5.2.1] - 2022-02-16 47 | ### Fixed 48 | - TS types and readme. 49 | 50 | ## [5.2.0] - 2022-02-14 51 | ### Added 52 | - `.find` now accepts a second param as the starting node in both types. 53 | - `.findReverse` added to DoublyLinkedList. 54 | - `.fromArray` static method added to both types. 55 | 56 | ## [5.1.1] - 2021-06-20 57 | ### Fixed 58 | - index.d.ts 59 | 60 | ## [5.1.0] - 2021-06-14 61 | ### Added 62 | - typescript. 63 | 64 | ## [5.0.1] - 2021-04-12 65 | ### Fixed 66 | - README 67 | 68 | ## [5.0.0] - 2021-04-12 69 | 70 | ### Changed 71 | - insert/remove methods now all returns the inserted/removed nodes. 72 | - `insertLast` in LinkedList now accepts a starting node as a second param, useful to insert a node at the end in O(1) runtime. 73 | - `removeEach` now returns the number of removed nodes. 74 | 75 | ### Added 76 | - `remove(node)` to DoublyLinkedList to remove any node in O(1) runtime. 77 | - `hasNext`/`hasPrev` cleaner checks of connected nodes to Node classes. 78 | 79 | ### Fixed 80 | - bug in removeEach method. 81 | - improved README, splitted LinkedList/DoublyLinkedList readmes. 82 | 83 | ## [4.0.0] - 2021-02-16 84 | 85 | ### Changed 86 | - `.removeFirst()`, `.removeLast()`, `.removeAt`, `.removeEach` now return the removed nodes. 87 | 88 | 89 | ## [3.0.3] - 2021-01-30 90 | 91 | ### Fixed 92 | - `.removeFirst()` when removing first from a single-node linked list. 93 | 94 | ## [3.0.2] - 2021-01-02 95 | 96 | ### Fixed 97 | - readme 98 | 99 | ## [3.0.1] - 2021-01-02 100 | 101 | ### Fixed 102 | - readme 103 | 104 | ## [3.0.0] - 2020-12-30 105 | ### Changed 106 | - `.insertAt(position, value)` position now comes first then value. 107 | - `.insert*` methods now returns a `this` reference so it can be chained. 108 | - `.forEach(cb)` callback now gets called with node and position (starting from 0); 109 | - `.forEachReverse(cb)` callback now gets called with node and position (starting from size -1); 110 | - `LinkedListNode` & `DoublyLinkedListNode` classes are now exported in index. 111 | 112 | ### Fixed 113 | - jsdoc 114 | - readme 115 | 116 | ## [2.0.3] - 2020-04-04 117 | ### Added 118 | `.isEmpty()` method for LinkedList & DoublyLinkedList. 119 | 120 | ### Fixed 121 | - README & jsdocs 122 | 123 | ## [2.0.2] - 2020-03-22 124 | ### Fixed 125 | - jsdocs 126 | 127 | ## [2.0.1] - 2020-03-22 128 | ### Fixed 129 | - Readme & package.json 130 | 131 | ## [2.0.0] - 2020-03-22 132 | ### Changed 133 | - New release for LinkedList & DoublyLinkedList 134 | -------------------------------------------------------------------------------- /DoublyLinkedList.md: -------------------------------------------------------------------------------- 1 | # DoublyLinkedList 2 | 3 | # Contents 4 | * [Install](#install) 5 | * [require](#require) 6 | * [import](#import) 7 | * [API](#api) 8 | * [constructor](#constructor) 9 | * [insertFirst](#insertfirst) 10 | * [insertLast](#insertlast) 11 | * [insertBefore](#insertbefore) 12 | * [insertAfter](#insertafter) 13 | * [insertAt](#insertat) 14 | * [forEach](#foreach) 15 | * [forEachReverse](#foreachreverse) 16 | * [find](#find) 17 | * [findReverse](#findreverse) 18 | * [filter](#filter) 19 | * [toArray](#toarray) 20 | * [isEmpty](#isempty) 21 | * [head](#head) 22 | * [tail](#tail) 23 | * [count](#count) 24 | * [removeFirst](#removefirst) 25 | * [removeLast](#removelast) 26 | * [remove](#remove) 27 | * [removeAt](#removeat) 28 | * [removeEach](#removeeach) 29 | * [clear](#clear) 30 | * [DoublyLinkedList.fromArray](#doublylinkedlistfromarray) 31 | * [DoublyLinkedListNode](#doublylinkedlistnode) 32 | * [Build](#build) 33 | * [License](#license) 34 | 35 | ## install 36 | ```sh 37 | npm install --save @datastructures-js/linked-list 38 | ``` 39 | 40 | ## require 41 | ```js 42 | const { 43 | DoublyLinkedList, 44 | DoublyLinkedListNode, 45 | } = require('@datastructures-js/linked-list'); 46 | ``` 47 | 48 | ## import 49 | ```js 50 | import { 51 | DoublyLinkedList, 52 | DoublyLinkedListNode, 53 | } from '@datastructures-js/linked-list'; 54 | ``` 55 | 56 | ## API 57 | 58 | ### constructor 59 | 60 | ##### JS 61 | ```js 62 | const doublyLinkedList = new DoublyLinkedList(); 63 | ``` 64 | 65 | ##### TS 66 | ```js 67 | const doublyLinkedList = new DoublyLinkedList(); 68 | ``` 69 | 70 | you can also extend DoublyLinkedListNode (in JS & TS) to use as the list node type 71 | 72 | ```js 73 | class Point extends DoublyLinkedListNode { 74 | x: number; 75 | 76 | y: number; 77 | 78 | constructor(x: number, y: number) { 79 | super(); 80 | this.x = x; 81 | this.y = y; 82 | } 83 | 84 | toString() { 85 | return `(${this.x},${this.y})`; 86 | } 87 | } 88 | 89 | const points = new DoublyLinkedList(); 90 | ``` 91 | 92 | ### insertFirst 93 | inserts a node at the beginning of the list in O(1) runetime and returns the inserted node. 94 | 95 | ```js 96 | console.log(doublyLinkedList.insertFirst(3).getValue()); // 3 97 | console.log(doublyLinkedList.insertFirst(2).getValue()); // 2 98 | console.log(doublyLinkedList.insertFirst(1).getValue()); // 1 99 | 100 | points.insertFirst(new Point(2, 3)); 101 | points.insertFirst(new Point(1, 2)); 102 | points.insertFirst(new Point(0, 1)); 103 | ``` 104 | 105 | ### insertLast 106 | inserts a node at the end of the list in O(1) runtime and returns the inserted node. 107 | 108 | ```js 109 | const last4 = doublyLinkedList.insertLast(4); 110 | console.log(last4.getValue()); // 4 111 | console.log(last4.getNext()); // null 112 | console.log(last4.getPrev().getValue()); // 3 113 | 114 | const last5 = doublyLinkedList.insertLast(5); 115 | console.log(last5.getValue()); // 5 116 | console.log(last5.getNext()); // null 117 | console.log(last5.getPrev().getValue()); // 4 118 | 119 | points.insertLast(new Point(3, 4)); 120 | points.insertLast(new Point(4, 5)); 121 | points.insertLast(new Point(5, 6)); 122 | ``` 123 | 124 | ### insertBefore 125 | inserts a node before an existing node in O(1) runtime and returns the inserted node. 126 | 127 | ```js 128 | const n23 = points.find((p) => p.x === 2 && p.y === 3); 129 | const inserted = points.insertBefore(new Point(-1, -2), n23); // insert (-1,-2) before (2,3) 130 | console.log(inserted.getNext().toString()); // (2,3) 131 | ``` 132 | 133 | ### insertAfter 134 | inserts a node after an existing node in O(1) runtime and returns the inserted node. 135 | 136 | ```js 137 | const n34 = points.find((p) => p.x === 3 && p.y === 4); 138 | const inserted = points.insertAfter(new Point(-6, -7), n34); // insert (-6,-7) before (3,4) 139 | console.log(inserted.getPrev().toString()); // (3,4) 140 | ``` 141 | 142 | ### insertAt 143 | inserts a node at a specific position of the list in O(n) runtime. First (head) node is at position 0. 144 | 145 | ```js 146 | const node2 = doublyLinkedList.insertAt(2, 5); 147 | console.log(node2.getValue()); // 5 148 | ``` 149 | 150 | ### forEach 151 | Traverse the list from beginning to end, and pass each node to the callback. 152 | 153 | ```js 154 | doublyLinkedList.forEach( 155 | (node, position) => console.log(node.getValue(), position) 156 | ); 157 | /* 158 | 1 0 159 | 2 1 160 | 5 2 161 | 3 3 162 | 4 4 163 | 5 5 164 | */ 165 | 166 | points.forEach((point) => console.log(point.toString())); 167 | /* 168 | (0,1) 169 | (1,2) 170 | (-1,-2) 171 | (2,3) 172 | (3,4) 173 | (-6,-7) 174 | (4,5) 175 | (5,6) 176 | */ 177 | ``` 178 | 179 | ### forEachReverse 180 | Traverse the list from end to beginning, and pass each node to the callback. 181 | 182 | ```js 183 | doublyLinkedList.forEachReverse( 184 | (node, position) => console.log(node.getValue(), position) 185 | ); 186 | /* 187 | 5 5 188 | 4 4 189 | 3 3 190 | 5 2 191 | 2 1 192 | 1 0 193 | */ 194 | 195 | points.forEachReverse((point) => console.log(point.toString())); 196 | /* 197 | (5,6) 198 | (4,5) 199 | (-6,-7) 200 | (3,4) 201 | (2,3) 202 | (-1,-2) 203 | (1,2) 204 | (0,1) 205 | */ 206 | ``` 207 | 208 | ### find 209 | finds the first node that matches the callback criteria or null if nothing is found. It also accepts a second param as the starting node to start searching from. 210 | 211 | ```js 212 | const node5 = doublyLinkedList.find( 213 | (node, position) => node.getValue() === 5 214 | ); 215 | console.log(node5.getValue()); // 5 216 | 217 | console.log(points.find((point) => point.x === 4).toString()); // (4,5) 218 | ``` 219 | 220 | ### findReverse 221 | finds the first node that match a callback criteria or null if nothing found by scanning the list from end to beginning. It also accepts a second param as the starting end node to start searching from. 222 | 223 | ```js 224 | const node5 = doublyLinkedList.findReverse( 225 | (node, position) => node.getValue() === 5 226 | ); 227 | console.log(node5.getValue()); // 5 228 | 229 | console.log(points.findReverse((point) => point.x === 4).toString()); // (4,5) 230 | ``` 231 | 232 | ### filter 233 | returns a filtered doubly linked list of all the nodes that match a callback criteria. 234 | 235 | ```js 236 | const filterLinkedList = doublyLinkedList.filter( 237 | (node, position) => node.getValue() > 2 238 | ); 239 | filterLinkedList.forEach( 240 | (node, position) => console.log(node.getValue(), position) 241 | ); 242 | /* 243 | 5 0 244 | 3 1 245 | 4 2 246 | 5 3 247 | */ 248 | 249 | points 250 | .filter((point) => point.y >= 4) 251 | .forEach((point) => console.log(point.toString())); 252 | /* 253 | (3,4) 254 | (4,5) 255 | (5,6) 256 | */ 257 | ``` 258 | 259 | ### toArray 260 | converts the doubly linked list into an array of nodes. 261 | 262 | ```js 263 | console.log(doublyLinkedList.toArray().map(n => n.getValue())); 264 | // [1, 2, 5, 3, 4, 5] 265 | 266 | console.log(points.toArray().map(p => p.toString())); 267 | // ['(0,1)', '(1,2)', '(-1,-2)', '(2,3)','(3,4)', '(-6,-7)', '(4,5)', '(5,6)'] 268 | ``` 269 | 270 | ### isEmpty 271 | checks if the list is empty. 272 | 273 | ```js 274 | console.log(doublyLinkedList.isEmpty()); // false 275 | ``` 276 | 277 | ### head 278 | returns the head (first) node of the list. 279 | 280 | ```js 281 | console.log(doublyLinkedList.head().getValue()); // 1 282 | ``` 283 | 284 | ### tail 285 | returns the tail (last) node of the list. 286 | 287 | ```js 288 | console.log(doublyLinkedList.tail().getValue()); // 5 289 | ``` 290 | 291 | ### count 292 | returns the number of nodes in the list. 293 | 294 | ```js 295 | console.log(doublyLinkedList.count()); // 6 296 | ``` 297 | 298 | ### removeFirst 299 | removes the first node in the list in O(1) runtime.. 300 | 301 | ```js 302 | const removed = doublyLinkedList.removeFirst(); 303 | console.log(removed.getValue()); // 1 304 | console.log(removed.getNext()); // null 305 | 306 | console.log(doublyLinkedList.toArray().map(n => n.getValue())); // [2, 5, 3, 4, 5] 307 | ``` 308 | 309 | ### removeLast 310 | removes and returns the last node in the list in O(1) runtime.. 311 | 312 | ```js 313 | const removed = doublyLinkedList.removeLast(); 314 | console.log(removed.getValue()); // 5 315 | console.log(removed.getNext()); // null 316 | 317 | console.log(doublyLinkedList.toArray().map(n => n.getValue())); // [2, 5, 3, 4] 318 | ``` 319 | 320 | ### remove 321 | Removes a given node from the list. This can be done by remembering the reference of the inserted nodes, then call this function to remove a node in O(1) runtime. 322 | 323 | ```js 324 | const memoizedNode = doublyLinkedList.insertAt(2, 10); 325 | console.log(doublyLinkedList.toArray().map(n => n.getValue())); // [2, 5, 10, 3, 4] 326 | 327 | doublyLinkedList.remove(memoizedNode); // O(1) 328 | console.log(doublyLinkedList.toArray().map(n => n.getValue())); // [2, 5, 3, 4] 329 | ``` 330 | 331 | ### removeAt 332 | removes and returns the node at a specific position in O(n) runtime. First (head) node is at position 0. 333 | 334 | ```js 335 | const removed = doublyLinkedList.removeAt(1); 336 | console.log(removed.getValue()); // 5 337 | console.log(removed.getNext()); // null 338 | 339 | console.log(doublyLinkedList.toArray().map(n => n.getValue())); // [2, 3, 4] 340 | ``` 341 | 342 | ### removeEach 343 | removes the nodes that match a callback criteria and returns the number of removed nodes. 344 | 345 | ```js 346 | const removedCount = doublyLinkedList.removeEach( 347 | (node, position) => node.getValue() > 2 348 | ); 349 | console.log(removedCount); // 2 350 | console.log(doublyLinkedList.toArray().map(n => n.getValue())); // [2] 351 | ``` 352 | 353 | ### clear 354 | clears the list. 355 | 356 | ```js 357 | doublyLinkedList.clear(); 358 | console.log(linkedList.count()); // 0 359 | console.log(doublyLinkedList.head()); // null 360 | console.log(doublyLinkedList.tail()); // null 361 | ``` 362 | 363 | ### DoublyLinkedList.fromArray 364 | creates a doubly linked list from an existing array. 365 | 366 | ##### JS 367 | ```js 368 | const dll = DoublyLinkedList.fromArray([1, 2, 3, 4, 5]); 369 | ``` 370 | 371 | ##### TS 372 | ```js 373 | const dll = DoublyLinkedList.fromArray([1, 2, 3, 4, 5]); 374 | ``` 375 | 376 | ### DoublyLinkedListNode 377 | 378 | #### setValue(value: any) 379 | sets the value on the node. 380 | 381 | #### getValue(): any 382 | gets the value of the node. 383 | 384 | #### setPrev(prev: DoublyLinkedListNode) 385 | sets the previous node. 386 | 387 | #### getPrev(): DoublyLinkedListNode 388 | gets the previous node. 389 | 390 | #### hasPrev(): boolean 391 | checks if node has a previous node. 392 | 393 | #### setNext(next: DoublyLinkedListNode) 394 | sets the next node. 395 | 396 | #### getNext(): DoublyLinkedListNode 397 | gets the next node. 398 | 399 | #### hasNext(): boolean 400 | checks if node has a next node. 401 | 402 | #### clone(): DoublyLinkedListNode 403 | clones the node without next or prev references. 404 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /LinkedList.md: -------------------------------------------------------------------------------- 1 | # LinkedList 2 | 3 | # Table of Contents 4 | * [Install](#install) 5 | * [require](#require) 6 | * [import](#import) 7 | * [API](#api) 8 | * [constructor](#constructor) 9 | * [insertFirst](#insertfirst) 10 | * [insertLast](#insertlast) 11 | * [insertAt](#insertat) 12 | * [forEach](#foreach) 13 | * [find](#find) 14 | * [filter](#filter) 15 | * [toArray](#toarray) 16 | * [isEmpty](#isempty) 17 | * [head](#head) 18 | * [count](#count) 19 | * [removeFirst](#removefirst) 20 | * [removeLast](#removelast) 21 | * [removeAt](#removeat) 22 | * [removeEach](#removeeach) 23 | * [clear](#clear) 24 | * [LinkedList.fromArray](#linkedlistfromarray) 25 | * [LinkedListNode](#linkedlistnode) 26 | 27 | ## install 28 | ```sh 29 | npm install --save @datastructures-js/linked-list 30 | ``` 31 | 32 | ## require 33 | ```js 34 | const { 35 | LinkedList, 36 | LinkedListNode 37 | } = require('@datastructures-js/linked-list'); 38 | ``` 39 | 40 | ## import 41 | ```js 42 | import { 43 | LinkedList, 44 | LinkedListNode 45 | } from '@datastructures-js/linked-list'; 46 | ``` 47 | 48 | ## API 49 | 50 | ### constructor 51 | 52 | ##### JS 53 | ```js 54 | const linkedList = new LinkedList(); 55 | ``` 56 | 57 | ##### TS 58 | ```js 59 | const linkedList = new LinkedList(); 60 | ``` 61 | 62 | you can also extend LinkedListNode (in JS & TS) to use as the list node type 63 | 64 | ```js 65 | class Point extends LinkedListNode { 66 | x: number; 67 | 68 | y: number; 69 | 70 | constructor(x: number, y: number) { 71 | super(); 72 | this.x = x; 73 | this.y = y; 74 | } 75 | 76 | toString() { 77 | return `(${this.x},${this.y})`; 78 | } 79 | } 80 | 81 | const points = new LinkedList(); 82 | ``` 83 | 84 | ### insertFirst 85 | inserts a node at the beginning of the list in O(1) runetime and returns the inserted node. 86 | 87 | ```js 88 | console.log(linkedList.insertFirst(3).getValue()); // 3 89 | console.log(linkedList.insertFirst(2).getValue()); // 2 90 | console.log(linkedList.insertFirst(1).getValue()); // 1 91 | 92 | points.insertFirst(new Point(2, 3)); 93 | points.insertFirst(new Point(1, 2)); 94 | points.insertFirst(new Point(0, 1)); 95 | ``` 96 | 97 | ### insertLast 98 | inserts a node at the end of the list in O(n) runtime. it also accepts an second param as the starting node which can be used to insert in O(1) runtime if last inserted node is memoized. 99 | 100 | ```js 101 | const last4 = linkedList.insertLast(4); 102 | console.log(last4.getValue()); // 4 103 | console.log(last4.getNext()); // null 104 | 105 | const last5 = linkedList.insertLast(5, last4); // O(1) 106 | console.log(last5.getValue()); // 5 107 | console.log(last5.getNext()); // null 108 | 109 | points.insertLast(new Point(3, 4)); 110 | points.insertLast(new Point(4, 5)); 111 | points.insertLast(new Point(5, 6)); 112 | ``` 113 | 114 | ### insertAt 115 | inserts a node at a specific position of the list in O(n) runtime. First (head) node is at position 0. 116 | 117 | ```js 118 | const node2 = linkedList.insertAt(2, 5); 119 | console.log(node2.getValue()); // 5 120 | ``` 121 | 122 | ### forEach 123 | Traverse the list from beginning to end, and pass each node to the callback. 124 | 125 | ```js 126 | linkedList.forEach( 127 | (node, position) => console.log(node.getValue(), position) 128 | ); 129 | /* 130 | 1 0 131 | 2 1 132 | 5 2 133 | 3 3 134 | 4 4 135 | 5 5 136 | */ 137 | 138 | points.forEach((point) => console.log(point.toString())); 139 | /* 140 | (0,1) 141 | (1,2) 142 | (2,3) 143 | (3,4) 144 | (4,5) 145 | (5,6) 146 | */ 147 | ``` 148 | 149 | ### find 150 | finds the first node that matches the callback criteria or null if nothing is found. It also accepts a second param as the starting node to start searching from. 151 | 152 | ```js 153 | const node5 = linkedList.find( 154 | (node, position) => node.getValue() === 5 155 | ); 156 | console.log(node5.getValue()); // 5 157 | 158 | console.log(points.find((point) => point.x === 4).toString()); // (4,5) 159 | ``` 160 | 161 | ### filter 162 | returns a filtered linked list of all the nodes that match a callback criteria. 163 | 164 | ```js 165 | linkedList 166 | .filter((node, position) => node.getValue() > 2); 167 | .forEach((node, position) => console.log(node.getValue(), position)); 168 | /* 169 | 5 0 170 | 3 1 171 | 4 2 172 | 5 3 173 | */ 174 | 175 | points 176 | .filter((point) => point.y >= 4) 177 | .forEach((point) => console.log(point.toString())); 178 | /* 179 | (3,4) 180 | (4,5) 181 | (5,6) 182 | */ 183 | ``` 184 | 185 | ### toArray 186 | converts the linked list into an array of nodes. 187 | 188 | ```js 189 | console.log(linkedList.toArray().map(n => n.getValue())); // [1, 2, 5, 3, 4, 5] 190 | 191 | console.log(points.toArray().map(p => p.toString())); 192 | // ['(0,1)', '(1,2)', '(2,3)', '(3,4)', '(4,5)', '(5,6)'] 193 | ``` 194 | 195 | ### isEmpty 196 | checks if the linked list is empty. 197 | 198 | ```js 199 | console.log(linkedList.isEmpty()); // false 200 | ``` 201 | 202 | ### head 203 | returns the head node in the linked list. 204 | 205 | ```js 206 | console.log(linkedList.head().getValue()); // 1 207 | ``` 208 | 209 | ### count 210 | returns the number of nodes in the linked list. 211 | 212 | ```js 213 | console.log(linkedList.count()); // 6 214 | ``` 215 | 216 | ### removeFirst 217 | removes and returns the first node in the list in O(1) runtime. 218 | 219 | ```js 220 | const removed = linkedList.removeFirst(); 221 | console.log(removed.getValue()); // 1 222 | console.log(removed.getNext()); // null 223 | 224 | console.log(linkedList.toArray().map(n => n.getValue())); // [2, 5, 3, 4, 5] 225 | ``` 226 | 227 | ### removeLast 228 | removes and returns the last node in the list in O(n) runtime. 229 | 230 | ```js 231 | const removed = linkedList.removeLast(); 232 | console.log(removed.getValue()); // 5 233 | console.log(removed.getNext()); // null 234 | 235 | console.log(linkedList.toArray().map(n => n.getValue())); // [2, 5, 3, 4] 236 | ``` 237 | 238 | ### removeAt 239 | removes and returns the node at a specific position in O(n) runtime. First (head) node is at position 0. 240 | 241 | ```js 242 | const removed = linkedList.removeAt(1); 243 | console.log(removed.getValue()); // 5 244 | console.log(removed.getNext()); // null 245 | 246 | console.log(linkedList.toArray()); // [2, 3, 4] 247 | ``` 248 | 249 | ### removeEach 250 | removes the nodes that match a callback criteria and returns the number of removed nodes. 251 | 252 | ```js 253 | const removedCount = linkedList.removeEach( 254 | (node, position) => node.getValue() > 2 255 | ); 256 | console.log(removedCount); // 2 257 | console.log(linkedList.toArray().map(n => n.getValue())); // [2] 258 | ``` 259 | 260 | ### clear 261 | clears the linked list. 262 | 263 | ```js 264 | linkedList.clear(); 265 | console.log(linkedList.count()); // 0 266 | console.log(linkedList.head()); // null 267 | ``` 268 | 269 | ### LinkedList.fromArray 270 | creates a linked list from an existing array. 271 | 272 | ##### JS 273 | ```js 274 | const ll = LinkedList.fromArray([1, 2, 3, 4, 5]); 275 | ``` 276 | 277 | ##### TS 278 | ```js 279 | const ll = LinkedList.fromArray([1, 2, 3, 4, 5]); 280 | ``` 281 | 282 | ### LinkedListNode 283 | 284 | #### setValue(value: any) 285 | sets the value on the node. 286 | 287 | #### getValue(): any 288 | gets the value of the node. 289 | 290 | #### setNext(next: LinkedListNode) 291 | sets the next node. 292 | 293 | #### getNext(): LinkedListNode 294 | gets the next node. 295 | 296 | #### hasNext(): boolean 297 | checks if node has a next node. 298 | 299 | #### clone(): LinkedListNode 300 | clones the node without next reference. 301 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @datastrucures-js/linked-list 2 | [![npm](https://img.shields.io/npm/v/@datastructures-js/linked-list.svg)](https://www.npmjs.com/package/@datastructures-js/linked-list) 3 | [![npm](https://img.shields.io/npm/dm/@datastructures-js/linked-list.svg)](https://www.npmjs.com/package/@datastructures-js/linked-list) [![npm](https://img.shields.io/badge/node-%3E=%206.0-blue.svg)](https://www.npmjs.com/package/@datastructures-js/linked-list) 4 | 5 | a javascript implementation of LinkedList & DoublyLinkedList. 6 | 7 | ### [LinkedList](https://github.com/datastructures-js/linked-list/blob/master/LinkedList.md) 8 | 9 | ### [DoublyLinkedList](https://github.com/datastructures-js/linked-list/blob/master/DoublyLinkedList.md) 10 | 11 | ## Build 12 | ``` 13 | grunt build 14 | ``` 15 | 16 | ## License 17 | The MIT License. Full License is [here](https://github.com/datastructures-js/linked-list/blob/master/LICENSE) 18 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { LinkedList } from './src/linkedList'; 2 | import { LinkedListNode } from './src/linkedListNode'; 3 | import { DoublyLinkedList } from './src/doublyLinkedList'; 4 | import { DoublyLinkedListNode } from './src/doublyLinkedListNode'; 5 | 6 | export { LinkedList } 7 | export { LinkedListNode } 8 | export { DoublyLinkedList } 9 | export { DoublyLinkedListNode } 10 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { LinkedListNode } = require('./src/linkedListNode'); 2 | const { LinkedList } = require('./src/linkedList'); 3 | 4 | const { DoublyLinkedListNode } = require('./src/doublyLinkedListNode'); 5 | const { DoublyLinkedList } = require('./src/doublyLinkedList'); 6 | 7 | module.exports = { 8 | LinkedListNode, 9 | LinkedList, 10 | DoublyLinkedListNode, 11 | DoublyLinkedList 12 | }; 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@datastructures-js/linked-list", 3 | "version": "6.1.3", 4 | "description": "a javascript implementation of LinkedList & DoublyLinkedList", 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/linked-list.git" 13 | }, 14 | "keywords": [ 15 | "linked list", 16 | "linked list es6", 17 | "linked list js", 18 | "doubly linked list", 19 | "doubly linked list es6", 20 | "doubly linked list js" 21 | ], 22 | "author": "Eyas Ranjous ", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/datastructures-js/linked-list/issues" 26 | }, 27 | "homepage": "https://github.com/datastructures-js/linked-list#readme", 28 | "devDependencies": { 29 | "chai": "^4.2.0", 30 | "eslint": "^6.7.2", 31 | "eslint-config-airbnb-base": "^14.0.0", 32 | "eslint-plugin-import": "^2.19.1", 33 | "grunt": "^1.0.4", 34 | "grunt-eslint": "^22.0.0", 35 | "grunt-mocha-istanbul": "^5.0.2", 36 | "grunt-mocha-test": "^0.13.3", 37 | "istanbul": "^0.4.5", 38 | "mocha": "^6.2.2" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/doublyLinkedList.d.ts: -------------------------------------------------------------------------------- 1 | import { DoublyLinkedListNode } from './doublyLinkedListNode'; 2 | 3 | export class DoublyLinkedList { 4 | constructor(); 5 | insertFirst(value: T | DoublyLinkedListNode): T extends DoublyLinkedListNode ? T : DoublyLinkedListNode; 6 | insertLast(value: T | DoublyLinkedListNode): T extends DoublyLinkedListNode ? T : DoublyLinkedListNode; 7 | insertAt(position: number, value: T | DoublyLinkedListNode): T extends DoublyLinkedListNode ? T : DoublyLinkedListNode; 8 | insertBefore(value: T | DoublyLinkedListNode, node?: T extends DoublyLinkedListNode ? T : DoublyLinkedListNode): T extends DoublyLinkedListNode ? T : DoublyLinkedListNode; 9 | insertAfter(value: T | DoublyLinkedListNode, node?: T extends DoublyLinkedListNode ? T : DoublyLinkedListNode): T extends DoublyLinkedListNode ? T : DoublyLinkedListNode; 10 | removeFirst(): T extends DoublyLinkedListNode ? T : DoublyLinkedListNode; 11 | removeLast(): T extends DoublyLinkedListNode ? T : DoublyLinkedListNode; 12 | removeAt(position: number): T extends DoublyLinkedListNode ? T : DoublyLinkedListNode; 13 | remove(node: T extends DoublyLinkedListNode ? T : DoublyLinkedListNode): T extends DoublyLinkedListNode ? T : DoublyLinkedListNode; 14 | removeEach(cb: (node: T extends DoublyLinkedListNode ? T : DoublyLinkedListNode, position: number) => boolean): number; 15 | forEach(cb: (node: T extends DoublyLinkedListNode ? T : DoublyLinkedListNode, position: number) => void): void; 16 | forEachReverse(cb: (node: T extends DoublyLinkedListNode ? T : DoublyLinkedListNode, position: number) => void): void; 17 | find( 18 | cb: (node: T extends DoublyLinkedListNode ? T : DoublyLinkedListNode) => boolean, 19 | startingNode?: T extends DoublyLinkedListNode ? T : DoublyLinkedListNode 20 | ): T extends DoublyLinkedListNode ? T : DoublyLinkedListNode; 21 | findReverse( 22 | cb: (node: T extends DoublyLinkedListNode ? T : DoublyLinkedListNode) => boolean, 23 | startingNode?: T extends DoublyLinkedListNode ? T : DoublyLinkedListNode 24 | ): T extends DoublyLinkedListNode ? T : DoublyLinkedListNode; 25 | filter(cb: (node: T extends DoublyLinkedListNode ? T : DoublyLinkedListNode, position: number) => boolean): DoublyLinkedList; 26 | head(): T extends DoublyLinkedListNode ? T : DoublyLinkedListNode; 27 | tail(): T extends DoublyLinkedListNode ? T : DoublyLinkedListNode; 28 | count(): number; 29 | toArray(): (T extends DoublyLinkedListNode ? T : DoublyLinkedListNode)[]; 30 | isEmpty(): boolean; 31 | clear(): void; 32 | static fromArray(values: (T extends DoublyLinkedListNode ? T : DoublyLinkedListNode)[]): DoublyLinkedList; 33 | } 34 | -------------------------------------------------------------------------------- /src/doublyLinkedList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * datastructures-js/linked-list 3 | * @license MIT 4 | * @copyright 2020 Eyas Ranjous 5 | */ 6 | 7 | const { DoublyLinkedListNode } = require('./doublyLinkedListNode'); 8 | 9 | /** 10 | * @class 11 | */ 12 | class DoublyLinkedList { 13 | constructor() { 14 | this._head = null; 15 | this._tail = null; 16 | this._count = 0; 17 | } 18 | 19 | /** 20 | * Adds a node at the beginning of the list. 21 | * @public 22 | * @param {T | DoublyLinkedListNode} value 23 | * @returns {T | DoublyLinkedListNode} 24 | */ 25 | insertFirst(value) { 26 | let newNode = value; 27 | if (!(newNode instanceof DoublyLinkedListNode)) { 28 | newNode = new DoublyLinkedListNode(value); 29 | } 30 | 31 | if (this.isEmpty()) { 32 | this._head = newNode; 33 | this._tail = newNode; 34 | } else { 35 | this._head.setPrev(newNode); 36 | newNode.setNext(this._head); 37 | this._head = newNode; 38 | } 39 | this._count += 1; 40 | return newNode; 41 | } 42 | 43 | /** 44 | * Adds a node at the end of the list. 45 | * @public 46 | * @param {T | DoublyLinkedListNode} value 47 | * @returns {T | DoublyLinkedListNode} 48 | */ 49 | insertLast(value) { 50 | let newNode = value; 51 | if (!(newNode instanceof DoublyLinkedListNode)) { 52 | newNode = new DoublyLinkedListNode(value); 53 | } 54 | 55 | if (this.isEmpty()) { 56 | this._head = newNode; 57 | this._tail = newNode; 58 | } else { 59 | newNode.setPrev(this._tail); 60 | this._tail.setNext(newNode); 61 | this._tail = newNode; 62 | } 63 | this._count += 1; 64 | return newNode; 65 | } 66 | 67 | /** 68 | * Adds a node at a specific position. 69 | * @public 70 | * @param {number} position 71 | * @param {T | DoublyLinkedListNode} value 72 | * @returns {T | DoublyLinkedListNode} 73 | */ 74 | insertAt(position, value) { 75 | if ( 76 | Number.isNaN(+position) 77 | || position < 0 || position > this._count 78 | ) { 79 | throw new Error('.insertAt expects a position num <= linked list size'); 80 | } 81 | 82 | if (position === 0) { 83 | return this.insertFirst(value); 84 | } 85 | 86 | if (position === this._count) { 87 | return this.insertLast(value); 88 | } 89 | 90 | let currentPosition = 1; 91 | let prev = this._head; 92 | while (currentPosition < position) { 93 | currentPosition += 1; 94 | prev = prev.getNext(); 95 | } 96 | 97 | let newNode = value; 98 | if (!(newNode instanceof DoublyLinkedListNode)) { 99 | newNode = new DoublyLinkedListNode(value); 100 | } 101 | newNode.setNext(prev.getNext()); 102 | newNode.setPrev(prev); 103 | newNode.getNext().setPrev(newNode); 104 | newNode.getPrev().setNext(newNode); 105 | this._count += 1; 106 | return newNode; 107 | } 108 | 109 | /** 110 | * Adds a node before an existing node. 111 | * @public 112 | * @param {T | DoublyLinkedListNode} value 113 | * @param {T | DoublyLinkedListNode} existingNode 114 | * @returns {T | DoublyLinkedListNode} 115 | */ 116 | insertBefore(value, existingNode) { 117 | if (!existingNode) { 118 | return this.insertLast(value); 119 | } 120 | 121 | if (existingNode === this._head) { 122 | return this.insertFirst(value); 123 | } 124 | 125 | let newNode = value; 126 | if (!(newNode instanceof DoublyLinkedListNode)) { 127 | newNode = new DoublyLinkedListNode(value); 128 | } 129 | 130 | newNode.setNext(existingNode); 131 | newNode.setPrev(existingNode.getPrev()); 132 | 133 | newNode.getNext().setPrev(newNode); 134 | newNode.getPrev().setNext(newNode); 135 | 136 | this._count += 1; 137 | 138 | return newNode; 139 | } 140 | 141 | /** 142 | * Adds a node after an existing node. 143 | * @public 144 | * @param {T | DoublyLinkedListNode} value 145 | * @param {T | DoublyLinkedListNode} existingNode 146 | * @returns {T | DoublyLinkedListNode} 147 | */ 148 | insertAfter(value, existingNode) { 149 | if (!existingNode) { 150 | return this.insertFirst(value); 151 | } 152 | 153 | if (existingNode === this._tail) { 154 | return this.insertLast(value); 155 | } 156 | 157 | let newNode = value; 158 | if (!(newNode instanceof DoublyLinkedListNode)) { 159 | newNode = new DoublyLinkedListNode(value); 160 | } 161 | 162 | newNode.setPrev(existingNode); 163 | newNode.setNext(existingNode.getNext()); 164 | 165 | newNode.getNext().setPrev(newNode); 166 | newNode.getPrev().setNext(newNode); 167 | 168 | this._count += 1; 169 | 170 | return newNode; 171 | } 172 | 173 | /** 174 | * Removes the head node. 175 | * @public 176 | * @returns {T | DoublyLinkedListNode} 177 | */ 178 | removeFirst() { 179 | if (this.isEmpty()) return null; 180 | 181 | const removedNode = this._head; 182 | if (this._head.hasNext()) { 183 | this._head = this._head.getNext(); 184 | this._head.setPrev(null); 185 | } else { 186 | this._head = null; 187 | this._tail = null; 188 | } 189 | this._count -= 1; 190 | return removedNode.setNext(null); 191 | } 192 | 193 | /** 194 | * Removes the tail node. 195 | * @public 196 | * @returns {T | DoublyLinkedListNode} 197 | */ 198 | removeLast() { 199 | if (this.isEmpty()) return null; 200 | 201 | const removedNode = this._tail; 202 | if (this._tail.hasPrev()) { 203 | this._tail = this._tail.getPrev(); 204 | this._tail.setNext(null); 205 | } else { 206 | this._head = null; 207 | this._tail = null; 208 | } 209 | this._count -= 1; 210 | return removedNode.setPrev(null); 211 | } 212 | 213 | /** 214 | * Removes a node in a specific position. 215 | * @public 216 | * @param {number} position 217 | * @returns {T | DoublyLinkedListNode} 218 | */ 219 | removeAt(position) { 220 | if ( 221 | Number.isNaN(+position) 222 | || position < 0 223 | || position >= this._count 224 | ) { 225 | return null; 226 | } 227 | 228 | if (position === 0) { 229 | return this.removeFirst(); 230 | } 231 | 232 | if (position === this._count - 1) { 233 | return this.removeLast(); 234 | } 235 | 236 | let currentPosition = 1; 237 | let current = this._head.getNext(); 238 | while (currentPosition < position) { 239 | currentPosition += 1; 240 | current = current.getNext(); 241 | } 242 | return this.remove(current); 243 | } 244 | 245 | /** 246 | * Removes a node from the list by its reference. 247 | * @public 248 | * @param {T | DoublyLinkedListNode} node 249 | * @returns {T | DoublyLinkedListNode} 250 | */ 251 | remove(node) { 252 | if (node && !(node instanceof DoublyLinkedListNode)) { 253 | throw new Error('remove: expects a DoublyLinkedListNode node'); 254 | } 255 | 256 | if (!node) { 257 | return null; 258 | } 259 | 260 | if (!node.hasPrev()) { 261 | return this.removeFirst(); 262 | } 263 | 264 | if (!node.hasNext()) { 265 | return this.removeLast(); 266 | } 267 | 268 | node.getPrev().setNext(node.getNext()); 269 | node.getNext().setPrev(node.getPrev()); 270 | this._count -= 1; 271 | return node.setPrev(null).setNext(null); 272 | } 273 | 274 | /** 275 | * Removes all nodes based on a callback. 276 | * @public 277 | * @param {function} cb 278 | * @returns {number} number of removed nodes 279 | */ 280 | removeEach(cb) { 281 | if (typeof cb !== 'function') { 282 | throw new Error('.removeEach(cb) expects a callback'); 283 | } 284 | 285 | let removedCount = 0; 286 | let position = 0; 287 | let current = this._head; 288 | while (current instanceof DoublyLinkedListNode) { 289 | if (cb(current, position)) { 290 | const next = current.getNext(); 291 | this.remove(current); 292 | removedCount += 1; 293 | current = next; 294 | } else { 295 | current = current.getNext(); 296 | } 297 | position += 1; 298 | } 299 | return removedCount; 300 | } 301 | 302 | /** 303 | * Traverses the list from beginning to end. 304 | * @public 305 | * @param {function} cb 306 | */ 307 | forEach(cb) { 308 | if (typeof cb !== 'function') { 309 | throw new Error('.forEach(cb) expects a callback'); 310 | } 311 | 312 | let current = this._head; 313 | let position = 0; 314 | while (current instanceof DoublyLinkedListNode) { 315 | cb(current, position); 316 | position += 1; 317 | current = current.getNext(); 318 | } 319 | } 320 | 321 | /** 322 | * Traverses the list backward from end to beginning 323 | * @public 324 | * @param {function} cb 325 | */ 326 | forEachReverse(cb) { 327 | if (typeof cb !== 'function') { 328 | throw new Error('.forEachReverse(cb) expects a callback'); 329 | } 330 | 331 | let current = this._tail; 332 | let position = this._count - 1; 333 | while (current instanceof DoublyLinkedListNode) { 334 | cb(current, position); 335 | position -= 1; 336 | current = current.getPrev(); 337 | } 338 | } 339 | 340 | /** 341 | * Finds a node in the list using a callback 342 | * @public 343 | * @param {function} cb 344 | * @param {T | DoublyLinkedListNode} [startingNode] 345 | * @returns {T | DoublyLinkedListNode} 346 | */ 347 | find(cb, startingNode = this._head) { 348 | if (typeof cb !== 'function') { 349 | throw new Error('.find(cb) expects a callback'); 350 | } 351 | 352 | if (startingNode && !(startingNode instanceof DoublyLinkedListNode)) { 353 | throw new Error('.find(cb) expects to start from a DoublyLinkedListNode'); 354 | } 355 | 356 | let current = startingNode; 357 | while (current instanceof DoublyLinkedListNode) { 358 | if (cb(current)) { 359 | return current; 360 | } 361 | current = current.getNext(); 362 | } 363 | return null; 364 | } 365 | 366 | /** 367 | * Finds a node in the list using a callback in reverse order 368 | * @public 369 | * @param {function} cb 370 | * @param {T | DoublyLinkedListNode} [startingNode] 371 | * @returns {T | DoublyLinkedListNode} 372 | */ 373 | findReverse(cb, startingNode = this._tail) { 374 | if (typeof cb !== 'function') { 375 | throw new Error('.findReverse(cb) expects a callback'); 376 | } 377 | 378 | if (startingNode && !(startingNode instanceof DoublyLinkedListNode)) { 379 | throw new Error('.findReverse(cb) expects to start from a DoublyLinkedListNode'); 380 | } 381 | 382 | let current = startingNode; 383 | while (current instanceof DoublyLinkedListNode) { 384 | if (cb(current)) { 385 | return current; 386 | } 387 | current = current.getPrev(); 388 | } 389 | return null; 390 | } 391 | 392 | /** 393 | * Filters the list based on a callback. 394 | * @public 395 | * @param {function} cb 396 | * @returns {LinkedList} 397 | */ 398 | filter(cb) { 399 | if (typeof cb !== 'function') { 400 | throw new Error('.filter(cb) expects a callback'); 401 | } 402 | 403 | const result = new DoublyLinkedList(); 404 | this.forEach((node, position) => { 405 | if (cb(node, position)) { 406 | result.insertLast(node.clone()); 407 | } 408 | }); 409 | return result; 410 | } 411 | 412 | /** 413 | * Returns the head node. 414 | * @public 415 | * @returns {T | DoublyLinkedListNode} 416 | */ 417 | head() { 418 | return this._head; 419 | } 420 | 421 | /** 422 | * Returns the tail node. 423 | * @public 424 | * @returns {T | DoublyLinkedListNode} 425 | */ 426 | tail() { 427 | return this._tail; 428 | } 429 | 430 | /** 431 | * Returns the nodes count in the list. 432 | * @public 433 | * @returns {number} 434 | */ 435 | count() { 436 | return this._count; 437 | } 438 | 439 | /** 440 | * Converts the doubly linked list into an array. 441 | * @public 442 | * @returns {array} 443 | */ 444 | toArray() { 445 | const result = []; 446 | this.forEach((node) => result.push(node)); 447 | return result; 448 | } 449 | 450 | /** 451 | * Checks if the list is empty. 452 | * @public 453 | * @returns {boolean} 454 | */ 455 | isEmpty() { 456 | return this._head === null; 457 | } 458 | 459 | /** 460 | * Clears the list 461 | * @public 462 | */ 463 | clear() { 464 | this._head = null; 465 | this._tail = null; 466 | this._count = 0; 467 | } 468 | 469 | /** 470 | * Creates a doubly linked list from an array 471 | * @public 472 | * @static 473 | * @param {array} values 474 | * @return {DoublyLinkedList} 475 | */ 476 | static fromArray(values) { 477 | if (!Array.isArray(values)) { 478 | throw new Error('cannot create DoublyLinkedList from none-array values'); 479 | } 480 | 481 | const doublyLinkedList = new DoublyLinkedList(); 482 | values.forEach((value) => { 483 | doublyLinkedList.insertLast(value); 484 | }); 485 | return doublyLinkedList; 486 | } 487 | } 488 | 489 | exports.DoublyLinkedList = DoublyLinkedList; 490 | -------------------------------------------------------------------------------- /src/doublyLinkedListNode.d.ts: -------------------------------------------------------------------------------- 1 | export class DoublyLinkedListNode { 2 | constructor(value?: any, prev?: DoublyLinkedListNode, next?: DoublyLinkedListNode); 3 | setValue(value: any): DoublyLinkedListNode; 4 | getValue(): any; 5 | setNext(next: DoublyLinkedListNode): DoublyLinkedListNode; 6 | getNext(): DoublyLinkedListNode; 7 | hasNext(): boolean; 8 | setPrev(prev: DoublyLinkedListNode): DoublyLinkedListNode; 9 | getPrev(): DoublyLinkedListNode; 10 | hasPrev(): boolean; 11 | clone(): DoublyLinkedListNode; 12 | } 13 | -------------------------------------------------------------------------------- /src/doublyLinkedListNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * datastructures-js/linked-list 3 | * @copyright 2020 Eyas Ranjous 4 | * @license MIT 5 | */ 6 | 7 | /** 8 | * @class 9 | */ 10 | class DoublyLinkedListNode { 11 | /** 12 | * Creates a doubly linked list node. 13 | * @param {any} value 14 | * @param {DoublyLinkedListNode} [prev] 15 | * @param {DoublyLinkedListNode} [next] 16 | */ 17 | constructor(value, prev, next) { 18 | this._value = value; 19 | this.setPrev(prev); 20 | this.setNext(next); 21 | } 22 | 23 | /** 24 | * @public 25 | * @param {object} value 26 | */ 27 | setValue(value) { 28 | this._value = value; 29 | return this; 30 | } 31 | 32 | /** 33 | * @public 34 | * @returns {object} 35 | */ 36 | getValue() { 37 | return this._value; 38 | } 39 | 40 | /** 41 | * @public 42 | * @param {DoublyLinkedListNode} [next] 43 | * @returns {DoublyLinkedListNode} 44 | */ 45 | setNext(next) { 46 | if (next && !(next instanceof DoublyLinkedListNode)) { 47 | throw new Error('setNext expects a DoublyLinkedListNode or null'); 48 | } 49 | this._next = next || null; 50 | return this; 51 | } 52 | 53 | /** 54 | * @public 55 | * @returns {DoublyLinkedListNode} 56 | */ 57 | getNext() { 58 | return this._next; 59 | } 60 | 61 | /** 62 | * @public 63 | * @returns {boolean} 64 | */ 65 | hasNext() { 66 | return this._next instanceof DoublyLinkedListNode; 67 | } 68 | 69 | /** 70 | * @public 71 | * @param {DoublyLinkedListNode} [prev] 72 | * @returns {DoublyLinkedList} 73 | */ 74 | setPrev(prev) { 75 | if (prev && !(prev instanceof DoublyLinkedListNode)) { 76 | throw new Error('setPrev expects a DoublyLinkedListNode or null'); 77 | } 78 | this._prev = prev || null; 79 | return this; 80 | } 81 | 82 | /** 83 | * @public 84 | * @returns {DoublyLinkedListNode} 85 | */ 86 | getPrev() { 87 | return this._prev; 88 | } 89 | 90 | /** 91 | * @public 92 | * @returns {boolean} 93 | */ 94 | hasPrev() { 95 | return this._prev instanceof DoublyLinkedListNode; 96 | } 97 | 98 | /** 99 | * @public 100 | * @returns {DoublyLinkedListNode} 101 | */ 102 | clone() { 103 | const props = { ...this }; 104 | const clone = Reflect.construct(this.constructor, []); 105 | Object.keys(props).forEach((prop) => { 106 | clone[prop] = props[prop]; 107 | }); 108 | clone.setNext(null); 109 | clone.setPrev(null); 110 | return clone; 111 | } 112 | } 113 | 114 | exports.DoublyLinkedListNode = DoublyLinkedListNode; 115 | -------------------------------------------------------------------------------- /src/linkedList.d.ts: -------------------------------------------------------------------------------- 1 | import { LinkedListNode } from './linkedListNode'; 2 | 3 | export class LinkedList { 4 | constructor(); 5 | insertFirst(value: T | LinkedListNode): T extends LinkedListNode ? T : LinkedListNode; 6 | insertLast(value: T | LinkedListNode): T extends LinkedListNode ? T : LinkedListNode; 7 | insertAt(position: number, value: T | LinkedListNode): T extends LinkedListNode ? T : LinkedListNode; 8 | removeFirst(): T extends LinkedListNode ? T : LinkedListNode; 9 | removeLast(): T extends LinkedListNode ? T : LinkedListNode; 10 | removeEach(cb: (node: T extends LinkedListNode ? T : LinkedListNode, position: number) => boolean): number; 11 | removeAt(position: number): T extends LinkedListNode ? T : LinkedListNode; 12 | forEach(cb: (node: T extends LinkedListNode ? T : LinkedListNode, position: number) => void): void; 13 | find( 14 | cb: (node: T extends LinkedListNode ? T : LinkedListNode) => boolean, 15 | startingNode?: T extends LinkedListNode ? T : LinkedListNode 16 | ): T extends LinkedListNode ? T : LinkedListNode; 17 | filter(cb: (node: T extends LinkedListNode ? T : LinkedListNode, position: number) => boolean): LinkedList; 18 | head(): T extends LinkedListNode ? T : LinkedListNode; 19 | count(): number; 20 | toArray(): (T extends LinkedListNode ? T : LinkedListNode)[]; 21 | isEmpty(): boolean; 22 | clear(): void; 23 | static fromArray(values: (T extends LinkedListNode ? T : LinkedListNode)[]): LinkedList; 24 | } 25 | -------------------------------------------------------------------------------- /src/linkedList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * datastructures-js/linked-list 3 | * @license MIT 4 | * @copyright 2020 Eyas Ranjous 5 | */ 6 | 7 | const { LinkedListNode } = require('./linkedListNode'); 8 | 9 | /* 10 | * @class 11 | */ 12 | class LinkedList { 13 | constructor() { 14 | this._head = null; 15 | this._count = 0; 16 | } 17 | 18 | /** 19 | * Adds a node at the beginning of the list. 20 | * @public 21 | * @param {T | LinkedListNode} value 22 | * @returns {T | LinkedListNode} 23 | */ 24 | insertFirst(value) { 25 | let newNode = value; 26 | if (!(newNode instanceof LinkedListNode)) { 27 | newNode = new LinkedListNode(value); 28 | } 29 | newNode.setNext(this._head); 30 | this._head = newNode; 31 | this._count += 1; 32 | return this._head; 33 | } 34 | 35 | /** 36 | * Adds a node at the end of the list. 37 | * @public 38 | * @param {T | LinkedListNode} value 39 | * @param {T | LinkedListNode} [startingNode] 40 | * @returns {T | LinkedListNode} 41 | */ 42 | insertLast(value, startingNode) { 43 | if (this.isEmpty()) { 44 | return this.insertFirst(value); 45 | } 46 | 47 | if (startingNode && !(startingNode instanceof LinkedListNode)) { 48 | throw new Error('insertLast expects a LinkedListNode starting node'); 49 | } 50 | 51 | let current = startingNode || this._head; 52 | while (current.hasNext()) { 53 | current = current.getNext(); 54 | } 55 | 56 | let newNode = value; 57 | if (!(newNode instanceof LinkedListNode)) { 58 | newNode = new LinkedListNode(value); 59 | } 60 | current.setNext(newNode); 61 | this._count += 1; 62 | return newNode; 63 | } 64 | 65 | /** 66 | * Adds a node at a specific position. 67 | * @public 68 | * @param {number} position 69 | * @param {T | LinkedListNode} value 70 | * @returns {T | LinkedListNode} 71 | */ 72 | insertAt(position, value) { 73 | if ( 74 | Number.isNaN(+position) 75 | || position < 0 || position > this._count 76 | ) { 77 | throw new Error('.insertAt expects a position num <= linked list size'); 78 | } 79 | 80 | // head node is at position 0 81 | if (position === 0) { 82 | return this.insertFirst(value); 83 | } 84 | 85 | let currentPosition = 1; 86 | let prev = this._head; 87 | while (currentPosition < position) { 88 | currentPosition += 1; 89 | prev = prev.getNext(); 90 | } 91 | 92 | // add it at a position after the head, between prev & prev.getNext() 93 | let newNode = value; 94 | if (!(newNode instanceof LinkedListNode)) { 95 | newNode = new LinkedListNode(value); 96 | } 97 | newNode.setNext(prev.getNext()); 98 | prev.setNext(newNode); 99 | this._count += 1; 100 | return newNode; 101 | } 102 | 103 | /** 104 | * Removes the head node. 105 | * @public 106 | * @returns {T | LinkedListNode} 107 | */ 108 | removeFirst() { 109 | if (this.isEmpty()) return null; 110 | 111 | const removed = this._head; 112 | this._head = this._head.getNext(); 113 | this._count -= 1; 114 | return removed.setNext(null); 115 | } 116 | 117 | /** 118 | * Removes the last node in the list. 119 | * @public 120 | * @returns {T | LinkedListNode} 121 | */ 122 | removeLast() { 123 | if (this.isEmpty()) return null; 124 | 125 | let prev = null; 126 | let current = this._head; 127 | while (current.hasNext()) { 128 | prev = current; 129 | current = current.getNext(); 130 | } 131 | 132 | // linked list has 1 node 133 | if (prev === null) { 134 | return this.removeFirst(); 135 | } 136 | 137 | prev.setNext(null); 138 | this._count -= 1; 139 | return current; 140 | } 141 | 142 | /** 143 | * Removes all nodes based on a callback. 144 | * @public 145 | * @param {function} cb 146 | * @returns {number} number of removed nodes 147 | */ 148 | removeEach(cb) { 149 | if (typeof cb !== 'function') { 150 | throw new Error('.removeEach(cb) expects a callback'); 151 | } 152 | 153 | let removedCount = 0; 154 | let position = 0; 155 | let prev = null; 156 | let current = this._head; 157 | while (current instanceof LinkedListNode) { 158 | if (cb(current, position)) { 159 | const removedNode = current; 160 | if (prev === null) { 161 | this._head = this._head.getNext(); 162 | current = this._head; 163 | } else { 164 | prev.setNext(prev.getNext().getNext()); 165 | current = current.getNext(); 166 | } 167 | removedNode.setNext(null); 168 | this._count -= 1; 169 | removedCount += 1; 170 | } else { 171 | prev = current; 172 | current = current.getNext(); 173 | } 174 | position += 1; 175 | } 176 | return removedCount; 177 | } 178 | 179 | /** 180 | * Removes a node at a specific position. 181 | * @public 182 | * @param {number} position 183 | * @returns {T | LinkedListNode} 184 | */ 185 | removeAt(position) { 186 | if ( 187 | Number.isNaN(+position) 188 | || position < 0 189 | || position >= this._count 190 | ) { 191 | return null; 192 | } 193 | 194 | if (position === 0) { 195 | return this.removeFirst(); 196 | } 197 | 198 | let counter = 1; 199 | let prev = this._head; 200 | while (counter < position) { 201 | counter += 1; 202 | prev = prev.getNext(); 203 | } 204 | const removed = prev.getNext(); 205 | prev.setNext(prev.getNext().getNext()); 206 | this._count -= 1; 207 | return removed.setNext(null); 208 | } 209 | 210 | /** 211 | * Traverses the list from beginning to end. 212 | * @public 213 | * @param {function} cb 214 | */ 215 | forEach(cb) { 216 | if (typeof cb !== 'function') { 217 | throw new Error('.forEach(cb) expects a callback'); 218 | } 219 | 220 | let current = this._head; 221 | let position = 0; 222 | while (current instanceof LinkedListNode) { 223 | cb(current, position); 224 | position += 1; 225 | current = current.getNext(); 226 | } 227 | } 228 | 229 | /** 230 | * Finds one node in the list based on a callback. 231 | * @public 232 | * @param {function} cb 233 | * @param {T | LinkedListNode} [startingNode] 234 | * @returns {T | LinkedListNode} 235 | */ 236 | find(cb, startingNode = this._head) { 237 | if (typeof cb !== 'function') { 238 | throw new Error('.find(cb) expects a callback'); 239 | } 240 | 241 | if (startingNode && !(startingNode instanceof LinkedListNode)) { 242 | throw new Error('.find(cb) expects to start from a LinkedListNode'); 243 | } 244 | 245 | let current = startingNode; 246 | while (current instanceof LinkedListNode) { 247 | if (cb(current)) { 248 | return current; 249 | } 250 | current = current.getNext(); 251 | } 252 | return null; 253 | } 254 | 255 | /** 256 | * Filters the list based on a callback. 257 | * @public 258 | * @param {function} cb - callback should return true for required nodes. 259 | * @returns {LinkedList} 260 | */ 261 | filter(cb) { 262 | if (typeof cb !== 'function') { 263 | throw new Error('.filter(cb) expects a callback'); 264 | } 265 | 266 | let last = null; 267 | const result = new LinkedList(); 268 | this.forEach((node, position) => { 269 | if (cb(node, position)) { 270 | last = result.insertLast(node.clone(), last); 271 | } 272 | }); 273 | 274 | return result; 275 | } 276 | 277 | /** 278 | * Returns the head node. 279 | * @public 280 | * @returns {T | LinkedListNode} 281 | */ 282 | head() { 283 | return this._head; 284 | } 285 | 286 | /** 287 | * Returns the nodes count in the list. 288 | * @public 289 | * @returns {number} 290 | */ 291 | count() { 292 | return this._count; 293 | } 294 | 295 | /** 296 | * Converts the linked list into an array. 297 | * @public 298 | * @returns {array} 299 | */ 300 | toArray() { 301 | const result = []; 302 | this.forEach((node) => result.push(node)); 303 | return result; 304 | } 305 | 306 | /** 307 | * Checks if the list is empty. 308 | * @public 309 | * @returns {boolean} 310 | */ 311 | isEmpty() { 312 | return this._head === null; 313 | } 314 | 315 | /** 316 | * Clears the list 317 | * @public 318 | */ 319 | clear() { 320 | this._head = null; 321 | this._count = 0; 322 | } 323 | 324 | /** 325 | * Creates a linked list from an array 326 | * @public 327 | * @static 328 | * @param {array} values 329 | * @return {LinkedList} 330 | */ 331 | static fromArray(values) { 332 | if (!Array.isArray(values)) { 333 | throw new Error('cannot create LinkedList from none-array values'); 334 | } 335 | 336 | const linkedList = new LinkedList(); 337 | let lastInserted = null; 338 | values.forEach((value) => { 339 | lastInserted = linkedList.insertLast(value, lastInserted); 340 | }); 341 | return linkedList; 342 | } 343 | } 344 | 345 | exports.LinkedList = LinkedList; 346 | -------------------------------------------------------------------------------- /src/linkedListNode.d.ts: -------------------------------------------------------------------------------- 1 | export class LinkedListNode { 2 | constructor(value?: any, next?: LinkedListNode); 3 | setValue(value: any): LinkedListNode; 4 | getValue(): any; 5 | setNext(next: LinkedListNode): LinkedListNode; 6 | getNext(): LinkedListNode; 7 | hasNext(): boolean; 8 | clone(): LinkedListNode; 9 | } 10 | -------------------------------------------------------------------------------- /src/linkedListNode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * datastructures-js/linked-list 3 | * @license MIT 4 | * @copyright 2020 Eyas Ranjous 5 | * 6 | * @class 7 | */ 8 | class LinkedListNode { 9 | /** 10 | * Creates a linked list node. 11 | * @param {any} value 12 | * @param {LinkedListNode} [next] 13 | */ 14 | constructor(value, next) { 15 | this._value = value; 16 | this.setNext(next); 17 | } 18 | 19 | /** 20 | * @public 21 | * @param {any} value 22 | * @returns {LinkedListNode} 23 | */ 24 | setValue(value) { 25 | this._value = value; 26 | return this; 27 | } 28 | 29 | /** 30 | * @public 31 | * @returns {any} 32 | */ 33 | getValue() { 34 | return this._value; 35 | } 36 | 37 | /** 38 | * @public 39 | * @param {LinkedListNode} [next] 40 | * @returns {LinkedListNode} 41 | */ 42 | setNext(next) { 43 | if (next && !(next instanceof LinkedListNode)) { 44 | throw new Error('setNext expects a LinkedListNode or null'); 45 | } 46 | this._next = next || null; 47 | return this; 48 | } 49 | 50 | /** 51 | * @public 52 | * @returns {LinkedListNode} 53 | */ 54 | getNext() { 55 | return this._next; 56 | } 57 | 58 | /** 59 | * @public 60 | * @returns {boolean} 61 | */ 62 | hasNext() { 63 | return this._next instanceof LinkedListNode; 64 | } 65 | 66 | /** 67 | * @public 68 | * @returns {LinkedListNode} 69 | */ 70 | clone() { 71 | const props = { ...this }; 72 | const clone = Reflect.construct(this.constructor, []); 73 | Object.keys(props).forEach((prop) => { 74 | clone[prop] = props[prop]; 75 | }); 76 | clone.setNext(null); 77 | return clone; 78 | } 79 | } 80 | 81 | exports.LinkedListNode = LinkedListNode; 82 | -------------------------------------------------------------------------------- /test/doublyLinkedList.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { DoublyLinkedList } = require('../src/doublyLinkedList'); 3 | const { DoublyLinkedListNode } = require('../src/doublyLinkedListNode'); 4 | 5 | describe('doublyLinkedList tests', () => { 6 | const doublyLinkedList = new DoublyLinkedList(); 7 | 8 | describe('.insertFirst(value)', () => { 9 | it('add a node at the beginning of the list', () => { 10 | expect(doublyLinkedList.insertFirst(1)) 11 | .to.be.instanceof(DoublyLinkedListNode); 12 | expect(doublyLinkedList.insertFirst(2)) 13 | .to.be.instanceof(DoublyLinkedListNode); 14 | }); 15 | }); 16 | 17 | describe('.insertLast(value)', () => { 18 | it('add a node to the end of the list', () => { 19 | expect(doublyLinkedList.insertLast(3)) 20 | .to.be.instanceof(DoublyLinkedListNode); 21 | expect(doublyLinkedList.insertLast(4)) 22 | .to.be.instanceof(DoublyLinkedListNode); 23 | }); 24 | }); 25 | 26 | describe('.insertAt(position, value)', () => { 27 | it('add a node at a specific position', () => { 28 | expect(doublyLinkedList.insertAt(2, 5)) 29 | .to.be.instanceof(DoublyLinkedListNode); 30 | }); 31 | 32 | it('throws error position is not a valid number', () => { 33 | expect(() => doublyLinkedList.insertAt(-1, 5)).to.throws(Error) 34 | .and.to.have.property( 35 | 'message', 36 | '.insertAt expects a position num <= linked list size' 37 | ); 38 | }); 39 | }); 40 | 41 | describe('.count()', () => { 42 | it('get the count of nodes in the list', () => { 43 | expect(doublyLinkedList.count()).to.equal(5); 44 | }); 45 | }); 46 | 47 | describe('.isEmpty()', () => { 48 | it('checks if the list is empty', () => { 49 | expect(doublyLinkedList.isEmpty()).to.equal(false); 50 | }); 51 | }); 52 | 53 | describe('.head()', () => { 54 | it('get the head node', () => { 55 | expect(doublyLinkedList.head().getValue()).to.equal(2); 56 | }); 57 | }); 58 | 59 | describe('.tail()', () => { 60 | it('get the tail node', () => { 61 | expect(doublyLinkedList.tail().getValue()).to.equal(4); 62 | }); 63 | }); 64 | 65 | describe('.forEach(cb)', () => { 66 | it('traverse the linked list', () => { 67 | const values = []; 68 | const positions = []; 69 | doublyLinkedList.forEach((node, position) => { 70 | values.push(node.getValue()); 71 | positions.push(position); 72 | }); 73 | expect(values).to.deep.equal([2, 1, 5, 3, 4]); 74 | expect(positions).to.deep.equal([0, 1, 2, 3, 4]); 75 | }); 76 | 77 | it('throws an error if cb is not a function', () => { 78 | expect(() => doublyLinkedList.forEach('test')).to.throw(Error) 79 | .and.to.have.property('message', '.forEach(cb) expects a callback'); 80 | }); 81 | }); 82 | 83 | describe('.forEachReverse(cb)', () => { 84 | it('traverse the linked list', () => { 85 | const values = []; 86 | const positions = []; 87 | doublyLinkedList.forEachReverse((node, position) => { 88 | values.push(node.getValue()); 89 | positions.push(position); 90 | }); 91 | expect(values).to.deep.equal([4, 3, 5, 1, 2]); 92 | expect(positions).to.deep.equal([4, 3, 2, 1, 0]); 93 | }); 94 | 95 | it('throws an error if cb is not a function', () => { 96 | expect(() => doublyLinkedList.forEachReverse('test')).to.throw(Error) 97 | .and.to.have.property( 98 | 'message', 99 | '.forEachReverse(cb) expects a callback' 100 | ); 101 | }); 102 | }); 103 | 104 | describe('.find(cb)', () => { 105 | it('finds the first node that fulfills the callback condition', () => { 106 | const n1 = doublyLinkedList.find((node) => node.getValue() === 5); 107 | const n2 = doublyLinkedList.find((node) => node.getValue() === 7); 108 | expect(n1.getValue()).to.equal(5); 109 | expect(n2).to.equal(null); 110 | }); 111 | 112 | it('throws an error if cb is not a function', () => { 113 | expect(() => doublyLinkedList.find('test')).to.throw(Error) 114 | .and.to.have.property('message', '.find(cb) expects a callback'); 115 | }); 116 | 117 | it('fins a node from a starting node', () => { 118 | const n5 = doublyLinkedList.find((node) => node.getValue() === 5); 119 | const n4 = doublyLinkedList.find((node) => node.getValue() === 4, n5); 120 | expect(n4.getValue()).to.equal(4); 121 | }); 122 | 123 | it('throws an error if starting node is not valid', () => { 124 | expect(() => doublyLinkedList.find(() => null, 'test')).to.throw(Error) 125 | .and.to.have.property('message', '.find(cb) expects to start from a DoublyLinkedListNode'); 126 | }); 127 | }); 128 | 129 | describe('.findReverse(cb)', () => { 130 | it('finds the first node that fulfills the callback condition', () => { 131 | const n1 = doublyLinkedList.findReverse((node) => node.getValue() === 5); 132 | const n2 = doublyLinkedList.findReverse((node) => node.getValue() === 7); 133 | expect(n1.getValue()).to.equal(5); 134 | expect(n2).to.equal(null); 135 | }); 136 | 137 | it('throws an error if cb is not a function', () => { 138 | expect(() => doublyLinkedList.findReverse('test')).to.throw(Error) 139 | .and.to.have.property('message', '.findReverse(cb) expects a callback'); 140 | }); 141 | 142 | it('fins a node from a starting node', () => { 143 | const n5 = doublyLinkedList.findReverse((node) => node.getValue() === 5); 144 | const n2 = doublyLinkedList.findReverse((node) => node.getValue() === 2, n5); 145 | expect(n2.getValue()).to.equal(2); 146 | }); 147 | 148 | it('throws an error if starting node is not valid', () => { 149 | it('throws an error if starting node is not valid', () => { 150 | expect(() => doublyLinkedList.findReverse(() => null, 'test')).to.throw(Error) 151 | .and.to.have.property('message', '.find(cb) expects to start from a DoublyLinkedListNode'); 152 | }); 153 | }); 154 | }); 155 | 156 | describe('.toArray()', () => { 157 | it('convert the linked list to array in same order', () => { 158 | expect(doublyLinkedList.toArray().map((n) => n.getValue())) 159 | .to.deep.equal([2, 1, 5, 3, 4]); 160 | }); 161 | }); 162 | 163 | describe('.filter(cb)', () => { 164 | it('filters the linked list based on a callback', () => { 165 | expect(doublyLinkedList.filter( 166 | (node) => node.getValue() > 2 167 | ).toArray().map((n) => n.getValue())).to.deep.equal([5, 3, 4]); 168 | }); 169 | 170 | it('throws an error if cb is not a function', () => { 171 | expect(() => doublyLinkedList.filter('test')).to.throw(Error) 172 | .and.to.have.property('message', '.filter(cb) expects a callback'); 173 | }); 174 | }); 175 | 176 | describe('.removeFirst()', () => { 177 | it('remove the first node', () => { 178 | const removed = doublyLinkedList.removeFirst(); 179 | expect(removed.getValue()).to.equal(2); 180 | expect(doublyLinkedList.count()).to.equal(4); 181 | expect(doublyLinkedList.head().getValue()).to.equal(1); 182 | }); 183 | 184 | it('remove first node with a single node list', () => { 185 | const d = new DoublyLinkedList(); 186 | d.insertFirst('test'); 187 | expect(d.removeFirst().getValue()).to.equal('test'); 188 | expect(d.isEmpty()).to.equal(true); 189 | }); 190 | }); 191 | 192 | describe('.removeLast()', () => { 193 | it('remove the last node', () => { 194 | const removed = doublyLinkedList.removeLast(); 195 | expect(removed.getValue()).to.equal(4); 196 | expect(doublyLinkedList.count()).to.equal(3); 197 | expect(doublyLinkedList.find((n) => n.getValue() === 4)).to.equal(null); 198 | }); 199 | }); 200 | 201 | describe('.removeAt(position)', () => { 202 | it('remove a node', () => { 203 | const removed = doublyLinkedList.removeAt(1); 204 | expect(removed.getValue()).to.equal(5); 205 | expect(doublyLinkedList.count()).to.equal(2); 206 | expect(doublyLinkedList.find((n) => n.getValue() === 5)).to.equal(null); 207 | }); 208 | 209 | it('does nothing if position is not valid', () => { 210 | expect(doublyLinkedList.removeAt('test')).to.equal(null); 211 | expect(doublyLinkedList.removeAt(-1)).to.equal(null); 212 | expect(doublyLinkedList.removeAt(2)).to.equal(null); 213 | }); 214 | }); 215 | 216 | describe('.removeEach(cb)', () => { 217 | it('remove nodes based on a callback', () => { 218 | doublyLinkedList.insertLast(4); 219 | const removedCount = doublyLinkedList.removeEach((n) => n.getValue() > 1); 220 | expect(removedCount).to.deep.equal(2); 221 | expect(doublyLinkedList.toArray().map((n) => n.getValue())).to.deep.equal([1]); 222 | expect(doublyLinkedList.count()).to.equal(1); 223 | 224 | const dll2 = new DoublyLinkedList(); 225 | [12, 21, 31, 42].forEach((n) => dll2.insertLast(n)); 226 | dll2.removeEach((n) => n.getValue() <= 21); 227 | expect(dll2.toArray().map((n) => n.getValue())).to.eql([31, 42]); 228 | }); 229 | 230 | it('throws an error if cb is not a function', () => { 231 | expect(() => doublyLinkedList.removeEach('test')).to.throw(Error) 232 | .and.to.have.property('message', '.removeEach(cb) expects a callback'); 233 | }); 234 | }); 235 | 236 | describe('.clear()', () => { 237 | it('clear the linked list', () => { 238 | doublyLinkedList.clear(); 239 | expect(doublyLinkedList.count()).to.equal(0); 240 | expect(doublyLinkedList.isEmpty()).to.equal(true); 241 | }); 242 | }); 243 | 244 | describe('.fromArray(values)', () => { 245 | it('create a doubly linked list from an array', () => { 246 | expect(DoublyLinkedList.fromArray([1, 2, 3, 4, 5]).toArray().map((n) => n.getValue())) 247 | .to.eql([1, 2, 3, 4, 5]); 248 | }); 249 | }); 250 | 251 | describe('LinkedList with extended node type', () => { 252 | class Point extends DoublyLinkedListNode { 253 | constructor(x, y) { 254 | super(); 255 | this.x = x; 256 | this.y = y; 257 | } 258 | } 259 | const points = new DoublyLinkedList(); 260 | 261 | it('insert instances of extended type', () => { 262 | points.insertLast(new Point(1, 10)); 263 | points.insertLast(new Point(2, 15)); 264 | points.insertLast(new Point(3, 8)); 265 | expect(points.toArray().map( 266 | (p) => ({ x: p.x, y: p.y }) 267 | )).to.eql([ 268 | { x: 1, y: 10 }, 269 | { x: 2, y: 15 }, 270 | { x: 3, y: 8 } 271 | ]); 272 | }); 273 | 274 | it('filter custom nodes', () => { 275 | const filtered = points.filter((p) => p.x >= 2); 276 | expect(filtered.toArray().map( 277 | (p) => ({ x: p.x, y: p.y }) 278 | )).to.eql([ 279 | { x: 2, y: 15 }, 280 | { x: 3, y: 8 } 281 | ]); 282 | }); 283 | }); 284 | }); 285 | -------------------------------------------------------------------------------- /test/linkedList.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { LinkedList } = require('../src/linkedList'); 3 | const { LinkedListNode } = require('../src/linkedListNode'); 4 | 5 | describe('linkedList tests', () => { 6 | const linkedList = new LinkedList(); 7 | 8 | describe('.insertFirst(value)', () => { 9 | it('insert a node at the beginning', () => { 10 | expect(linkedList.insertFirst(1)).to.be.instanceof(LinkedListNode); 11 | expect(linkedList.insertFirst(2)).to.be.instanceof(LinkedListNode); 12 | }); 13 | }); 14 | 15 | describe('.insertLast(value)', () => { 16 | it('insert a node to the end ', () => { 17 | expect(linkedList.insertLast(3)).to.be.instanceof(LinkedListNode); 18 | expect(linkedList.insertLast(4)).to.be.instanceof(LinkedListNode); 19 | }); 20 | }); 21 | 22 | describe('.insertAt(position, value)', () => { 23 | it('add a node at a specific position', () => { 24 | expect(linkedList.insertAt(2, 5)).to.be.instanceof(LinkedListNode); 25 | }); 26 | 27 | it('throws an error position is not a valid number', () => { 28 | expect(() => linkedList.insertAt(-1, 5)).to.throws(Error) 29 | .and.to.have.property( 30 | 'message', 31 | '.insertAt expects a position num <= linked list size' 32 | ); 33 | }); 34 | }); 35 | 36 | describe('.count()', () => { 37 | it('get the count of nodes in the list', () => { 38 | expect(linkedList.count()).to.equal(5); 39 | }); 40 | }); 41 | 42 | describe('.isEmpty()', () => { 43 | it('checks if the list is empty', () => { 44 | expect(linkedList.isEmpty()).to.equal(false); 45 | }); 46 | }); 47 | 48 | describe('.head()', () => { 49 | it('get the head node', () => { 50 | expect(linkedList.head().getValue()).to.equal(2); 51 | }); 52 | }); 53 | 54 | describe('.forEach(cb)', () => { 55 | it('traverse the linked list', () => { 56 | const positions = []; 57 | const values = []; 58 | linkedList.forEach((node, position) => { 59 | values.push(node.getValue()); 60 | positions.push(position); 61 | }); 62 | expect(values).to.deep.equal([2, 1, 5, 3, 4]); 63 | expect(positions).to.deep.equal([0, 1, 2, 3, 4]); 64 | }); 65 | 66 | it('throws an error if cb is not a function', () => { 67 | expect(() => linkedList.forEach('test')).to.throw(Error) 68 | .and.to.have.property('message', '.forEach(cb) expects a callback'); 69 | }); 70 | }); 71 | 72 | describe('.find(cb)', () => { 73 | it('finds the first node that fulfills the callback condition', () => { 74 | const n1 = linkedList.find((node) => node.getValue() === 5); 75 | const n2 = linkedList.find((node) => node.getValue() === 7); 76 | expect(n1.getValue()).to.equal(5); 77 | expect(n2).to.equal(null); 78 | }); 79 | 80 | it('throws an error if cb is not a function', () => { 81 | expect(() => linkedList.find('test')).to.throw(Error) 82 | .and.to.have.property('message', '.find(cb) expects a callback'); 83 | }); 84 | 85 | it('fins a node from a starting node', () => { 86 | const n5 = linkedList.find((node) => node.getValue() === 5); 87 | const n4 = linkedList.find((node) => node.getValue() === 4, n5); 88 | expect(n4.getValue()).to.equal(4); 89 | }); 90 | 91 | it('throws an error if starting node is not valid', () => { 92 | expect(() => linkedList.find(() => null, 'test')).to.throw(Error) 93 | .and.to.have.property('message', '.find(cb) expects to start from a LinkedListNode'); 94 | }); 95 | }); 96 | 97 | describe('.toArray()', () => { 98 | it('convert the linkedList to array in same order', () => { 99 | expect(linkedList.toArray().map((n) => n.getValue())) 100 | .to.deep.equal([2, 1, 5, 3, 4]); 101 | }); 102 | }); 103 | 104 | describe('.filter(cb)', () => { 105 | it('filters the linked list based on a callback', () => { 106 | expect(linkedList.filter((node) => node.getValue() > 2).toArray().map((n) => n.getValue())) 107 | .to.deep.equal([5, 3, 4]); 108 | }); 109 | 110 | it('throws an error if cb is not a function', () => { 111 | expect(() => linkedList.filter('test')).to.throw(Error) 112 | .and.to.have.property('message', '.filter(cb) expects a callback'); 113 | }); 114 | }); 115 | 116 | describe('.removeFirst()', () => { 117 | it('remove the first node', () => { 118 | const removed = linkedList.removeFirst(); 119 | expect(removed.getValue()).to.equal(2); 120 | expect(linkedList.count()).to.equal(4); 121 | expect(linkedList.head().getValue()).to.equal(1); 122 | }); 123 | }); 124 | 125 | describe('.removeLast()', () => { 126 | it('remove the last node', () => { 127 | const removed = linkedList.removeLast(); 128 | expect(removed.getValue()).to.equal(4); 129 | expect(linkedList.count()).to.equal(3); 130 | expect(linkedList.find((n) => n.getValue() === 4)).to.equal(null); 131 | }); 132 | }); 133 | 134 | describe('.removeAt(position)', () => { 135 | it('remove a node', () => { 136 | const removed = linkedList.removeAt(1); 137 | expect(removed.getValue()).to.equal(5); 138 | expect(linkedList.count()).to.equal(2); 139 | expect(linkedList.find((n) => n.getValue() === 5)).to.equal(null); 140 | }); 141 | 142 | it('does nothing if position is not valid', () => { 143 | expect(linkedList.removeAt('test')).to.equal(null); 144 | expect(linkedList.removeAt(-1)).to.equal(null); 145 | expect(linkedList.removeAt(2)).to.equal(null); 146 | }); 147 | }); 148 | 149 | describe('.removeEach(cb)', () => { 150 | it('remove nodes based on a callback', () => { 151 | linkedList.insertLast(4); 152 | const n3 = linkedList.find((n) => n.getValue() === 3); 153 | expect(linkedList.removeEach((n) => n.getValue() > 1)).to.equal(2); 154 | expect(linkedList.count()).to.deep.equal(1); 155 | expect(linkedList.toArray().map((n) => n.getValue())).to.deep.equal([1]); 156 | expect(n3.getNext()).to.equal(null); 157 | 158 | const ll2 = new LinkedList(); 159 | [12, 21, 31, 42].forEach((n) => ll2.insertLast(n)); 160 | ll2.removeEach((n) => n.getValue() <= 21); 161 | expect(ll2.toArray().map((n) => n.getValue())).to.eql([31, 42]); 162 | }); 163 | 164 | it('throws an error if cb is not a function', () => { 165 | expect(() => linkedList.removeEach('test')).to.throw(Error) 166 | .and.to.have.property('message', '.removeEach(cb) expects a callback'); 167 | }); 168 | }); 169 | 170 | describe('.clear()', () => { 171 | it('clear the linked list', () => { 172 | linkedList.clear(); 173 | expect(linkedList.count()).to.equal(0); 174 | expect(linkedList.isEmpty()).to.equal(true); 175 | }); 176 | }); 177 | 178 | describe('.fromArray(values)', () => { 179 | it('create a linked list from an array', () => { 180 | expect(LinkedList.fromArray([1, 2, 3, 4, 5]).toArray().map((n) => n.getValue())) 181 | .to.eql([1, 2, 3, 4, 5]); 182 | }); 183 | }); 184 | 185 | describe('LinkedList with extended node type', () => { 186 | class Point extends LinkedListNode { 187 | constructor(x, y) { 188 | super(); 189 | this.x = x; 190 | this.y = y; 191 | } 192 | } 193 | const points = new LinkedList(); 194 | 195 | it('insert instances of extended type', () => { 196 | points.insertLast(new Point(1, 10)); 197 | points.insertLast(new Point(2, 15)); 198 | points.insertLast(new Point(3, 8)); 199 | expect(points.toArray().map( 200 | (p) => ({ x: p.x, y: p.y }) 201 | )).to.eql([ 202 | { x: 1, y: 10 }, 203 | { x: 2, y: 15 }, 204 | { x: 3, y: 8 } 205 | ]); 206 | }); 207 | 208 | it('filter custom nodes', () => { 209 | const filtered = points.filter((p) => p.x >= 2); 210 | expect(filtered.toArray().map( 211 | (p) => ({ x: p.x, y: p.y }) 212 | )).to.eql([ 213 | { x: 2, y: 15 }, 214 | { x: 3, y: 8 } 215 | ]); 216 | }); 217 | }); 218 | }); 219 | --------------------------------------------------------------------------------