main.js (15020B)
1 /** 2 * @license Apache-2.0 3 * 4 * Copyright (c) 2018 The Stdlib Authors. 5 * 6 * Licensed under the Apache License, Version 2.0 (the "License"); 7 * you may not use this file except in compliance with the License. 8 * You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software 13 * distributed under the License is distributed on an "AS IS" BASIS, 14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 * See the License for the specific language governing permissions and 16 * limitations under the License. 17 */ 18 19 /* eslint-disable no-restricted-syntax, no-invalid-this */ 20 21 'use strict'; 22 23 // MODULES // 24 25 var setReadOnly = require( './../../define-nonenumerable-read-only-property' ); 26 var setReadOnlyAccessor = require( './../../define-nonenumerable-read-only-accessor' ); 27 var iteratorSymbol = require( '@stdlib/symbol/iterator' ); 28 var Node = require( './node.js' ); // eslint-disable-line stdlib/no-redeclare 29 30 31 // MAIN // 32 33 /** 34 * Linked list constructor. 35 * 36 * @constructor 37 * @returns {LinkedList} linked list instance 38 * 39 * @example 40 * var list = new LinkedList(); 41 * 42 * // Add values to the list: 43 * list.push( 'foo' ).push( 'bar' ); 44 * 45 * // Remove the last value: 46 * var v = list.pop(); 47 * // returns 'bar' 48 * 49 * // Add a new value to the list: 50 * list.push( 'beep' ); 51 * 52 * // Remove the first value: 53 * v = list.shift(); 54 * // returns 'foo' 55 */ 56 function LinkedList() { 57 if ( !(this instanceof LinkedList) ) { 58 return new LinkedList(); 59 } 60 this._length = 0; 61 this._first = null; 62 this._last = null; 63 return this; 64 } 65 66 /** 67 * Clears the list. 68 * 69 * @name clear 70 * @memberof LinkedList.prototype 71 * @type {Function} 72 * @returns {LinkedList} list instance 73 * 74 * @example 75 * var list = new LinkedList(); 76 * 77 * // Add values to the list: 78 * list.push( 'foo' ).push( 'bar' ); 79 * 80 * // Peek at the first value: 81 * var v = list.first().value; 82 * // returns 'foo' 83 * 84 * // Examine the list length: 85 * var len = list.length; 86 * // returns 2 87 * 88 * // Clear all list items: 89 * list.clear(); 90 * 91 * // Peek at the first value: 92 * v = list.first(); 93 * // returns undefined 94 * 95 * // Examine the list length: 96 * len = list.length; 97 * // returns 0 98 */ 99 setReadOnly( LinkedList.prototype, 'clear', function clear() { 100 this._length = 0; 101 this._first = null; 102 this._last = null; 103 return this; 104 }); 105 106 /** 107 * Returns the first list node. 108 * 109 * @name first 110 * @memberof LinkedList.prototype 111 * @type {Function} 112 * @returns {(Node|void)} list node 113 * 114 * @example 115 * var list = new LinkedList(); 116 * 117 * // Add values to the list: 118 * list.push( 'foo' ).push( 'bar' ); 119 * 120 * // Peek at the first value: 121 * var v = list.first().value; 122 * // returns 'foo' 123 */ 124 setReadOnly( LinkedList.prototype, 'first', function first() { 125 if ( this._length ) { 126 return this._first; 127 } 128 }); 129 130 /** 131 * Inserts a value into the list **after** a provided list node. 132 * 133 * @name insert 134 * @memberof LinkedList.prototype 135 * @type {Function} 136 * @param {Node} node - node after which to insert the value 137 * @param {*} value - value to insert 138 * @throws {Error} must provide a node belonging to the list 139 * @returns {LinkedList} list instance 140 * 141 * @example 142 * var list = new LinkedList(); 143 * 144 * // Add values to the list: 145 * list.push( 'foo' ).push( 'bar' ).push( 'beep' ); 146 * 147 * // Determine the list length: 148 * var len = list.length; 149 * // returns 3 150 * 151 * // Get the second node: 152 * var node = list.first().next; 153 * 154 * // Insert a value after the second node: 155 * list.insert( node, 'boop' ); 156 * 157 * // Determine the list length: 158 * len = list.length; 159 * // returns 4 160 */ 161 setReadOnly( LinkedList.prototype, 'insert', function insert( node, value ) { 162 /* eslint-disable no-underscore-dangle */ 163 var n; 164 165 // Case: last node (equivalent to `push()`) 166 if ( node === this._last ) { 167 return this.push( value ); 168 } 169 // Unfortunately, we need to check whether we have been provided a node belonging to our list by walking the list. If we don't, we could erroneously increment the list length. This means our runtime goes from the theoretical O(1) to O(N). 170 n = this._first; 171 while ( n !== this._last && n !== node ) { 172 n = n._next; 173 } 174 // Check if we iterated through the entire list: 175 if ( n === this._last ) { 176 throw new Error( 'invalid argument. The list does not contain the provided list node.' ); 177 } 178 // Create a new list node: 179 n = new Node( value ); 180 181 // Update pointers: 182 node._next._prev = n; 183 n._next = node._next; 184 185 node._next = n; 186 n._prev = node; 187 188 // Increment the list length: 189 this._length += 1; 190 191 return this; 192 193 /* eslint-enable no-underscore-dangle */ 194 }); 195 196 /** 197 * Returns an iterator for iterating over a list. 198 * 199 * ## Notes 200 * 201 * - In order to prevent confusion arising from list mutation during iteration, a returned iterator **always** iterates over a list "snapshot", which is defined as the list of elements at the time of this method's invocation. 202 * 203 * @name iterator 204 * @memberof LinkedList.prototype 205 * @type {Function} 206 * @returns {Iterator} iterator 207 * 208 * @example 209 * var list = new LinkedList(); 210 * 211 * // Add values to the list: 212 * list.push( 'foo' ).push( 'bar' ); 213 * 214 * // Create an iterator: 215 * var it = list.iterator(); 216 * 217 * // Iterate over the list... 218 * var v = it.next().value; 219 * // returns 'foo' 220 * 221 * v = it.next().value; 222 * // returns 'bar' 223 * 224 * var bool = it.next().done; 225 * // returns true 226 */ 227 setReadOnly( LinkedList.prototype, 'iterator', function iterator() { 228 var values; 229 var iter; 230 var self; 231 var FLG; 232 var i; 233 234 self = this; 235 236 // Initialize the iteration index: 237 i = -1; 238 239 // Create a copy of list values (necessary in order to "snapshot" the list; otherwise, values could come and go between calls to `next`): 240 values = this.toArray(); 241 242 // Create an iterator protocol-compliant object: 243 iter = {}; 244 setReadOnly( iter, 'next', next ); 245 setReadOnly( iter, 'return', end ); 246 if ( iteratorSymbol ) { 247 setReadOnly( iter, iteratorSymbol, factory ); 248 } 249 return iter; 250 251 /** 252 * Returns an iterator protocol-compliant object containing the next iterated value. 253 * 254 * @private 255 * @returns {Object} iterator protocol-compliant object 256 */ 257 function next() { 258 i += 1; 259 if ( FLG || i >= values.length ) { 260 return { 261 'done': true 262 }; 263 } 264 return { 265 'value': values[ i ], 266 'done': false 267 }; 268 } 269 270 /** 271 * Finishes an iterator. 272 * 273 * @private 274 * @param {*} [value] - value to return 275 * @returns {Object} iterator protocol-compliant object 276 */ 277 function end( value ) { 278 FLG = true; 279 if ( arguments.length ) { 280 return { 281 'value': value, 282 'done': true 283 }; 284 } 285 return { 286 'done': true 287 }; 288 } 289 290 /** 291 * Returns a new iterator. 292 * 293 * @private 294 * @returns {Iterator} iterator 295 */ 296 function factory() { 297 return self.iterator(); 298 } 299 }); 300 301 /** 302 * Returns the last node. 303 * 304 * @name last 305 * @memberof LinkedList.prototype 306 * @type {Function} 307 * @returns {(Node|void)} list node 308 * 309 * @example 310 * var list = new LinkedList(); 311 * 312 * // Add values to the list: 313 * list.push( 'foo' ).push( 'bar' ); 314 * 315 * // Peek at the last value: 316 * var v = list.last().value; 317 * // returns 'bar' 318 */ 319 setReadOnly( LinkedList.prototype, 'last', function last() { 320 if ( this._length ) { 321 return this._last; 322 } 323 }); 324 325 /** 326 * List length. 327 * 328 * @name length 329 * @memberof LinkedList.prototype 330 * @type {NonNegativeInteger} 331 * 332 * @example 333 * var list = new LinkedList(); 334 * 335 * // Examine the initial list length: 336 * var len = list.length; 337 * // returns 0 338 * 339 * // Add values to the list: 340 * list.push( 'foo' ).push( 'bar' ); 341 * 342 * // Retrieve the current list length: 343 * len = list.length; 344 * // returns 2 345 */ 346 setReadOnlyAccessor( LinkedList.prototype, 'length', function get() { 347 return this._length; 348 }); 349 350 /** 351 * Removes a value from the end of the list. 352 * 353 * @name pop 354 * @memberof LinkedList.prototype 355 * @type {Function} 356 * @returns {(*|void)} removed value 357 * 358 * @example 359 * var list = new LinkedList(); 360 * 361 * // Add values to the list: 362 * list.push( 'foo' ).push( 'bar' ); 363 * 364 * // Remove the last value: 365 * var v = list.pop(); 366 * // returns 'bar' 367 * 368 * // Add a new value to the list: 369 * list.push( 'beep' ); 370 * 371 * // Remove the last value: 372 * v = list.pop(); 373 * // returns 'beep' 374 */ 375 setReadOnly( LinkedList.prototype, 'pop', function pop() { 376 /* eslint-disable no-underscore-dangle */ 377 var value; 378 if ( this._length ) { 379 // Retrieve the last value: 380 value = this._last.value; 381 382 // Check whether we have a new "tail" or whether we have emptied the list... 383 if ( this._last._prev ) { 384 this._last = this._last._prev; 385 this._last._next = null; 386 } else { 387 // List is empty: 388 this._first = null; 389 this._last = null; 390 } 391 // Decrement the list length: 392 this._length -= 1; 393 } 394 return value; 395 396 /* eslint-enable no-underscore-dangle */ 397 }); 398 399 /** 400 * Adds a value to the end of the list. 401 * 402 * @name push 403 * @memberof LinkedList.prototype 404 * @type {Function} 405 * @returns {LinkedList} list instance 406 * 407 * @example 408 * var list = new LinkedList(); 409 * 410 * // Add values to the list: 411 * list.push( 'foo' ).push( 'bar' ); 412 * 413 * // Remove the last value: 414 * var v = list.pop(); 415 * // returns 'bar' 416 * 417 * // Add a new value to the list: 418 * list.push( 'beep' ); 419 * 420 * // Remove the last value: 421 * v = list.pop(); 422 * // returns 'beep' 423 */ 424 setReadOnly( LinkedList.prototype, 'push', function push( value ) { 425 var node; 426 427 // Create a new list node: 428 node = new Node( value ); 429 430 // Check whether the list is currently empty... 431 if ( this._length === 0 ) { 432 // This is the only list node, making it both the first and last node: 433 this._first = node; 434 this._last = node; 435 } else { 436 // Link the new node to the previous last node: 437 node._prev = this._last; // eslint-disable-line no-underscore-dangle 438 439 // Link the previous last node to the new node: 440 this._last._next = node; // eslint-disable-line no-underscore-dangle 441 442 // Update the pointer for the last node: 443 this._last = node; 444 } 445 // Increment the list length: 446 this._length += 1; 447 448 return this; 449 }); 450 451 /** 452 * Removes a list node from the list. 453 * 454 * @name remove 455 * @memberof LinkedList.prototype 456 * @type {Function} 457 * @param {Node} node - node to remove 458 * @throws {Error} must provide a node belonging to the list 459 * @returns {(*|void)} removed value 460 * 461 * @example 462 * var list = new LinkedList(); 463 * 464 * // Add values to the list: 465 * list.push( 'foo' ).push( 'bar' ).push( 'beep' ); 466 * 467 * // Determine the list length: 468 * var len = list.length; 469 * // returns 3 470 * 471 * // Get the second node: 472 * var node = list.first().next; 473 * 474 * // Remove the second node: 475 * var v = list.remove( node ); 476 * // returns 'bar' 477 * 478 * // Determine the list length: 479 * len = list.length; 480 * // returns 2 481 */ 482 setReadOnly( LinkedList.prototype, 'remove', function remove( node ) { 483 /* eslint-disable no-underscore-dangle */ 484 var value; 485 var n; 486 487 // Case: first node (equivalent to `shift()`) 488 if ( node === this._first ) { 489 return this.shift(); 490 } 491 // Case: last node (equivalent to `pop()`) 492 if ( node === this._last ) { 493 return this.pop(); 494 } 495 // Retrieve the node value: 496 value = node.value; 497 498 // Unfortunately, we need to check whether we have been provided a node belonging to our list by walking the list. If we don't, we could erroneously decrement the list length. This means our runtime goes from the theoretical O(1) to O(N). 499 n = this._first; 500 while ( n !== this._last && n !== node ) { 501 n = n._next; 502 } 503 // Check if we iterated through the entire list: 504 if ( n === this._last ) { 505 throw new Error( 'invalid argument. The list does not contain the provided list node.' ); 506 } 507 // Update pointers: 508 node._prev._next = node._next; 509 node._next._prev = node._prev; 510 511 // Decrement the list length: 512 this._length -= 1; 513 514 return value; 515 516 /* eslint-enable no-underscore-dangle */ 517 }); 518 519 /** 520 * Removes a value from the beginning of the list. 521 * 522 * @name shift 523 * @memberof LinkedList.prototype 524 * @type {Function} 525 * @returns {(*|void)} removed value 526 * 527 * @example 528 * var list = new LinkedList(); 529 * 530 * // Add values to the list: 531 * list.push( 'foo' ).push( 'bar' ); 532 * 533 * // Remove the first value: 534 * var v = list.shift(); 535 * // returns 'foo' 536 * 537 * // Add a new value to the list: 538 * list.push( 'beep' ); 539 * 540 * // Remove the first value: 541 * v = list.shift(); 542 * // returns 'bar' 543 */ 544 setReadOnly( LinkedList.prototype, 'shift', function shift() { 545 /* eslint-disable no-underscore-dangle */ 546 var value; 547 if ( this._length ) { 548 // Retrieve the first value: 549 value = this._first.value; 550 551 // Check whether we have a new "head" or whether we have emptied the list... 552 if ( this._first._next ) { 553 this._first = this._first._next; 554 this._first._prev = null; 555 } else { 556 // List is empty: 557 this._first = null; 558 this._last = null; 559 } 560 // Decrement the list length: 561 this._length -= 1; 562 } 563 return value; 564 565 /* eslint-enable no-underscore-dangle */ 566 }); 567 568 /** 569 * Returns an array of list values. 570 * 571 * @name toArray 572 * @memberof LinkedList.prototype 573 * @type {Function} 574 * @returns {Array} list values 575 * 576 * @example 577 * var list = new LinkedList(); 578 * 579 * // Add values to the list: 580 * list.push( 'foo' ).push( 'bar' ); 581 * 582 * // Get an array of list values: 583 * var vals = list.toArray(); 584 * // returns [ 'foo', 'bar' ] 585 */ 586 setReadOnly( LinkedList.prototype, 'toArray', function toArray() { 587 var node; 588 var out; 589 var i; 590 591 out = []; 592 node = this._first; 593 for ( i = 0; i < this._length; i++ ) { 594 out.push( node.value ); 595 node = node.next; 596 } 597 return out; 598 }); 599 600 /** 601 * Serializes a list as JSON. 602 * 603 * ## Notes 604 * 605 * - `JSON.stringify()` implicitly calls this method when stringifying a `LinkedList` instance. 606 * 607 * @name toJSON 608 * @memberof LinkedList.prototype 609 * @type {Function} 610 * @returns {Object} serialized list 611 * 612 * @example 613 * var list = new LinkedList(); 614 * 615 * // Add values to the list: 616 * list.push( 'foo' ).push( 'bar' ); 617 * 618 * // Serialize to JSON: 619 * var o = list.toJSON(); 620 * // returns { 'type': 'linked-list', 'data': [ 'foo', 'bar' ] } 621 */ 622 setReadOnly( LinkedList.prototype, 'toJSON', function toJSON() { 623 var out = {}; 624 out.type = 'linked-list'; 625 out.data = this.toArray(); 626 return out; 627 }); 628 629 /** 630 * Adds a value to the beginning of the list. 631 * 632 * @name unshift 633 * @memberof LinkedList.prototype 634 * @type {Function} 635 * @returns {LinkedList} list instance 636 * 637 * @example 638 * var list = new LinkedList(); 639 * 640 * // Add values to the beginning of the list: 641 * list.unshift( 'foo' ).unshift( 'bar' ); 642 * 643 * // Remove the last value: 644 * var v = list.pop(); 645 * // returns 'foo' 646 * 647 * // Add a new value to the beginning of the list: 648 * list.unshift( 'beep' ); 649 * 650 * // Remove the last value: 651 * v = list.pop(); 652 * // returns 'bar' 653 */ 654 setReadOnly( LinkedList.prototype, 'unshift', function unshift( value ) { 655 var node; 656 657 // Create a new list node: 658 node = new Node( value ); 659 660 // Check whether the list is currently empty... 661 if ( this._length === 0 ) { 662 // This is the only list node, making it both the first and last node: 663 this._first = node; 664 this._last = node; 665 } else { 666 // Link the new node to the previous first node: 667 node._next = this._first; // eslint-disable-line no-underscore-dangle 668 669 // Link the previous first node to the new node: 670 this._first._prev = node; // eslint-disable-line no-underscore-dangle 671 672 // Update the pointer for the first node: 673 this._first = node; 674 } 675 // Increment the list length: 676 this._length += 1; 677 678 return this; 679 }); 680 681 682 // EXPORTS // 683 684 module.exports = LinkedList;