subay 0.0.13 → 0.1.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/arr.ts ADDED
@@ -0,0 +1,428 @@
1
+ // fully adopt from https://github.com/localvoid/ivi/blob/fe3b159fac6cddb7521dccc47d0a041b60d0947e/packages/ivi/src/lib/core.ts#L1210
2
+
3
+ const enum MagicValues {
4
+ /**
5
+ * One of the children nodes were moved.
6
+ */
7
+ RearrangeNodes = 1073741823, // Max SMI Value
8
+ /**
9
+ * New node marker.
10
+ */
11
+ NewNodeMark = -1,
12
+ /**
13
+ * LIS marker.
14
+ */
15
+ LISMark = -2,
16
+ }
17
+
18
+ /**
19
+ * Update children list with track by key algorithm.
20
+ *
21
+ * High-level overview of the algorithm that is implemented in this function:
22
+ *
23
+ * This algorithm finds a minimum number of DOM operations. It works in
24
+ * several steps:
25
+ *
26
+ * 1. Common prefix and suffix optimization.
27
+ *
28
+ * Look for nodes with identical keys by simultaneously iterating through nodes
29
+ * in the old children list `A` and new children list `B` from both sides.
30
+ *
31
+ * A: -> [a b c d] <-
32
+ * B: -> [a b d] <-
33
+ *
34
+ * Skip nodes "a" and "b" at the start, and node "d" at the end.
35
+ *
36
+ * A: -> [c] <-
37
+ * B: -> [] <-
38
+ *
39
+ * 2. Zero length optimizations.
40
+ *
41
+ * Check if the size of one of the list is equal to zero. When length of the
42
+ * old children list is zero, insert remaining nodes from the new list. When
43
+ * length of the new children list is zero, remove remaining nodes from the old
44
+ * list.
45
+ *
46
+ * A: -> [a b c g] <-
47
+ * B: -> [a g] <-
48
+ *
49
+ * Skip nodes "a" and "g" (prefix and suffix optimization).
50
+ *
51
+ * A: [b c]
52
+ * B: []
53
+ *
54
+ * Remove nodes "b" and "c".
55
+ *
56
+ * 3. Index and unmount removed nodes.
57
+ *
58
+ * A: [b c d e f]
59
+ * B: [c b h f e]
60
+ * P: [. . . . .] // . == -1
61
+ *
62
+ * Create array `P` (`sources`) with the length of the new children list and
63
+ * fills it with `NewNodeMark` values. This mark indicates that node at this
64
+ * position should be mounted. Later we will assign node positions in the old
65
+ * children list to this array.
66
+ *
67
+ * A: [b c d e f]
68
+ * B: [c b h f e]
69
+ * P: [. . . . .] // . == -1
70
+ * I: {
71
+ * c: 0, // B[0] == c
72
+ * b: 1, // B[1] == b
73
+ * h: 2,
74
+ * f: 3,
75
+ * e: 4,
76
+ * }
77
+ * last = 0
78
+ *
79
+ * Create reverse index `I` that maps keys to node positions in the new
80
+ * children list.
81
+ *
82
+ * A: [b c d e f]
83
+ * ^
84
+ * B: [c b h f e]
85
+ * P: [. 0 . . .] // . == -1
86
+ * I: {
87
+ * c: 0,
88
+ * b: 1, <-
89
+ * h: 2,
90
+ * f: 3,
91
+ * e: 4,
92
+ * }
93
+ * last = 1
94
+ *
95
+ * Assign original positions of the nodes from the old children list to the
96
+ * array `P`.
97
+ *
98
+ * Iterate through nodes in the old children list and gets their new positions
99
+ * from the index `I`. Assign old node position to the array `P`. When index
100
+ * `I` doesn't have a key for the old node, it means that it should be
101
+ * unmounted.
102
+ *
103
+ * When we assigning positions to the array `P`, we also store position of the
104
+ * last seen node in the new children list `pos`, if the last seen position is
105
+ * greater than the current position of the node at the new list, then we are
106
+ * switching `rearrangeNodes` flag to `true` (`pos === RearrangeNodes`).
107
+ *
108
+ * A: [b c d e f]
109
+ * ^
110
+ * B: [c b h f e]
111
+ * P: [1 0 . . .] // . == -1
112
+ * I: {
113
+ * c: 0, <-
114
+ * b: 1,
115
+ * h: 2,
116
+ * f: 3,
117
+ * e: 4,
118
+ * }
119
+ * last = 1 // last > 0; rearrangeNodes = true
120
+ *
121
+ * The last position `1` is greater than the current position of the node at the
122
+ * new list `0`, switch `rearrangeNodes` flag to `true`.
123
+ *
124
+ * A: [b c d e f]
125
+ * ^
126
+ * B: [c b h f e]
127
+ * P: [1 0 . . .] // . == -1
128
+ * I: {
129
+ * c: 0,
130
+ * b: 1,
131
+ * h: 2,
132
+ * f: 3,
133
+ * e: 4,
134
+ * }
135
+ * rearrangeNodes = true
136
+ *
137
+ * Node with key "d" doesn't exist in the index `I`, unmounts node `d`.
138
+ *
139
+ * A: [b c d e f]
140
+ * ^
141
+ * B: [c b h f e]
142
+ * P: [1 0 . . 3] // . == -1
143
+ * I: {
144
+ * c: 0,
145
+ * b: 1,
146
+ * h: 2,
147
+ * f: 3,
148
+ * e: 4, <-
149
+ * }
150
+ * rearrangeNodes = true
151
+ *
152
+ * Assign position `3` for `e` node.
153
+ *
154
+ * A: [b c d e f]
155
+ * ^
156
+ * B: [c b h f e]
157
+ * P: [1 0 . 4 3] // . == -1
158
+ * I: {
159
+ * c: 0,
160
+ * b: 1,
161
+ * h: 2,
162
+ * f: 3, <-
163
+ * e: 4,
164
+ * }
165
+ * rearrangeNodes = true
166
+ *
167
+ * Assign position `4` for 'f' node.
168
+ *
169
+ * 4. Find minimum number of moves when `rearrangeNodes` flag is on and mount
170
+ * new nodes.
171
+ *
172
+ * A: [b c d e f]
173
+ * B: [c b h f e]
174
+ * P: [1 * . 4 *] // . == -1 * == -2
175
+ *
176
+ * When `rearrangeNodes` is on, mark all nodes in the array `P` that belong to
177
+ * the [longest increasing subsequence](http://en.wikipedia.org/wiki/Longest_increasing_subsequence)
178
+ * and move all nodes that doesn't belong to this subsequence.
179
+ *
180
+ * Iterate over the new children list and the `P` array simultaneously. When
181
+ * value from `P` array is equal to `NewNodeMark`, mount a new node. When it
182
+ * isn't equal to `LisMark`, move it to a new position.
183
+ *
184
+ * A: [b c d e f]
185
+ * B: [c b h f e]
186
+ * ^ // new_pos == 4
187
+ * P: [1 * . 4 *] // . == NewNodeMark * == LisMark
188
+ * ^
189
+ *
190
+ * Node "e" has `LisMark` value in the array `P`, nothing changes.
191
+ *
192
+ * A: [b c d e f]
193
+ * B: [c b h f e]
194
+ * ^ // new_pos == 3
195
+ * P: [1 * . 4 *] // . == NewNodeMark * == LisMark
196
+ * ^
197
+ *
198
+ * Node "f" has `4` value in the array `P`, move it before the next node "e".
199
+ *
200
+ * A: [b c d e f]
201
+ * B: [c b h f e]
202
+ * ^ // new_pos == 2
203
+ * P: [1 * . 4 *] // . == NewNodeMark * == LisMark
204
+ * ^
205
+ *
206
+ * Node "h" has `NewNodeMark` value in the array `P`, mount new node "h".
207
+ *
208
+ * A: [b c d e f]
209
+ * B: [c b h f e]
210
+ * ^ // new_pos == 1
211
+ * P: [1 * . 4 *] // . == NewNodeMark * == LisMark
212
+ * ^
213
+ *
214
+ * Node "b" has `LisMark` value in the array `P`, nothing changes.
215
+ *
216
+ * A: [b c d e f]
217
+ * B: [c b h f e]
218
+ * ^ // new_pos == 0
219
+ * P: [1 * . 4 *] // . == NewNodeMark * == LisMark
220
+ *
221
+ * Node "c" has `1` value in the array `P`, move it before the next node "b".
222
+ *
223
+ * When `rearrangeNodes` flag is off, skip LIS algorithm and mount nodes that
224
+ * have `NewNodeMark` value in the array `P`.
225
+ *
226
+ * NOTE: There are many variations of this algorithm that are used by many UI
227
+ * libraries and many implementations are still using an old optimization
228
+ * technique that were removed several years ago from this implementation. This
229
+ * optimization were used to improve performance of simple moves/swaps. E.g.
230
+ *
231
+ * A: -> [a b c] <-
232
+ * B: -> [c b a] <-
233
+ *
234
+ * Move "a" and "c" nodes to the other edge.
235
+ *
236
+ * A: -> [b] <-
237
+ * B: -> [b] <-
238
+ *
239
+ * Skip node "b".
240
+ *
241
+ * This optimization were removed because it breaks invariant that insert and
242
+ * remove operations shouldn't trigger a move operation. E.g.
243
+ *
244
+ * A: -> [a b]
245
+ * B: [c a] <-
246
+ *
247
+ * Move node "a" to the end.
248
+ *
249
+ * A: [b]
250
+ * B: [c a]
251
+ *
252
+ * Remove node "b" and insert node "c".
253
+ *
254
+ * In this use case, this optimization performs one unnecessary operation.
255
+ * Instead of removing node "b" and inserting node "c", it also moves node "a".
256
+ *
257
+ */
258
+ export const arr = <T>(
259
+ aKeys: readonly T[],
260
+ bKeys: readonly T[],
261
+ onDelete: (item: T) => void,
262
+ onCreate: (item: T) => void,
263
+ onRemain: (item: T) => void,
264
+ onLocate: (item: T) => void,
265
+ ): void => {
266
+ const bLength = bKeys.length;
267
+ const aLength = aKeys.length;
268
+
269
+ if (bLength === 0) {
270
+ // New children list is empty.
271
+ if (aLength > 0) {
272
+ // Unmount nodes from the old children list.
273
+ aKeys.forEach(onDelete);
274
+ }
275
+ } else if (aLength === 0) {
276
+ // Old children list is empty.
277
+ bKeys.forEach(onCreate);
278
+ } else {
279
+ let aEnd = aLength - 1;
280
+ let bEnd = bLength - 1;
281
+ let start = 0;
282
+
283
+ // Step 1
284
+ // Align common elements from the start
285
+ while (start <= aEnd && start <= bEnd && aKeys[start] === bKeys[start]) {
286
+ onRemain(aKeys[start++]!);
287
+ }
288
+ // Align common elements from the end
289
+ while (start <= aEnd && start <= bEnd && aKeys[aEnd] === bKeys[bEnd]) {
290
+ aEnd--;
291
+ bEnd--;
292
+ }
293
+
294
+ // Step 2
295
+ if (start > aEnd) {
296
+ // All nodes from `a` are updated, insert the rest from `b`.
297
+ while (start <= bEnd) {
298
+ onCreate(bKeys[start++]!);
299
+ }
300
+ } else if (start > bEnd) {
301
+ // All nodes from `b` are updated, remove the rest from `a`.
302
+ while (start <= aEnd) {
303
+ onDelete(aKeys[start++]!);
304
+ }
305
+ } else {
306
+ // Step 3
307
+ const bLength = bEnd - start + 1;
308
+ const sources = new Int32Array(bLength); // Maps positions in the new children list to positions in the old list.
309
+ const keyIndex = new Map<T, number>(); // Maps keys to their positions in the new children list.
310
+ for (let i = 0, j = start; i < bLength; i++, j++) {
311
+ // `NewNodeMark` value indicates that node doesn't exist in the old children list.
312
+ sources[i] = MagicValues.NewNodeMark;
313
+ keyIndex.set(bKeys[j]!, j);
314
+ }
315
+
316
+ // When `nodePosition === RearrangeNodes`, it means that one of the nodes is in the wrong position and we should
317
+ // rearrange nodes with LIS-based algorithm `markLIS()`.
318
+ let nodePosition = 0;
319
+ for (let i = start; i <= aEnd; i++) {
320
+ const nextPosition = keyIndex.get(aKeys[i]!);
321
+ if (nextPosition !== void 0) {
322
+ nodePosition = nodePosition < nextPosition ? nextPosition : MagicValues.RearrangeNodes;
323
+ sources[nextPosition - start] = i;
324
+ } else {
325
+ onDelete(aKeys[i]!);
326
+ }
327
+ }
328
+
329
+ // Step 4
330
+
331
+ // Mark LIS nodes only when this node weren't moved `moveNode === false` and we've detected that one of the
332
+ // children nodes were moved `pos === MagicValues.MovedChildren`.
333
+ if (nodePosition === MagicValues.RearrangeNodes) {
334
+ markLIS(sources);
335
+ }
336
+ for (let i = 0; i < bLength; i++) {
337
+ const bNode = bKeys[start + i]!;
338
+ const lisValue = sources[i]!;
339
+ if (lisValue === -1) {
340
+ onCreate(bNode);
341
+ } else if (nodePosition === MagicValues.RearrangeNodes && lisValue !== MagicValues.LISMark) {
342
+ onLocate(bNode);
343
+ } else {
344
+ onRemain(bNode);
345
+ }
346
+ }
347
+ }
348
+
349
+ // Delayed update for nodes from Step 1 (prefix only). Reconciliation algorithm always updates nodes from left to
350
+ // right.
351
+ for (let i = 1; i + aEnd < aLength /*&& i + bEnd < bLength*/; i++) {
352
+ onRemain(aKeys[i + aEnd]!);
353
+ }
354
+ }
355
+ };
356
+
357
+ /**
358
+ * Modified Longest Increased Subsequence algorithm.
359
+ *
360
+ * Mutates input array `a` and replaces all values that are part of LIS with -2 value.
361
+ *
362
+ * Constraints:
363
+ * - Doesn't work with negative numbers. -1 values are ignored.
364
+ * - Input array `a` should contain at least one value that is greater than -1.
365
+ *
366
+ * {@link http://en.wikipedia.org/wiki/Longest_increasing_subsequence}
367
+ *
368
+ * @example
369
+ *
370
+ * const A = Int32Array.from([-1, 0, 2, 1]);
371
+ * markLIS(A);
372
+ * // A => [-1, -2, 2, -2]
373
+ *
374
+ * @param a Array of numbers.
375
+ */
376
+ const markLIS = (a: Int32Array): void => {
377
+ const length = a.length;
378
+ const parent = new Int32Array(length);
379
+ const index = new Int32Array(length);
380
+ let indexLength = 0;
381
+ let i = 0;
382
+ let j: number;
383
+ let k: number;
384
+ let lo: number;
385
+ let hi: number;
386
+
387
+ // Skip -1 values at the start of the input array `a`.
388
+ while (a[i] === MagicValues.NewNodeMark) i++;
389
+
390
+ index[0] = i++;
391
+ for (; i < length; i++) {
392
+ k = a[i]!;
393
+ if (k !== MagicValues.NewNodeMark) {
394
+ // Ignore -1 values.
395
+ j = index[indexLength]!;
396
+ if (a[j]! < k) {
397
+ parent[i] = j;
398
+ index[++indexLength] = i;
399
+ } else {
400
+ lo = 0;
401
+ hi = indexLength;
402
+
403
+ while (lo < hi) {
404
+ j = (lo + hi) >> 1;
405
+ if (a[index[j]!]! < k) {
406
+ lo = j + 1;
407
+ } else {
408
+ hi = j;
409
+ }
410
+ }
411
+
412
+ if (k < a[index[lo]!]!) {
413
+ if (lo > 0) {
414
+ parent[i] = index[lo - 1]!;
415
+ }
416
+ index[lo] = i;
417
+ }
418
+ }
419
+ }
420
+ }
421
+
422
+ // Mutate input array `a` and assign -2 value to all nodes that are part of LIS.
423
+ j = index[indexLength]!;
424
+ while (indexLength-- >= 0) {
425
+ a[j] = MagicValues.LISMark;
426
+ j = parent[j]!;
427
+ }
428
+ };