list-toolkit 1.0.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.
package/src/List.js ADDED
@@ -0,0 +1,303 @@
1
+ 'use strict';
2
+
3
+ class ListNode {
4
+ constructor() {
5
+ this.prev = this.next = this;
6
+ }
7
+ }
8
+
9
+ const pop = head => {
10
+ const rest = head.next;
11
+ head.prev.next = head.next;
12
+ head.next.prev = head.prev;
13
+ head.prev = head.next = head;
14
+ return {node: head, list: rest};
15
+ };
16
+
17
+ const extract = (from, to) => {
18
+ const prev = from.prev,
19
+ next = to.next;
20
+ prev.next = next;
21
+ next.prev = prev;
22
+ from.prev = to;
23
+ to.next = from;
24
+ return from;
25
+ };
26
+
27
+ const splice = (head1, head2) => {
28
+ const tail1 = head1.prev,
29
+ tail2 = head2.prev;
30
+ tail1.next = head2;
31
+ head2.prev = tail1;
32
+ tail2.next = head1;
33
+ head1.prev = tail2;
34
+ return head1;
35
+ };
36
+
37
+ class ListValueNode extends ListNode {
38
+ constructor(value) {
39
+ super();
40
+ this.value = value;
41
+ }
42
+
43
+ pop() {
44
+ return pop(this).node.value;
45
+ }
46
+
47
+ addBefore(value) {
48
+ splice(this, new ListValueNode(value));
49
+ return this;
50
+ }
51
+
52
+ addAfter(value) {
53
+ splice(this.next, new ListValueNode(value));
54
+ return this;
55
+ }
56
+
57
+ insertBefore(list) {
58
+ splice(this, pop(list).list);
59
+ return this;
60
+ }
61
+
62
+ insertAfter(list) {
63
+ splice(this.next, pop(list).list);
64
+ return this;
65
+ }
66
+ }
67
+
68
+ class List extends ListNode {
69
+ get isEmpty() {
70
+ return this.next === this;
71
+ }
72
+
73
+ get front() {
74
+ return this.next;
75
+ }
76
+
77
+ get back() {
78
+ return this.prev;
79
+ }
80
+
81
+ getLength() {
82
+ let n = 0;
83
+ for (let p = this.next; p !== this; ++n, p = p.next);
84
+ return n;
85
+ }
86
+
87
+ popFront() {
88
+ if (this.next !== this) {
89
+ return this.next.pop();
90
+ }
91
+ }
92
+
93
+ popBack() {
94
+ if (this.next !== this) {
95
+ return this.prev.pop();
96
+ }
97
+ }
98
+
99
+ pushFront(value) {
100
+ splice(this.next, new ListValueNode(value));
101
+ return this;
102
+ }
103
+
104
+ pushBack(value) {
105
+ splice(this, new ListValueNode(value));
106
+ return this;
107
+ }
108
+
109
+ appendFront(list) {
110
+ if (list.next !== list) {
111
+ splice(this.next, extract(list.next, list.prev));
112
+ }
113
+ return this;
114
+ }
115
+
116
+ appendBack(list) {
117
+ if (list.next !== list) {
118
+ splice(this, extract(list.next, list.prev));
119
+ }
120
+ return this;
121
+ }
122
+
123
+ moveToFront(node) {
124
+ if (this.next !== node) {
125
+ splice(this.next, extract(node, node));
126
+ }
127
+ return this;
128
+ }
129
+
130
+ moveToBack(node) {
131
+ if (this.prev !== node) {
132
+ splice(this, extract(node, node));
133
+ }
134
+ return this;
135
+ }
136
+
137
+ clear() {
138
+ this.prev = this.next = this;
139
+ return this;
140
+ }
141
+
142
+ remove(from, to = from) {
143
+ extract(from, to);
144
+ return this;
145
+ }
146
+
147
+ extract(from, to) {
148
+ return splice(new List(), extract(from, to));
149
+ }
150
+
151
+ reverse() {
152
+ let next = this.next;
153
+ this.next = this.prev;
154
+ this.prev = next;
155
+ while (next !== this) {
156
+ const node = next;
157
+ next = node.next;
158
+ node.next = node.prev;
159
+ node.prev = next;
160
+ }
161
+ return this;
162
+ }
163
+
164
+ sort(compareFn) {
165
+ let current = this.next;
166
+ for (const value of Array.from(this).sort(compareFn)) {
167
+ current.value = value;
168
+ current = current.next;
169
+ }
170
+ return this;
171
+ }
172
+
173
+ // iterators
174
+
175
+ [Symbol.iterator]() {
176
+ let current = this.next;
177
+ return {
178
+ next: () => {
179
+ if (current === this) return {done: true};
180
+ const value = current.value;
181
+ current = current.next;
182
+ return {value};
183
+ }
184
+ };
185
+ }
186
+
187
+ getIterable(from, to) {
188
+ return {
189
+ [Symbol.iterator]: () => {
190
+ let current = from || this.next;
191
+ const stop = to ? to.next : this;
192
+ return {
193
+ next: () => {
194
+ if (current === stop) return {done: true};
195
+ const value = current.value;
196
+ current = current.next;
197
+ return {value};
198
+ }
199
+ };
200
+ }
201
+ };
202
+ }
203
+
204
+ getNodeIterable(from, to) {
205
+ return {
206
+ [Symbol.iterator]: () => {
207
+ let current = from || this.next;
208
+ const stop = to ? to.next : this;
209
+ return {
210
+ next: () => {
211
+ if (current === stop) return {done: true};
212
+ const value = current;
213
+ current = current.next;
214
+ return {value};
215
+ }
216
+ };
217
+ }
218
+ };
219
+ }
220
+
221
+ getReverseIterable(from, to) {
222
+ return {
223
+ [Symbol.iterator]: () => {
224
+ let current = to || this.prev;
225
+ const stop = from ? from.prev : this;
226
+ return {
227
+ next: () => {
228
+ if (current === stop) return {done: true};
229
+ const value = current.value;
230
+ current = current.prev;
231
+ return {value};
232
+ }
233
+ };
234
+ }
235
+ };
236
+ }
237
+
238
+ getReverseNodeIterable(from, to) {
239
+ return {
240
+ [Symbol.iterator]: () => {
241
+ let current = to || this.prev;
242
+ const stop = from ? from.prev : this;
243
+ return {
244
+ next: () => {
245
+ if (current === stop) return {done: true};
246
+ const value = current;
247
+ current = current.prev;
248
+ return {value};
249
+ }
250
+ };
251
+ }
252
+ };
253
+ }
254
+
255
+ // helpers
256
+
257
+ makeFrom(values) {
258
+ return List.from(values);
259
+ }
260
+
261
+ pushValuesFront(values) {
262
+ for (const value of values) {
263
+ this.pushFront(value);
264
+ }
265
+ return this;
266
+ }
267
+
268
+ pushValuesBack(values) {
269
+ for (const value of values) {
270
+ this.pushBack(value);
271
+ }
272
+ return this;
273
+ }
274
+
275
+ appendValuesFront(values) {
276
+ return this.appendFront(List.from(values));
277
+ }
278
+
279
+ appendValuesBack(values) {
280
+ return this.appendBack(List.from(values));
281
+ }
282
+
283
+ static from(values) {
284
+ const list = new List();
285
+ for (const value of values) {
286
+ list.pushBack(value);
287
+ }
288
+ return list;
289
+ }
290
+ }
291
+
292
+ List.pop = pop;
293
+ List.extract = extract;
294
+ List.splice = splice;
295
+
296
+ List.Node = ListNode;
297
+ List.ValueNode = ListValueNode;
298
+
299
+ List.prototype.pop = List.prototype.popFront;
300
+ List.prototype.push = List.prototype.pushFront;
301
+ List.prototype.append = List.prototype.appendBack;
302
+
303
+ export default List;
@@ -0,0 +1,304 @@
1
+ 'use strict';
2
+
3
+ import {copyOptions} from './utils.js';
4
+
5
+ class ListHead {
6
+ constructor(head = null, options) {
7
+ if (head instanceof ListHead) {
8
+ ({nextName: this.nextName, prevName: this.prevName, head: this.head} = head);
9
+ return;
10
+ }
11
+ copyOptions(this, ListHead.defaults, options);
12
+ if (head instanceof ListHead.Unsafe) {
13
+ this.head = head.head;
14
+ return;
15
+ }
16
+ if (head) {
17
+ switch ((head[this.nextName] ? 2 : 0) + (head[this.prevName] ? 1 : 0)) {
18
+ case 0:
19
+ this.adopt(head);
20
+ break;
21
+ case 3:
22
+ // do nothing
23
+ break;
24
+ default:
25
+ throw new Error(`head is not an empty object nor a list with required properties (${this.nextName}, ${this.prevName})`);
26
+ }
27
+ } else {
28
+ head = this.makeNode();
29
+ }
30
+ this.head = head;
31
+ }
32
+
33
+ get isEmpty() {
34
+ return this.head[this.nextName] === this.head;
35
+ }
36
+
37
+ get front() {
38
+ return this.head[this.nextName];
39
+ }
40
+
41
+ get back() {
42
+ return this.head[this.prevName];
43
+ }
44
+
45
+ getLength() {
46
+ let n = 0;
47
+ for (let p = this.head[this.nextName]; p !== this.head; ++n, p = p[this.nextName]);
48
+ return n;
49
+ }
50
+
51
+ popFront() {
52
+ if (this.head[this.prevName] !== this.head) {
53
+ return ListHead.pop(this, this.head[this.nextName]).node;
54
+ }
55
+ }
56
+
57
+ popBack() {
58
+ if (this.head[this.prevName] !== this.head) {
59
+ return ListHead.pop(this, this.head[this.prevName]).node;
60
+ }
61
+ }
62
+
63
+ pushFront(node) {
64
+ this.adopt(node);
65
+ ListHead.splice(this, this.head[this.nextName], node);
66
+ return this;
67
+ }
68
+
69
+ pushBack(node) {
70
+ this.adopt(node);
71
+ ListHead.splice(this, this.head, node);
72
+ return this;
73
+ }
74
+
75
+ appendFront(list) {
76
+ if (list instanceof ListHead) {
77
+ list = list.head;
78
+ }
79
+ if (list[this.prevName] !== list) {
80
+ ListHead.splice(this, this.head[this.nextName], ListHead.extract(this, list[this.nextName], list[this.prevName]));
81
+ }
82
+ return this;
83
+ }
84
+
85
+ appendBack(list) {
86
+ if (list instanceof ListHead) {
87
+ list = list.head;
88
+ }
89
+ if (list[this.prevName] !== list) {
90
+ ListHead.splice(this, this.head, ListHead.extract(this, list[this.nextName], list[this.prevName]));
91
+ }
92
+ return this;
93
+ }
94
+
95
+ moveToFront(node) {
96
+ if (this.head[this.nextName] !== node) {
97
+ ListHead.splice(this, this.head[this.nextName], ListHead.extract(this, node, node));
98
+ }
99
+ return this;
100
+ }
101
+
102
+ moveToBack(node) {
103
+ if (this.head[this.prevName] !== node) {
104
+ ListHead.splice(this, this.head, ListHead.extract(this, node, node));
105
+ }
106
+ return this;
107
+ }
108
+
109
+ clear(drop) {
110
+ if (drop) {
111
+ while (!this.isEmpty) this.popFront();
112
+ } else {
113
+ this.head[this.prevName] = this.head[this.nextName] = this.head;
114
+ }
115
+ return this;
116
+ }
117
+
118
+ remove(from, to = from) {
119
+ ListHead.extract(this, from, to);
120
+ return this;
121
+ }
122
+
123
+ extract(from, to) {
124
+ return this.make(ListHead.splice(this, this.makeNode(), ListHead.extract(this, from, to)));
125
+ }
126
+
127
+ reverse() {
128
+ const list = this.head;
129
+ let next = list[this.nextName];
130
+ list[this.nextName] = list[this.prevName];
131
+ list[this.prevName] = next;
132
+ while (next !== list) {
133
+ const node = next;
134
+ next = node[this.nextName];
135
+ node[this.nextName] = node[this.prevName];
136
+ node[this.prevName] = next;
137
+ }
138
+ return this;
139
+ }
140
+
141
+ sort(compareFn) {
142
+ let prev = this.head;
143
+ for (const node of Array.from(this).sort(compareFn)) {
144
+ prev[this.nextName] = node;
145
+ node[this.prevName] = prev;
146
+ prev = node;
147
+ }
148
+ this.head[this.prevName] = prev;
149
+ prev[this.nextName] = this.head;
150
+ return this;
151
+ }
152
+
153
+ // iterators
154
+
155
+ [Symbol.iterator]() {
156
+ let current = this.head[this.nextName];
157
+ return {
158
+ next: () => {
159
+ if (current === this.head) return {done: true};
160
+ const value = current;
161
+ current = current[this.nextName];
162
+ return {value};
163
+ }
164
+ };
165
+ }
166
+
167
+ getIterable(from, to) {
168
+ return {
169
+ [Symbol.iterator]: () => {
170
+ let current = from || this.head[this.nextName];
171
+ const stop = to ? to[this.nextName] : this.head;
172
+ return {
173
+ next: () => {
174
+ if (current === stop) return {done: true};
175
+ const value = current;
176
+ current = current[this.nextName];
177
+ return {value};
178
+ }
179
+ };
180
+ }
181
+ };
182
+ }
183
+
184
+ getReverseIterable(from, to) {
185
+ return {
186
+ [Symbol.iterator]: () => {
187
+ let current = to || this.head[this.prevName];
188
+ const stop = from ? from[this.prevName] : this.head;
189
+ return {
190
+ next: () => {
191
+ if (current === stop) return {done: true};
192
+ const value = current;
193
+ current = current[this.prevName];
194
+ return {value};
195
+ }
196
+ };
197
+ }
198
+ };
199
+ }
200
+
201
+ // static utilities
202
+
203
+ static pop({nextName, prevName}, node) {
204
+ const list = node[nextName];
205
+ // the next line stitches the rest of the list excluding the node, and collapse the node into a one-node list
206
+ node[prevName] = node[nextName] = {[nextName]: node[prevName][nextName], [prevName]: node[nextName][prevName]} = node;
207
+ return {node, list};
208
+ }
209
+
210
+ static extract({nextName, prevName}, from, to) {
211
+ const prev = from[prevName],
212
+ next = to[nextName];
213
+ prev[nextName] = next;
214
+ next[prevName] = prev;
215
+ from[prevName] = to;
216
+ to[nextName] = from;
217
+ return from;
218
+ }
219
+
220
+ static splice({nextName, prevName}, list1, list2) {
221
+ const tail1 = list1[prevName],
222
+ tail2 = list2[prevName];
223
+ tail1[nextName] = list2;
224
+ list2[prevName] = tail1;
225
+ tail2[nextName] = list1;
226
+ list1[prevName] = tail2;
227
+ return list1;
228
+ }
229
+
230
+ // node helpers
231
+
232
+ makeNode() {
233
+ const node = {};
234
+ node[this.nextName] = node[this.prevName] = node;
235
+ return node;
236
+ }
237
+
238
+ adopt(node) {
239
+ if (node[this.nextName] || node[this.prevName]) {
240
+ if (node[this.nextName] === node && node[this.prevName] === node) return node;
241
+ throw new Error('node is already a part of a list, or there is a name clash');
242
+ }
243
+ node[this.nextName] = node[this.prevName] = node;
244
+ return node;
245
+ }
246
+
247
+ // helpers
248
+
249
+ clone() {
250
+ return new ListHead(this);
251
+ }
252
+
253
+ make(newHead = null) {
254
+ return new ListHead(newHead, this);
255
+ }
256
+
257
+ makeFrom(values) {
258
+ return ListHead.from(values, this);
259
+ }
260
+
261
+ pushValuesFront(values) {
262
+ for (const value of values) {
263
+ this.pushFront(value);
264
+ }
265
+ return this;
266
+ }
267
+
268
+ pushValuesBack(values) {
269
+ for (const value of values) {
270
+ this.pushBack(value);
271
+ }
272
+ return this;
273
+ }
274
+
275
+ appendValuesFront(values) {
276
+ return this.appendFront(ListHead.from(values, this));
277
+ }
278
+
279
+ appendValuesBack(values) {
280
+ return this.appendBack(ListHead.from(values, this));
281
+ }
282
+
283
+ static from(values, options) {
284
+ const list = new ListHead(null, options);
285
+ for (const value of values) {
286
+ list.pushBack(value);
287
+ }
288
+ return list;
289
+ }
290
+ }
291
+
292
+ ListHead.defaults = {nextName: 'next', prevName: 'prev'};
293
+
294
+ ListHead.prototype.pop = ListHead.prototype.popFront;
295
+ ListHead.prototype.push = ListHead.prototype.pushFront;
296
+ ListHead.prototype.append = ListHead.prototype.appendBack;
297
+
298
+ ListHead.Unsafe = class {
299
+ constructor(head) {
300
+ this.head = head;
301
+ }
302
+ };
303
+
304
+ export default ListHead;