@ohos-ports/doublylinked 2.5.6-beta.1

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.
@@ -0,0 +1,746 @@
1
+ /* doublylinked
2
+ ------------------------
3
+ (c) 2017-present Panates
4
+ SQB may be freely distributed under the MIT license.
5
+ For details and documentation:
6
+ https://panates.github.io/doublylinked/
7
+ */
8
+ 'use strict';
9
+
10
+ /**
11
+ *
12
+ * @class
13
+ */
14
+ class DoublyLinked {
15
+ /**
16
+ * @param {*} element... - The elements to add to the end of the list
17
+ * @constructor
18
+ */
19
+ constructor(...element) {
20
+ this._cursor = undefined;
21
+ this._head = undefined;
22
+ this._tail = undefined;
23
+ this._length = 0;
24
+ this._eof = undefined;
25
+ if (element.length) {
26
+ this.push.apply(this, element);
27
+ }
28
+ }
29
+
30
+ /**
31
+ *
32
+ * @returns {Node}
33
+ */
34
+ get cursor() {
35
+ return this._cursor;
36
+ }
37
+
38
+ /**
39
+ *
40
+ * @returns {Node}
41
+ */
42
+ get head() {
43
+ return this._head;
44
+ }
45
+
46
+ /**
47
+ *
48
+ * @returns {int}
49
+ */
50
+ get length() {
51
+ return this._length;
52
+ }
53
+
54
+ /**
55
+ *
56
+ * @returns {Node}
57
+ */
58
+ get tail() {
59
+ return this._tail;
60
+ }
61
+
62
+ /**
63
+ * Merges cursor list with and given lists/values into new list
64
+ *
65
+ * @param {String} element... - Lists and/or values to concatenate into a new list
66
+ * @return {DoublyLinked} - A new DoublyLinked instance
67
+ * @public
68
+ */
69
+ concat(...element) {
70
+ const result = new DoublyLinked();
71
+ const mergeFn = (acc, node) => {
72
+ acc.push(node);
73
+ return acc;
74
+ };
75
+ this.reduce(mergeFn, result);
76
+ for (const arg of element) {
77
+ if (arg instanceof DoublyLinked) {
78
+ arg.reduce(mergeFn, result);
79
+ } else result.push(arg);
80
+ }
81
+
82
+ return result.reset();
83
+ }
84
+
85
+ /**
86
+ * Returns the iterator object contains entries
87
+ *
88
+ * @return {Iterator}
89
+ */
90
+ entries() {
91
+ const list = this;
92
+ return {
93
+ [Symbol.iterator]() {
94
+ let _cursor;
95
+ let i = 0;
96
+ return {
97
+ next: () => {
98
+ _cursor = _cursor ? _cursor.next : list.head;
99
+ return {
100
+ value: _cursor && [i++, _cursor.value],
101
+ done: !_cursor,
102
+ };
103
+ },
104
+ };
105
+ },
106
+ };
107
+ }
108
+
109
+ /**
110
+ * Returns the iterator object contains keys
111
+ *
112
+ * @return {Iterator}
113
+ */
114
+ keys() {
115
+ const list = this;
116
+ return {
117
+ [Symbol.iterator]() {
118
+ let _cursor;
119
+ let i = 0;
120
+ return {
121
+ next: () => {
122
+ _cursor = _cursor ? _cursor.next : list.head;
123
+ return {
124
+ value: _cursor && i++,
125
+ done: !_cursor,
126
+ };
127
+ },
128
+ };
129
+ },
130
+ };
131
+ }
132
+
133
+ /**
134
+ * Returns the iterator object contains values
135
+ *
136
+ * @return {function}
137
+ */
138
+ values() {
139
+ const list = this;
140
+ return {
141
+ [Symbol.iterator]() {
142
+ let _cursor;
143
+ return {
144
+ next: () => {
145
+ _cursor = _cursor ? _cursor.next : list.head;
146
+ return {
147
+ value: _cursor && _cursor.value,
148
+ done: !_cursor,
149
+ };
150
+ },
151
+ };
152
+ },
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Tests whether all elements in the list pass the test implemented by
158
+ * the provided function (from left to right)
159
+ *
160
+ * @param {Function} callback - Function to test for each element
161
+ * @param {*} [thisArg] - Value to use as this when executing callback
162
+ * @return {Boolean} - true if the callback function returns a truthy value for every list element; otherwise, false
163
+ * @public
164
+ */
165
+ every(callback, thisArg) {
166
+ if (typeof callback !== 'function') {
167
+ throw new TypeError('You must provide a function as first argument');
168
+ }
169
+ if (!(this._length && callback)) {
170
+ return true;
171
+ }
172
+ thisArg = thisArg !== undefined ? thisArg : this;
173
+ let tmp = this._head;
174
+ let nxt;
175
+ let i = 0;
176
+ while (tmp) {
177
+ nxt = tmp.next;
178
+ if (!callback.call(thisArg, tmp.value, i++, thisArg)) {
179
+ return false;
180
+ }
181
+ tmp = nxt;
182
+ }
183
+ return true;
184
+ }
185
+
186
+ /**
187
+ * Tests whether all elements in the list pass the test implemented by
188
+ * the provided function (from right to left)
189
+ *
190
+ * @param {Function} callback - Function to test for each element
191
+ * @param {*} [thisArg] - Value to use as this when executing callback
192
+ * @return {Boolean} - true if the callback function returns a truthy value for every list element; otherwise, false
193
+ * @public
194
+ */
195
+ everyRight(callback, thisArg) {
196
+ if (typeof callback !== 'function') {
197
+ throw new TypeError('You must provide a function as first argument');
198
+ }
199
+ if (!(this._length && callback)) {
200
+ return true;
201
+ }
202
+ thisArg = thisArg !== undefined ? thisArg : this;
203
+ let tmp = this.tail;
204
+ for (let i = 0; i < this._length; i++) {
205
+ if (!callback.call(thisArg, tmp.value, this._length - i - 1, thisArg)) {
206
+ return false;
207
+ }
208
+ tmp = tmp.prev;
209
+ }
210
+ return true;
211
+ }
212
+
213
+ /**
214
+ * Creates a new list with all elements that pass the test implemented
215
+ * by the provided function
216
+ *
217
+ * @param {Function} callback - Function to test for each element
218
+ * @param {*} [thisArg] - Value to use as this when executing callback
219
+ * @return {DoublyLinked} - A new list with the elements that pass the test
220
+ * @public
221
+ */
222
+ filter(callback, thisArg) {
223
+ if (typeof callback !== 'function') {
224
+ throw new TypeError('You must provide a function as first argument');
225
+ }
226
+ thisArg = thisArg !== undefined ? thisArg : this;
227
+ let index = 0;
228
+ return this.reduce((acc, value) => {
229
+ if (callback.call(thisArg, value, index++, thisArg)) {
230
+ acc.push(value);
231
+ }
232
+ return acc;
233
+ }, new DoublyLinked());
234
+ }
235
+
236
+ /**
237
+ * Returns the value of the first element in the list that satisfies
238
+ * the provided testing function. Otherwise undefined is returned
239
+ *
240
+ * @param {Function} callback - Function to test for each element
241
+ * @param {*} [thisArg] - Value to use as this when executing callback
242
+ * @return {*} - A value in the list if an element passes the test; otherwise, undefined
243
+ * @public
244
+ */
245
+ find(callback, thisArg) {
246
+ if (typeof callback !== 'function') {
247
+ throw new TypeError('You must provide a function as first argument');
248
+ }
249
+ if (!this._length) {
250
+ return;
251
+ }
252
+ thisArg = thisArg !== undefined ? thisArg : this;
253
+ let tmp = this.head;
254
+ for (let i = 0; i < this.length; i++) {
255
+ if (callback.call(thisArg, tmp.value, i, thisArg)) {
256
+ this._cursor = tmp;
257
+ this._eof = false;
258
+ return tmp.value;
259
+ }
260
+ tmp = tmp.next;
261
+ }
262
+ this._cursor = undefined;
263
+ }
264
+
265
+ /**
266
+ * Executes a provided function once for each list element (from left to right)
267
+ *
268
+ * @param {Function} callback - Function to execute for each element
269
+ * @param {*} [thisArg] - Value to use as this when executing callback
270
+ * @public
271
+ */
272
+ forEach(callback, thisArg) {
273
+ this.every((element, index, instance) => {
274
+ callback.call(this, element, index, instance);
275
+ return true;
276
+ }, thisArg);
277
+ }
278
+
279
+ /**
280
+ * Executes a provided function once for each list element (from right-to-left)
281
+ *
282
+ * @param {Function} callback - Function to execute for each element
283
+ * @param {*} [thisArg] - Value to use as this when executing callback
284
+ * @public
285
+ */
286
+ forEachRight(callback, thisArg) {
287
+ this.everyRight((element, index, instance) => {
288
+ callback.call(this, element, index, instance);
289
+ return true;
290
+ }, thisArg);
291
+ }
292
+
293
+ /**
294
+ * Determines whether an list includes a certain element,
295
+ * returning true or false as appropriate
296
+ *
297
+ * @param {*} searchElement - The element to search for
298
+ * @param {int} [fromIndex = 0] - The position in this list at which to begin searching for searchElement
299
+ * @return {Boolean} - true if the searchElement found in the list; otherwise, false
300
+ * @public
301
+ */
302
+ includes(searchElement, fromIndex) {
303
+ const sameValueZero = (x, y) =>
304
+ x === y ||
305
+ (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
306
+
307
+ fromIndex = fromIndex || 0;
308
+ if (fromIndex < 0) {
309
+ fromIndex = this.length + fromIndex;
310
+ }
311
+ this.find(
312
+ (element, index) =>
313
+ index >= fromIndex && sameValueZero(element, searchElement),
314
+ );
315
+ return !!this.cursor;
316
+ }
317
+
318
+ /**
319
+ * Adds one or more elements right after the cursor node of the list and returns
320
+ * the new length of the list
321
+ *
322
+ * @param {*} element... - The elements to add after cursor node
323
+ * @returns {int} - The new length of the list
324
+ * @public
325
+ */
326
+ insert(...element) {
327
+ for (const arg of element) {
328
+ const node = new Node(this, arg);
329
+ if (this._length) {
330
+ this._cursor.next = node;
331
+ node.prev = this._cursor;
332
+ this._cursor = node;
333
+ } else {
334
+ this._head = node;
335
+ this._tail = node;
336
+ this._cursor = node;
337
+ }
338
+ this._length++;
339
+ this._eof = false;
340
+ }
341
+ return this._length;
342
+ }
343
+
344
+ /**
345
+ * Joins all elements of the list into a string and returns this string
346
+ *
347
+ * @param {String} [separator=','] - Specifies a string to separate each pair of adjacent elements of the list
348
+ * @return {String} - A string with all list elements joined. If length is 0, the empty string is returned
349
+ * @public
350
+ */
351
+ join(separator) {
352
+ separator = separator || ',';
353
+ let out = '';
354
+ this.forEach(value => {
355
+ out += (out ? separator : '') + value;
356
+ });
357
+ return out;
358
+ }
359
+
360
+ /**
361
+ * Creates a new list with the results of calling a provided function on
362
+ * every element in the calling list
363
+ *
364
+ * @param {Function} callback - Function that produces an element of the new list
365
+ * @return {DoublyLinked} - A new list with each element being the result of the callback function
366
+ * @public
367
+ */
368
+ map(callback) {
369
+ if (typeof callback !== 'function') {
370
+ throw new TypeError('You must provide a function as first argument');
371
+ }
372
+ const out = new DoublyLinked();
373
+ this.forEach((value, index, instance) =>
374
+ out.push(callback(value, index, instance)),
375
+ );
376
+ return out.reset();
377
+ }
378
+
379
+ /**
380
+ * Moves cursor to the next and returns its value
381
+ *
382
+ * @return {*} - Returns value of next node to the cursor. If cursor reaches to the end it returns undefined
383
+ * @public
384
+ */
385
+ next() {
386
+ if (this._cursor === this._tail) {
387
+ this._eof = true;
388
+ return undefined;
389
+ }
390
+ const c = this._cursor ? this._cursor.next : this._head;
391
+ this._cursor = c;
392
+ return c && c.value;
393
+ }
394
+
395
+ /**
396
+ * Moves cursor to the previous and returns its value
397
+ *
398
+ * @return {*} - Returns value of previous node to the cursor. If cursor reaches to the head it returns undefined
399
+ * @public
400
+ */
401
+ prev() {
402
+ let c;
403
+ if (this._eof) {
404
+ this._eof = false;
405
+ c = this._cursor = this._tail;
406
+ return c && c.value;
407
+ }
408
+ c = this._cursor && this._cursor.prev;
409
+ this._cursor = c;
410
+ return c && c.value;
411
+ }
412
+
413
+ /**
414
+ * Removes the last element from the list and returns that element
415
+ *
416
+ * @returns {*} - The removed element from the list; undefined if the list is empty.
417
+ * @public
418
+ */
419
+ pop() {
420
+ const ret = this._tail;
421
+ if (ret) {
422
+ ret.remove();
423
+ return ret.value;
424
+ }
425
+ }
426
+
427
+ /**
428
+ * Adds one or more elements to the end of the list and returns
429
+ * the new length of the list
430
+ *
431
+ * @param {*} element... - The elements to add to the end of the list
432
+ * @returns {int} - The new length of the list
433
+ * @public
434
+ */
435
+ push(...element) {
436
+ if (element.length) {
437
+ this._eof = false;
438
+ }
439
+ for (const arg of element) {
440
+ const node = new Node(this, arg);
441
+ if (this._length) {
442
+ this._tail.next = node;
443
+ node.prev = this._tail;
444
+ this._tail = node;
445
+ } else {
446
+ this._head = node;
447
+ this._tail = node;
448
+ }
449
+ this._length++;
450
+ }
451
+ return this._length;
452
+ }
453
+
454
+ /**
455
+ * Applies a function against an accumulator and each element in
456
+ * the list (from left-to-right) to reduce it to a single value
457
+ *
458
+ * @param {Function} callback - Function to execute on each element in the list
459
+ * @param {*} [initialValue] - Value to use as the first argument to the first call of the callback
460
+ * @return {*} - The value that results from the reduction
461
+ * @public
462
+ */
463
+ reduce(callback, initialValue) {
464
+ if (typeof callback !== 'function') {
465
+ throw new TypeError('You must provide a function as first argument');
466
+ }
467
+ let accumulator;
468
+ let tmp = this._head;
469
+ let i = 0;
470
+ if (arguments.length >= 2) {
471
+ accumulator = initialValue;
472
+ } else {
473
+ if (!this._length) {
474
+ throw new TypeError('Reduce of empty list with no initial value');
475
+ }
476
+ accumulator = tmp.value;
477
+ tmp = tmp.next;
478
+ i = 1;
479
+ }
480
+ while (tmp) {
481
+ accumulator = callback(accumulator, tmp.value, i++, this);
482
+ tmp = tmp.next;
483
+ }
484
+ return accumulator;
485
+ }
486
+
487
+ /**
488
+ * Applies a function against an accumulator and each element in
489
+ * the list (from right-to-left) to reduce it to a single value
490
+ *
491
+ * @param {Function} callback - Function to execute on each element in the list
492
+ * @param {*} [initialValue] - Value to use as the first argument to the first call of the callback
493
+ * @return {*} - The value that results from the reduction
494
+ * @public
495
+ */
496
+ reduceRight(callback, initialValue) {
497
+ if (typeof callback !== 'function') {
498
+ throw new TypeError('You must provide a function as first argument');
499
+ }
500
+ let accumulator;
501
+ let tmp = this._tail;
502
+ let i = this._length - 1;
503
+ if (arguments.length >= 2) {
504
+ accumulator = initialValue;
505
+ } else {
506
+ if (!this._length) {
507
+ throw new TypeError('Reduce of empty list with no initial value');
508
+ }
509
+ accumulator = tmp.value;
510
+ tmp = tmp.prev;
511
+ i--;
512
+ }
513
+ while (tmp) {
514
+ accumulator = callback(accumulator, tmp.value, i--, this);
515
+ tmp = tmp.prev;
516
+ }
517
+ return accumulator;
518
+ }
519
+
520
+ /**
521
+ * Removes an element from the list
522
+ *
523
+ * @param {*} element - The element to be removed
524
+ * @param {int} [fromIndex = 0] - The position in this list at which to begin searching for element
525
+ * @return {*} - Returns removed element if found, undefined otherwise
526
+ * @public
527
+ */
528
+ remove(element, fromIndex) {
529
+ if (this.includes(element, fromIndex)) {
530
+ const cur = this._cursor;
531
+ cur.remove();
532
+ return cur.value;
533
+ }
534
+ }
535
+
536
+ /**
537
+ * Resets cursor to head
538
+ *
539
+ * @return {DoublyLinked} - Returns the DoublyLinked instance which this method is called
540
+ * @public
541
+ */
542
+ reset() {
543
+ this._cursor = undefined;
544
+ this._eof = false;
545
+ return this;
546
+ }
547
+
548
+ /**
549
+ * Reverses a list in place. The first array element becomes the last, and the last list element becomes the first.
550
+ *
551
+ * @return {DoublyLinked} - Returns the DoublyLinked instance which this method is called
552
+ * @public
553
+ */
554
+ reverse() {
555
+ let cur = this._head;
556
+ let p;
557
+ let n;
558
+ for (let i = 0; i < this._length; i++) {
559
+ p = cur.prev;
560
+ n = cur.next;
561
+ cur.prev = n;
562
+ cur.next = p;
563
+ cur = n;
564
+ }
565
+ p = this._head;
566
+ n = this._tail;
567
+ this._head = n;
568
+ this._tail = p;
569
+ this.reset();
570
+ return this;
571
+ }
572
+
573
+ /**
574
+ * Removes the first element from the list and returns that element
575
+ *
576
+ * @returns {*} - The removed element from the list; undefined if the list is empty
577
+ * @public
578
+ */
579
+ shift() {
580
+ const ret = this._head;
581
+ if (ret) {
582
+ ret.remove();
583
+ return ret.value;
584
+ }
585
+ }
586
+
587
+ /**
588
+ * Returns a shallow copy of a portion of an array into a new array object
589
+ * selected from start to end (end not included) where start and
590
+ * end represent the index of items in that array.
591
+ *
592
+ * @param {number} [start]
593
+ * @param {number} [end]
594
+ * @returns {Array}
595
+ * @public
596
+ */
597
+ slice(start, end) {
598
+ start = start || 0;
599
+ const acc = [];
600
+ this.every((value, index) => {
601
+ if (index >= start) {
602
+ acc.push(value);
603
+ }
604
+ return !end || index < end;
605
+ });
606
+ return acc;
607
+ }
608
+
609
+ /**
610
+ * Tests whether all elements in the list pass the test implemented by
611
+ * the provided function (from left to right)
612
+ *
613
+ * @param {Function} callback - Function to test for each element
614
+ * @param {*} [thisArg] - Value to use as this when executing callback
615
+ * @public
616
+ */
617
+ some(callback, thisArg) {
618
+ return !this.every(
619
+ (element, index, instance) =>
620
+ !callback.call(this, element, index, instance),
621
+ thisArg,
622
+ );
623
+ }
624
+
625
+ /**
626
+ * Tests whether at least one element in the list passes the test
627
+ * implemented by the provided function (from right to left)
628
+ *
629
+ * @param {Function} callback - Function to test for each element
630
+ * @param {*} [thisArg] - Value to use as this when executing callback
631
+ * @public
632
+ */
633
+ someRight(callback, thisArg) {
634
+ return !this.everyRight(
635
+ (element, index, instance) =>
636
+ !callback.call(this, element, index, instance),
637
+ thisArg,
638
+ );
639
+ }
640
+
641
+ /**
642
+ * Returns a new array containing elements of the list
643
+ *
644
+ * @return {Array} - A new Array instance contains elements of the list
645
+ * @public
646
+ */
647
+ toArray() {
648
+ return this.slice();
649
+ }
650
+
651
+ /**
652
+ * Returns a string representing the specified list and its elements.
653
+ * @return {string} - Returns a string representing the specified list and its elements.
654
+ */
655
+ toString() {
656
+ return 'DoublyLinked(' + this.join() + ')';
657
+ }
658
+
659
+ /**
660
+ * Adds one or more elements to the beginning of the list
661
+ * the new length of the list
662
+ *
663
+ * @param {*} element... - The elements to add to the front of the list
664
+ * @returns {int} - The new length of the list
665
+ * @public
666
+ */
667
+ unshift(...element) {
668
+ for (const arg of element) {
669
+ const node = new Node(this, arg);
670
+ if (this._length) {
671
+ this._head.prev = node;
672
+ node.next = this._head;
673
+ this._head = node;
674
+ } else {
675
+ this._head = node;
676
+ this._tail = node;
677
+ }
678
+ this._length++;
679
+ }
680
+ return this._length;
681
+ }
682
+
683
+ /**
684
+ * Returns the iterator object contains entries
685
+ *
686
+ * @return {Object} - Returns the iterator object contains entries
687
+ */
688
+ [Symbol.iterator]() {
689
+ let _cursor;
690
+ return {
691
+ next: () => {
692
+ _cursor = _cursor ? _cursor.next : this.head;
693
+ return {
694
+ value: _cursor && _cursor.value,
695
+ done: !_cursor,
696
+ };
697
+ },
698
+ };
699
+ }
700
+ }
701
+
702
+ /**
703
+ *
704
+ * @constructor
705
+ */
706
+ class Node {
707
+ constructor(list, value) {
708
+ this.list = list;
709
+ this.value = value;
710
+ this.prev = undefined;
711
+ this.next = undefined;
712
+ }
713
+
714
+ remove() {
715
+ if (!this.list) {
716
+ return;
717
+ }
718
+ if (this.prev) {
719
+ // noinspection JSUnresolvedVariable
720
+ this.prev.next = this.next;
721
+ }
722
+ if (this.next) {
723
+ // noinspection JSUnresolvedVariable
724
+ this.next.prev = this.prev;
725
+ }
726
+ if (this === this.list._cursor) {
727
+ this.list._cursor = this.next || this.prev;
728
+ }
729
+ if (this === this.list._head) {
730
+ this.list._head = this.next;
731
+ }
732
+ if (this === this.list._tail) {
733
+ this.list._tail = this.prev;
734
+ }
735
+ this.list._length--;
736
+ this.prev = undefined;
737
+ this.next = undefined;
738
+ this.list = undefined;
739
+ }
740
+ }
741
+
742
+ /**
743
+ * Expose `DoublyLinked`.
744
+ */
745
+
746
+ module.exports = DoublyLinked;