@stdlib/dstructs-doubly-linked-list 0.1.0

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