time-to-botec

Benchmark sampling in different programming languages
Log | Files | Refs | README

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;