main.js (16534B)
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 * Doubly linked list constructor. 35 * 36 * @constructor 37 * @returns {DoublyLinkedList} linked list instance 38 * 39 * @example 40 * var list = new DoublyLinkedList(); 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 DoublyLinkedList() { 57 if ( !(this instanceof DoublyLinkedList) ) { 58 return new DoublyLinkedList(); 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 DoublyLinkedList.prototype 71 * @type {Function} 72 * @returns {DoublyLinkedList} list instance 73 * 74 * @example 75 * var list = new DoublyLinkedList(); 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( DoublyLinkedList.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 DoublyLinkedList.prototype 111 * @type {Function} 112 * @returns {(Node|void)} list node 113 * 114 * @example 115 * var list = new DoublyLinkedList(); 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( DoublyLinkedList.prototype, 'first', function first() { 125 if ( this._length ) { 126 return this._first; 127 } 128 }); 129 130 /** 131 * Inserts a value into the list either before or after a provided list node. 132 * 133 * @name insert 134 * @memberof DoublyLinkedList.prototype 135 * @type {Function} 136 * @param {Node} node - node after which to insert the value 137 * @param {*} value - value to insert 138 * @param {string} [location='after'] - location 139 * @throws {Error} must provide a node belonging to the list 140 * @throws {Error} must provide a recognized location 141 * @returns {DoublyLinkedList} list instance 142 * 143 * @example 144 * var list = new DoublyLinkedList(); 145 * 146 * // Add values to the list: 147 * list.push( 'foo' ).push( 'bar' ).push( 'beep' ); 148 * 149 * // Determine the list length: 150 * var len = list.length; 151 * // returns 3 152 * 153 * // Get the second node: 154 * var node = list.first().next; 155 * 156 * // Insert a value after the second node: 157 * list.insert( node, 'boop' ); 158 * 159 * // Determine the list length: 160 * len = list.length; 161 * // returns 4 162 */ 163 setReadOnly( DoublyLinkedList.prototype, 'insert', function insert( node, value, location ) { 164 /* eslint-disable no-underscore-dangle */ 165 var loc; 166 var n; 167 if ( arguments.length > 2 ) { 168 loc = location; 169 if ( loc !== 'before' && loc !== 'after' ) { 170 throw new Error( 'invalid argument. Third argument must be a recognized location. Value: `' + loc + '`.' ); 171 } 172 } else { 173 loc = 'after'; 174 } 175 // Case: insert after last node (equivalent to `push()`) 176 if ( loc === 'after' && node === this._last ) { 177 return this.push( value ); 178 } 179 // Case: insert before first node (equivalent to `unshift()`) 180 if ( loc === 'before' && node === this._first ) { 181 return this.unshift( value ); 182 } 183 // 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). 184 n = this._first; 185 while ( n !== this._last && n !== node ) { 186 n = n._next; 187 } 188 // Check if we iterated through the entire list: 189 if ( n === this._last && n !== node ) { 190 throw new Error( 'invalid argument. The list does not contain the provided list node.' ); 191 } 192 // Create a new list node: 193 n = new Node( value ); 194 195 // Update pointers... 196 if ( loc === 'after' ) { 197 node._next._prev = n; 198 n._next = node._next; 199 200 node._next = n; 201 n._prev = node; 202 } else { 203 node._prev._next = n; 204 n._prev = node._prev; 205 206 node._prev = n; 207 n._next = node; 208 } 209 // Increment the list length: 210 this._length += 1; 211 212 return this; 213 214 /* eslint-enable no-underscore-dangle */ 215 }); 216 217 /** 218 * Returns an iterator for iterating over a list. 219 * 220 * ## Notes 221 * 222 * - 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. 223 * 224 * @name iterator 225 * @memberof DoublyLinkedList.prototype 226 * @type {Function} 227 * @param {string} [direction="forward"] - iteration direction 228 * @throws {Error} must provide a recognized iteration direction 229 * @returns {Iterator} iterator 230 * 231 * @example 232 * var list = new DoublyLinkedList(); 233 * 234 * // Add values to the list: 235 * list.push( 'foo' ).push( 'bar' ); 236 * 237 * // Create an iterator: 238 * var it = list.iterator(); 239 * 240 * // Iterate over the list... 241 * var v = it.next().value; 242 * // returns 'foo' 243 * 244 * v = it.next().value; 245 * // returns 'bar' 246 * 247 * var bool = it.next().done; 248 * // returns true 249 */ 250 setReadOnly( DoublyLinkedList.prototype, 'iterator', function iterator( direction ) { 251 var values; 252 var iter; 253 var self; 254 var FLG; 255 var dir; 256 var inc; 257 var i; 258 if ( arguments.length ) { 259 dir = direction; 260 if ( dir !== 'forward' && dir !== 'reverse' ) { 261 throw new Error( 'invalid argument. Must provide a recognized iteration direction. Value: `' + dir + '`.' ); 262 } 263 } else { 264 dir = 'forward'; 265 } 266 self = this; 267 268 // Initialize the iteration index: 269 if ( dir === 'forward' ) { 270 i = -1; 271 inc = 1; 272 } else { 273 i = this._length; 274 inc = -1; 275 } 276 // Create a copy of list values (necessary in order to "snapshot" the list; otherwise, values could come and go between calls to `next`): 277 values = this.toArray(); 278 279 // Create an iterator protocol-compliant object: 280 iter = {}; 281 setReadOnly( iter, 'next', next ); 282 setReadOnly( iter, 'return', end ); 283 if ( iteratorSymbol ) { 284 setReadOnly( iter, iteratorSymbol, factory ); 285 } 286 return iter; 287 288 /** 289 * Returns an iterator protocol-compliant object containing the next iterated value. 290 * 291 * @private 292 * @returns {Object} iterator protocol-compliant object 293 */ 294 function next() { 295 i += inc; 296 if ( FLG || i < 0 || i >= values.length ) { 297 return { 298 'done': true 299 }; 300 } 301 return { 302 'value': values[ i ], 303 'done': false 304 }; 305 } 306 307 /** 308 * Finishes an iterator. 309 * 310 * @private 311 * @param {*} [value] - value to return 312 * @returns {Object} iterator protocol-compliant object 313 */ 314 function end( value ) { 315 FLG = true; 316 if ( arguments.length ) { 317 return { 318 'value': value, 319 'done': true 320 }; 321 } 322 return { 323 'done': true 324 }; 325 } 326 327 /** 328 * Returns a new iterator. 329 * 330 * @private 331 * @returns {Iterator} iterator 332 */ 333 function factory() { 334 return self.iterator(); 335 } 336 }); 337 338 /** 339 * Returns the last node. 340 * 341 * @name last 342 * @memberof DoublyLinkedList.prototype 343 * @type {Function} 344 * @returns {(Node|void)} list node 345 * 346 * @example 347 * var list = new DoublyLinkedList(); 348 * 349 * // Add values to the list: 350 * list.push( 'foo' ).push( 'bar' ); 351 * 352 * // Peek at the last value: 353 * var v = list.last().value; 354 * // returns 'bar' 355 */ 356 setReadOnly( DoublyLinkedList.prototype, 'last', function last() { 357 if ( this._length ) { 358 return this._last; 359 } 360 }); 361 362 /** 363 * List length. 364 * 365 * @name length 366 * @memberof DoublyLinkedList.prototype 367 * @type {NonNegativeInteger} 368 * 369 * @example 370 * var list = new DoublyLinkedList(); 371 * 372 * // Examine the initial list length: 373 * var len = list.length; 374 * // returns 0 375 * 376 * // Add values to the list: 377 * list.push( 'foo' ).push( 'bar' ); 378 * 379 * // Retrieve the current list length: 380 * len = list.length; 381 * // returns 2 382 */ 383 setReadOnlyAccessor( DoublyLinkedList.prototype, 'length', function get() { 384 return this._length; 385 }); 386 387 /** 388 * Removes a value from the end of the list. 389 * 390 * @name pop 391 * @memberof DoublyLinkedList.prototype 392 * @type {Function} 393 * @returns {(*|void)} removed value 394 * 395 * @example 396 * var list = new DoublyLinkedList(); 397 * 398 * // Add values to the list: 399 * list.push( 'foo' ).push( 'bar' ); 400 * 401 * // Remove the last value: 402 * var v = list.pop(); 403 * // returns 'bar' 404 * 405 * // Add a new value to the list: 406 * list.push( 'beep' ); 407 * 408 * // Remove the last value: 409 * v = list.pop(); 410 * // returns 'beep' 411 */ 412 setReadOnly( DoublyLinkedList.prototype, 'pop', function pop() { 413 /* eslint-disable no-underscore-dangle */ 414 var value; 415 if ( this._length ) { 416 // Retrieve the last value: 417 value = this._last.value; 418 419 // Check whether we have a new "tail" or whether we have emptied the list... 420 if ( this._last._prev ) { 421 this._last = this._last._prev; 422 this._last._next = null; 423 } else { 424 // List is empty: 425 this._first = null; 426 this._last = null; 427 } 428 // Decrement the list length: 429 this._length -= 1; 430 } 431 return value; 432 433 /* eslint-enable no-underscore-dangle */ 434 }); 435 436 /** 437 * Adds a value to the end of the list. 438 * 439 * @name push 440 * @memberof DoublyLinkedList.prototype 441 * @type {Function} 442 * @returns {DoublyLinkedList} list instance 443 * 444 * @example 445 * var list = new DoublyLinkedList(); 446 * 447 * // Add values to the list: 448 * list.push( 'foo' ).push( 'bar' ); 449 * 450 * // Remove the last value: 451 * var v = list.pop(); 452 * // returns 'bar' 453 * 454 * // Add a new value to the list: 455 * list.push( 'beep' ); 456 * 457 * // Remove the last value: 458 * v = list.pop(); 459 * // returns 'beep' 460 */ 461 setReadOnly( DoublyLinkedList.prototype, 'push', function push( value ) { 462 var node; 463 464 // Create a new list node: 465 node = new Node( value ); 466 467 // Check whether the list is currently empty... 468 if ( this._length === 0 ) { 469 // This is the only list node, making it both the first and last node: 470 this._first = node; 471 this._last = node; 472 } else { 473 // Link the new node to the previous last node: 474 node._prev = this._last; // eslint-disable-line no-underscore-dangle 475 476 // Link the previous last node to the new node: 477 this._last._next = node; // eslint-disable-line no-underscore-dangle 478 479 // Update the pointer for the last node: 480 this._last = node; 481 } 482 // Increment the list length: 483 this._length += 1; 484 485 return this; 486 }); 487 488 /** 489 * Removes a list node from the list. 490 * 491 * @name remove 492 * @memberof DoublyLinkedList.prototype 493 * @type {Function} 494 * @param {Node} node - node to remove 495 * @throws {Error} must provide a node belonging to the list 496 * @returns {(*|void)} removed value 497 * 498 * @example 499 * var list = new DoublyLinkedList(); 500 * 501 * // Add values to the list: 502 * list.push( 'foo' ).push( 'bar' ).push( 'beep' ); 503 * 504 * // Determine the list length: 505 * var len = list.length; 506 * // returns 3 507 * 508 * // Get the second node: 509 * var node = list.first().next; 510 * 511 * // Remove the second node: 512 * var v = list.remove( node ); 513 * // returns 'bar' 514 * 515 * // Determine the list length: 516 * len = list.length; 517 * // returns 2 518 */ 519 setReadOnly( DoublyLinkedList.prototype, 'remove', function remove( node ) { 520 /* eslint-disable no-underscore-dangle */ 521 var value; 522 var n; 523 524 // Case: first node (equivalent to `shift()`) 525 if ( node === this._first ) { 526 return this.shift(); 527 } 528 // Case: last node (equivalent to `pop()`) 529 if ( node === this._last ) { 530 return this.pop(); 531 } 532 // Retrieve the node value: 533 value = node.value; 534 535 // 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). 536 n = this._first; 537 while ( n !== this._last && n !== node ) { 538 n = n._next; 539 } 540 // Check if we iterated through the entire list: 541 if ( n === this._last ) { 542 throw new Error( 'invalid argument. The list does not contain the provided list node.' ); 543 } 544 // Update pointers: 545 node._prev._next = node._next; 546 node._next._prev = node._prev; 547 548 // Decrement the list length: 549 this._length -= 1; 550 551 return value; 552 553 /* eslint-enable no-underscore-dangle */ 554 }); 555 556 /** 557 * Removes a value from the beginning of the list. 558 * 559 * @name shift 560 * @memberof DoublyLinkedList.prototype 561 * @type {Function} 562 * @returns {(*|void)} removed value 563 * 564 * @example 565 * var list = new DoublyLinkedList(); 566 * 567 * // Add values to the list: 568 * list.push( 'foo' ).push( 'bar' ); 569 * 570 * // Remove the first value: 571 * var v = list.shift(); 572 * // returns 'foo' 573 * 574 * // Add a new value to the list: 575 * list.push( 'beep' ); 576 * 577 * // Remove the first value: 578 * v = list.shift(); 579 * // returns 'bar' 580 */ 581 setReadOnly( DoublyLinkedList.prototype, 'shift', function shift() { 582 /* eslint-disable no-underscore-dangle */ 583 var value; 584 if ( this._length ) { 585 // Retrieve the first value: 586 value = this._first.value; 587 588 // Check whether we have a new "head" or whether we have emptied the list... 589 if ( this._first._next ) { 590 this._first = this._first._next; 591 this._first._prev = null; 592 } else { 593 // List is empty: 594 this._first = null; 595 this._last = null; 596 } 597 // Decrement the list length: 598 this._length -= 1; 599 } 600 return value; 601 602 /* eslint-enable no-underscore-dangle */ 603 }); 604 605 /** 606 * Returns an array of list values. 607 * 608 * @name toArray 609 * @memberof DoublyLinkedList.prototype 610 * @type {Function} 611 * @returns {Array} list values 612 * 613 * @example 614 * var list = new DoublyLinkedList(); 615 * 616 * // Add values to the list: 617 * list.push( 'foo' ).push( 'bar' ); 618 * 619 * // Get an array of list values: 620 * var vals = list.toArray(); 621 * // returns [ 'foo', 'bar' ] 622 */ 623 setReadOnly( DoublyLinkedList.prototype, 'toArray', function toArray() { 624 var node; 625 var out; 626 var i; 627 628 out = []; 629 node = this._first; 630 for ( i = 0; i < this._length; i++ ) { 631 out.push( node.value ); 632 node = node.next; 633 } 634 return out; 635 }); 636 637 /** 638 * Serializes a list as JSON. 639 * 640 * ## Notes 641 * 642 * - `JSON.stringify()` implicitly calls this method when stringifying a `DoublyLinkedList` instance. 643 * 644 * @name toJSON 645 * @memberof DoublyLinkedList.prototype 646 * @type {Function} 647 * @returns {Object} serialized list 648 * 649 * @example 650 * var list = new DoublyLinkedList(); 651 * 652 * // Add values to the list: 653 * list.push( 'foo' ).push( 'bar' ); 654 * 655 * // Serialize to JSON: 656 * var o = list.toJSON(); 657 * // returns { 'type': 'doubly-linked-list', 'data': [ 'foo', 'bar' ] } 658 */ 659 setReadOnly( DoublyLinkedList.prototype, 'toJSON', function toJSON() { 660 var out = {}; 661 out.type = 'doubly-linked-list'; 662 out.data = this.toArray(); 663 return out; 664 }); 665 666 /** 667 * Adds a value to the beginning of the list. 668 * 669 * @name unshift 670 * @memberof DoublyLinkedList.prototype 671 * @type {Function} 672 * @returns {DoublyLinkedList} list instance 673 * 674 * @example 675 * var list = new DoublyLinkedList(); 676 * 677 * // Add values to the beginning of the list: 678 * list.unshift( 'foo' ).unshift( 'bar' ); 679 * 680 * // Remove the last value: 681 * var v = list.pop(); 682 * // returns 'foo' 683 * 684 * // Add a new value to the beginning of the list: 685 * list.unshift( 'beep' ); 686 * 687 * // Remove the last value: 688 * v = list.pop(); 689 * // returns 'bar' 690 */ 691 setReadOnly( DoublyLinkedList.prototype, 'unshift', function unshift( value ) { 692 var node; 693 694 // Create a new list node: 695 node = new Node( value ); 696 697 // Check whether the list is currently empty... 698 if ( this._length === 0 ) { 699 // This is the only list node, making it both the first and last node: 700 this._first = node; 701 this._last = node; 702 } else { 703 // Link the new node to the previous first node: 704 node._next = this._first; // eslint-disable-line no-underscore-dangle 705 706 // Link the previous first node to the new node: 707 this._first._prev = node; // eslint-disable-line no-underscore-dangle 708 709 // Update the pointer for the first node: 710 this._first = node; 711 } 712 // Increment the list length: 713 this._length += 1; 714 715 return this; 716 }); 717 718 719 // EXPORTS // 720 721 module.exports = DoublyLinkedList;