@fluidframework/core-utils 2.43.0 → 2.50.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/CHANGELOG.md +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/list.d.ts +163 -0
- package/dist/list.d.ts.map +1 -0
- package/dist/list.js +332 -0
- package/dist/list.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/list.d.ts +163 -0
- package/lib/list.d.ts.map +1 -0
- package/lib/list.js +326 -0
- package/lib/list.js.map +1 -0
- package/package.json +3 -3
- package/src/index.ts +7 -0
- package/src/list.ts +411 -0
package/src/list.ts
ADDED
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
|
|
3
|
+
* Licensed under the MIT License.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Represents a node in a doubly linked list.
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
export interface ListNode<T> {
|
|
11
|
+
/**
|
|
12
|
+
* The list this node belongs to, or undefined if not attached.
|
|
13
|
+
*/
|
|
14
|
+
readonly list: DoublyLinkedList<T> | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* The data value stored in this node.
|
|
17
|
+
*/
|
|
18
|
+
readonly data: T;
|
|
19
|
+
/**
|
|
20
|
+
* The next node in the list, or undefined if this is the last node.
|
|
21
|
+
*/
|
|
22
|
+
readonly next: ListNode<T> | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* The previous node in the list, or undefined if this is the first node.
|
|
25
|
+
*/
|
|
26
|
+
readonly prev: ListNode<T> | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* Removes this node from its list.
|
|
29
|
+
* @returns The removed node, or undefined if not in a list.
|
|
30
|
+
*/
|
|
31
|
+
remove(): ListNode<T> | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Represents a range of nodes in a doubly linked list.
|
|
36
|
+
* @internal
|
|
37
|
+
*/
|
|
38
|
+
export interface ListNodeRange<T> {
|
|
39
|
+
/**
|
|
40
|
+
* The first node in the range.
|
|
41
|
+
*/
|
|
42
|
+
first: ListNode<T>;
|
|
43
|
+
/**
|
|
44
|
+
* The last node in the range.
|
|
45
|
+
*/
|
|
46
|
+
last: ListNode<T>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class HeadNode<T> {
|
|
50
|
+
public _next: HeadNode<T> | DataNode<T> = this;
|
|
51
|
+
public _prev: HeadNode<T> | DataNode<T> = this;
|
|
52
|
+
public headNode: HeadNode<T> = this;
|
|
53
|
+
private readonly _list?: DoublyLinkedList<T>;
|
|
54
|
+
public constructor(list: DoublyLinkedList<T> | undefined) {
|
|
55
|
+
if (list) {
|
|
56
|
+
this._list = list;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
public get next(): DataNode<T> | undefined {
|
|
60
|
+
return this._next === this.headNode ? undefined : (this._next as DataNode<T>);
|
|
61
|
+
}
|
|
62
|
+
public get prev(): DataNode<T> | undefined {
|
|
63
|
+
return this._prev === this.headNode ? undefined : (this._prev as DataNode<T>);
|
|
64
|
+
}
|
|
65
|
+
public get list(): DoublyLinkedList<T> | undefined {
|
|
66
|
+
return this.headNode._list;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// The any is needed for use in the remove function, where the nodes are defined with a generic type.
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
72
|
+
const DeadHead = new HeadNode<any>(undefined);
|
|
73
|
+
|
|
74
|
+
class DataNode<T> extends HeadNode<T> implements ListNode<T> {
|
|
75
|
+
public constructor(
|
|
76
|
+
headNode: HeadNode<T>,
|
|
77
|
+
public readonly data: T,
|
|
78
|
+
) {
|
|
79
|
+
super(undefined);
|
|
80
|
+
this.headNode = headNode;
|
|
81
|
+
}
|
|
82
|
+
public remove(): ListNode<T> | undefined {
|
|
83
|
+
return this.list?.remove(this);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function insertAfter<T>(node: DataNode<T> | HeadNode<T>, items: T[]): ListNodeRange<T> {
|
|
88
|
+
let previousNode = node;
|
|
89
|
+
const oldNext = previousNode._next;
|
|
90
|
+
let newRange: ListNodeRange<T> | undefined;
|
|
91
|
+
for (const n of items) {
|
|
92
|
+
const newNode = new DataNode<T>(node.headNode, n);
|
|
93
|
+
if (newRange === undefined) {
|
|
94
|
+
newRange = { first: newNode, last: newNode };
|
|
95
|
+
} else {
|
|
96
|
+
newRange.last = newNode;
|
|
97
|
+
}
|
|
98
|
+
newNode._prev = previousNode;
|
|
99
|
+
previousNode._next = newNode;
|
|
100
|
+
previousNode = newNode;
|
|
101
|
+
}
|
|
102
|
+
oldNext._prev = previousNode;
|
|
103
|
+
previousNode._next = oldNext;
|
|
104
|
+
// explicitly prevent newRange from being undefined without casting,
|
|
105
|
+
// and without additional conditionals, as this is used in some perf critical areas.
|
|
106
|
+
// i could have just asserted, but that throws a non-user friendly error,
|
|
107
|
+
// so i went with a more user-friendly error, which describes the
|
|
108
|
+
// only condition that could lead to this being undefined in the current code.
|
|
109
|
+
if (newRange === undefined) {
|
|
110
|
+
throw new Error("items must not be empty");
|
|
111
|
+
}
|
|
112
|
+
return newRange;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* A doubly linked list implementation with array-like methods and node access.
|
|
117
|
+
* @typeParam T - The type of data stored in the list nodes.
|
|
118
|
+
* @internal
|
|
119
|
+
*/
|
|
120
|
+
export class DoublyLinkedList<T>
|
|
121
|
+
implements
|
|
122
|
+
Iterable<ListNode<T>>, // try to match array signature and semantics where possible
|
|
123
|
+
Pick<ListNode<T>[], "pop" | "shift" | "length" | "includes">
|
|
124
|
+
{
|
|
125
|
+
/**
|
|
126
|
+
* Creates a new doubly linked list optionally initialized with values.
|
|
127
|
+
* @param values - Optional iterable of values to populate the list.
|
|
128
|
+
*/
|
|
129
|
+
public constructor(values?: Iterable<T>) {
|
|
130
|
+
if (values !== undefined) {
|
|
131
|
+
this.push(...values);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Finds the first node matching the predicate.
|
|
136
|
+
* @param predicate - Function to test each node.
|
|
137
|
+
* @returns The first matching node, or undefined if none found.
|
|
138
|
+
*/
|
|
139
|
+
public find(
|
|
140
|
+
predicate: (value: ListNode<T>, obj: DoublyLinkedList<T>) => boolean,
|
|
141
|
+
): ListNode<T> | undefined {
|
|
142
|
+
let found: ListNode<T> | undefined;
|
|
143
|
+
walkList(this, (node) => {
|
|
144
|
+
if (predicate(node, this)) {
|
|
145
|
+
found = node;
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return found;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Returns an iterable that maps each node to a new value.
|
|
154
|
+
* @param callbackfn - Function to produce a new value for each node.
|
|
155
|
+
*/
|
|
156
|
+
public map<U>(callbackfn: (value: ListNode<T>) => U): Iterable<U> {
|
|
157
|
+
let node = this.first;
|
|
158
|
+
const iterator: IterableIterator<U> = {
|
|
159
|
+
next(): IteratorResult<U> {
|
|
160
|
+
if (node === undefined) {
|
|
161
|
+
return { done: true, value: undefined };
|
|
162
|
+
}
|
|
163
|
+
const rtn = { value: callbackfn(node), done: false };
|
|
164
|
+
node = node.next;
|
|
165
|
+
return rtn;
|
|
166
|
+
},
|
|
167
|
+
[Symbol.iterator]() {
|
|
168
|
+
return this;
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
return iterator;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Inserts items after the specified node.
|
|
176
|
+
* @param preceding - The node to insert after.
|
|
177
|
+
* @param items - Items to insert.
|
|
178
|
+
* @returns The range of newly inserted nodes.
|
|
179
|
+
*/
|
|
180
|
+
public insertAfter(preceding: ListNode<T>, ...items: T[]): ListNodeRange<T> {
|
|
181
|
+
if (!this._includes(preceding)) {
|
|
182
|
+
throw new Error("preceding not in list");
|
|
183
|
+
}
|
|
184
|
+
this._len += items.length;
|
|
185
|
+
return insertAfter(preceding, items);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Removes and returns the last node in the list.
|
|
190
|
+
* @returns The removed node, or undefined if the list is empty.
|
|
191
|
+
*/
|
|
192
|
+
public pop(): ListNode<T> | undefined {
|
|
193
|
+
return this.remove(this.last);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Appends items to the end of the list.
|
|
198
|
+
* @param items - Items to append.
|
|
199
|
+
* @returns The range of newly inserted nodes.
|
|
200
|
+
*/
|
|
201
|
+
public push(...items: T[]): ListNodeRange<T> {
|
|
202
|
+
this._len += items.length;
|
|
203
|
+
const start = this.headNode._prev;
|
|
204
|
+
return insertAfter(start, items);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Removes and returns the first node in the list.
|
|
209
|
+
* @returns The removed node, or undefined if the list is empty.
|
|
210
|
+
*/
|
|
211
|
+
public shift(): ListNode<T> | undefined {
|
|
212
|
+
return this.remove(this.first);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Inserts items at the start of the list.
|
|
217
|
+
* @param items - Items to insert.
|
|
218
|
+
* @returns The range of newly inserted nodes.
|
|
219
|
+
*/
|
|
220
|
+
public unshift(...items: T[]): ListNodeRange<T> {
|
|
221
|
+
this._len += items.length;
|
|
222
|
+
return insertAfter(this.headNode, items);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Removes nodes starting at `start` until `end` or `count` is reached.
|
|
227
|
+
* @param start - The node to start removing from.
|
|
228
|
+
* @param countOrEnd - The number of nodes to remove or the end node.
|
|
229
|
+
* @returns A new list containing the removed nodes.
|
|
230
|
+
*/
|
|
231
|
+
public splice(start: ListNode<T>, countOrEnd?: ListNode<T> | number): DoublyLinkedList<T> {
|
|
232
|
+
const newList = new DoublyLinkedList<T>();
|
|
233
|
+
walkList(
|
|
234
|
+
this,
|
|
235
|
+
(node) => {
|
|
236
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
237
|
+
const removedNode = this._remove(node)!;
|
|
238
|
+
// whats special here is we preserve the node
|
|
239
|
+
// this allow looking up the old node in the new list
|
|
240
|
+
// when something preserves a reference
|
|
241
|
+
removedNode.headNode = newList.headNode;
|
|
242
|
+
removedNode._next = newList.headNode;
|
|
243
|
+
removedNode._prev = newList.headNode._prev;
|
|
244
|
+
newList.headNode._prev._next = removedNode;
|
|
245
|
+
newList.headNode._prev = removedNode;
|
|
246
|
+
newList._len++;
|
|
247
|
+
if (node === countOrEnd || newList.length === countOrEnd) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
start,
|
|
252
|
+
);
|
|
253
|
+
return newList;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Checks if the node is in this list.
|
|
258
|
+
* @param node - The node to check.
|
|
259
|
+
* @returns True if the node is in the list.
|
|
260
|
+
*/
|
|
261
|
+
public includes(node: ListNode<T> | undefined): node is ListNode<T> {
|
|
262
|
+
return this._includes(node);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
private _includes(node: ListNode<T> | undefined): node is DataNode<T> {
|
|
266
|
+
return node instanceof DataNode && node.headNode === this.headNode;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private _remove(node: ListNode<T> | undefined): DataNode<T> | undefined {
|
|
270
|
+
if (this._includes(node)) {
|
|
271
|
+
node._prev._next = node._next;
|
|
272
|
+
node._next._prev = node._prev;
|
|
273
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
274
|
+
node.headNode = node._next = node._prev = DeadHead;
|
|
275
|
+
this._len--;
|
|
276
|
+
return node;
|
|
277
|
+
}
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Removes the specified node from the list.
|
|
283
|
+
* @param node - The node to remove.
|
|
284
|
+
* @returns The removed node, or undefined if not in the list.
|
|
285
|
+
*/
|
|
286
|
+
public remove(node: ListNode<T> | undefined): ListNode<T> | undefined {
|
|
287
|
+
return this._remove(node);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
public [Symbol.iterator](): IterableIterator<ListNode<T>> {
|
|
291
|
+
let value = this.first;
|
|
292
|
+
const iterator: IterableIterator<ListNode<T>> = {
|
|
293
|
+
next(): IteratorResult<ListNode<T>> {
|
|
294
|
+
if (value !== undefined) {
|
|
295
|
+
const rtn = { value, done: false };
|
|
296
|
+
value = value.next;
|
|
297
|
+
return rtn;
|
|
298
|
+
}
|
|
299
|
+
return { value: undefined, done: true };
|
|
300
|
+
},
|
|
301
|
+
[Symbol.iterator]() {
|
|
302
|
+
return this;
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
return iterator;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
private _len: number = 0;
|
|
309
|
+
private readonly headNode: HeadNode<T> | DataNode<T> = new HeadNode(this);
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* The number of nodes in the list.
|
|
313
|
+
*/
|
|
314
|
+
public get length(): number {
|
|
315
|
+
return this._len;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Whether the list is empty.
|
|
320
|
+
*/
|
|
321
|
+
public get empty(): boolean {
|
|
322
|
+
return this._len === 0;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* The first node in the list, or undefined if empty.
|
|
327
|
+
*/
|
|
328
|
+
public get first(): ListNode<T> | undefined {
|
|
329
|
+
return this.headNode.next;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* The last node in the list, or undefined if empty.
|
|
334
|
+
*/
|
|
335
|
+
public get last(): ListNode<T> | undefined {
|
|
336
|
+
return this.headNode.prev;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Iterates over the nodes of a `DoublyLinkedList`, calling the provided visitor function for each node.
|
|
342
|
+
* Iteration starts at the specified node (or at the head/tail if not provided) and proceeds forward or backward.
|
|
343
|
+
* If the visitor returns `false`, iteration stops early.
|
|
344
|
+
*
|
|
345
|
+
* @typeParam T - The type of data stored in the list nodes.
|
|
346
|
+
* @param list - The list to walk.
|
|
347
|
+
* @param visitor - Function called for each node. If it returns `false`, iteration stops.
|
|
348
|
+
* @param start - Optional node to start iteration from. If not provided, starts at the first (or last if `forward` is false) node.
|
|
349
|
+
* @param forward - If `true` (default), iterates forward; if `false`, iterates backward.
|
|
350
|
+
* @returns `false` if iteration was stopped early by the visitor, otherwise `true`.
|
|
351
|
+
*
|
|
352
|
+
* @internal
|
|
353
|
+
*/
|
|
354
|
+
export function walkList<T>(
|
|
355
|
+
list: DoublyLinkedList<T>,
|
|
356
|
+
visitor: (node: ListNode<T>) => boolean | void,
|
|
357
|
+
start?: ListNode<T>,
|
|
358
|
+
forward: boolean = true,
|
|
359
|
+
): boolean {
|
|
360
|
+
let current: ListNode<T> | undefined;
|
|
361
|
+
if (start) {
|
|
362
|
+
if (!list.includes(start)) {
|
|
363
|
+
throw new Error("start must be in the provided list");
|
|
364
|
+
}
|
|
365
|
+
current = start;
|
|
366
|
+
} else {
|
|
367
|
+
current = forward ? list.first : list.last;
|
|
368
|
+
}
|
|
369
|
+
// cache the next node, incase the visitor mutates the list
|
|
370
|
+
// need this to support splice
|
|
371
|
+
let next = forward ? current?.next : current?.prev;
|
|
372
|
+
while (current !== undefined) {
|
|
373
|
+
if (visitor(current) === false) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
current = next;
|
|
377
|
+
next = forward ? next?.next : next?.prev;
|
|
378
|
+
}
|
|
379
|
+
return true;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Creates a lazily evaluated iterable which returns values while the predicate returns true,
|
|
384
|
+
* and stops iterating at the first value where the predicate is false.
|
|
385
|
+
* @param start - the node to start the iteration from
|
|
386
|
+
* @param includePredicate - determine if the current value be included in the iteration or stop if iteration
|
|
387
|
+
*
|
|
388
|
+
* @internal
|
|
389
|
+
*/
|
|
390
|
+
export function iterateListValuesWhile<T>(
|
|
391
|
+
start: ListNode<T> | undefined,
|
|
392
|
+
includePredicate: (n: ListNode<T>) => boolean,
|
|
393
|
+
): Iterable<T> {
|
|
394
|
+
let next: ListNode<T> | undefined = start;
|
|
395
|
+
const iterator: IterableIterator<T> = {
|
|
396
|
+
next: (): IteratorResult<T> => {
|
|
397
|
+
if (next !== undefined) {
|
|
398
|
+
const current = next;
|
|
399
|
+
next = current.next;
|
|
400
|
+
if (includePredicate(current) === true) {
|
|
401
|
+
return { value: current.data, done: false };
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return { done: true, value: undefined };
|
|
405
|
+
},
|
|
406
|
+
[Symbol.iterator]() {
|
|
407
|
+
return this;
|
|
408
|
+
},
|
|
409
|
+
};
|
|
410
|
+
return iterator;
|
|
411
|
+
}
|