UNPKG

11.8 kBJavaScriptView Raw
1"use strict";
2/*---------------------------------------------------------------------------------------------
3 * Copyright (c) Microsoft Corporation. All rights reserved.
4 * Licensed under the MIT License. See License.txt in the project root for license information.
5 *--------------------------------------------------------------------------------------------*/
6var _a;
7Object.defineProperty(exports, "__esModule", { value: true });
8exports.LRUCache = exports.LinkedMap = exports.Touch = void 0;
9var Touch;
10(function (Touch) {
11 Touch.None = 0;
12 Touch.First = 1;
13 Touch.AsOld = Touch.First;
14 Touch.Last = 2;
15 Touch.AsNew = Touch.Last;
16})(Touch || (exports.Touch = Touch = {}));
17class LinkedMap {
18 constructor() {
19 this[_a] = 'LinkedMap';
20 this._map = new Map();
21 this._head = undefined;
22 this._tail = undefined;
23 this._size = 0;
24 this._state = 0;
25 }
26 clear() {
27 this._map.clear();
28 this._head = undefined;
29 this._tail = undefined;
30 this._size = 0;
31 this._state++;
32 }
33 isEmpty() {
34 return !this._head && !this._tail;
35 }
36 get size() {
37 return this._size;
38 }
39 get first() {
40 return this._head?.value;
41 }
42 get last() {
43 return this._tail?.value;
44 }
45 has(key) {
46 return this._map.has(key);
47 }
48 get(key, touch = Touch.None) {
49 const item = this._map.get(key);
50 if (!item) {
51 return undefined;
52 }
53 if (touch !== Touch.None) {
54 this.touch(item, touch);
55 }
56 return item.value;
57 }
58 set(key, value, touch = Touch.None) {
59 let item = this._map.get(key);
60 if (item) {
61 item.value = value;
62 if (touch !== Touch.None) {
63 this.touch(item, touch);
64 }
65 }
66 else {
67 item = { key, value, next: undefined, previous: undefined };
68 switch (touch) {
69 case Touch.None:
70 this.addItemLast(item);
71 break;
72 case Touch.First:
73 this.addItemFirst(item);
74 break;
75 case Touch.Last:
76 this.addItemLast(item);
77 break;
78 default:
79 this.addItemLast(item);
80 break;
81 }
82 this._map.set(key, item);
83 this._size++;
84 }
85 return this;
86 }
87 delete(key) {
88 return !!this.remove(key);
89 }
90 remove(key) {
91 const item = this._map.get(key);
92 if (!item) {
93 return undefined;
94 }
95 this._map.delete(key);
96 this.removeItem(item);
97 this._size--;
98 return item.value;
99 }
100 shift() {
101 if (!this._head && !this._tail) {
102 return undefined;
103 }
104 if (!this._head || !this._tail) {
105 throw new Error('Invalid list');
106 }
107 const item = this._head;
108 this._map.delete(item.key);
109 this.removeItem(item);
110 this._size--;
111 return item.value;
112 }
113 forEach(callbackfn, thisArg) {
114 const state = this._state;
115 let current = this._head;
116 while (current) {
117 if (thisArg) {
118 callbackfn.bind(thisArg)(current.value, current.key, this);
119 }
120 else {
121 callbackfn(current.value, current.key, this);
122 }
123 if (this._state !== state) {
124 throw new Error(`LinkedMap got modified during iteration.`);
125 }
126 current = current.next;
127 }
128 }
129 keys() {
130 const state = this._state;
131 let current = this._head;
132 const iterator = {
133 [Symbol.iterator]: () => {
134 return iterator;
135 },
136 next: () => {
137 if (this._state !== state) {
138 throw new Error(`LinkedMap got modified during iteration.`);
139 }
140 if (current) {
141 const result = { value: current.key, done: false };
142 current = current.next;
143 return result;
144 }
145 else {
146 return { value: undefined, done: true };
147 }
148 }
149 };
150 return iterator;
151 }
152 values() {
153 const state = this._state;
154 let current = this._head;
155 const iterator = {
156 [Symbol.iterator]: () => {
157 return iterator;
158 },
159 next: () => {
160 if (this._state !== state) {
161 throw new Error(`LinkedMap got modified during iteration.`);
162 }
163 if (current) {
164 const result = { value: current.value, done: false };
165 current = current.next;
166 return result;
167 }
168 else {
169 return { value: undefined, done: true };
170 }
171 }
172 };
173 return iterator;
174 }
175 entries() {
176 const state = this._state;
177 let current = this._head;
178 const iterator = {
179 [Symbol.iterator]: () => {
180 return iterator;
181 },
182 next: () => {
183 if (this._state !== state) {
184 throw new Error(`LinkedMap got modified during iteration.`);
185 }
186 if (current) {
187 const result = { value: [current.key, current.value], done: false };
188 current = current.next;
189 return result;
190 }
191 else {
192 return { value: undefined, done: true };
193 }
194 }
195 };
196 return iterator;
197 }
198 [(_a = Symbol.toStringTag, Symbol.iterator)]() {
199 return this.entries();
200 }
201 trimOld(newSize) {
202 if (newSize >= this.size) {
203 return;
204 }
205 if (newSize === 0) {
206 this.clear();
207 return;
208 }
209 let current = this._head;
210 let currentSize = this.size;
211 while (current && currentSize > newSize) {
212 this._map.delete(current.key);
213 current = current.next;
214 currentSize--;
215 }
216 this._head = current;
217 this._size = currentSize;
218 if (current) {
219 current.previous = undefined;
220 }
221 this._state++;
222 }
223 addItemFirst(item) {
224 // First time Insert
225 if (!this._head && !this._tail) {
226 this._tail = item;
227 }
228 else if (!this._head) {
229 throw new Error('Invalid list');
230 }
231 else {
232 item.next = this._head;
233 this._head.previous = item;
234 }
235 this._head = item;
236 this._state++;
237 }
238 addItemLast(item) {
239 // First time Insert
240 if (!this._head && !this._tail) {
241 this._head = item;
242 }
243 else if (!this._tail) {
244 throw new Error('Invalid list');
245 }
246 else {
247 item.previous = this._tail;
248 this._tail.next = item;
249 }
250 this._tail = item;
251 this._state++;
252 }
253 removeItem(item) {
254 if (item === this._head && item === this._tail) {
255 this._head = undefined;
256 this._tail = undefined;
257 }
258 else if (item === this._head) {
259 // This can only happened if size === 1 which is handle
260 // by the case above.
261 if (!item.next) {
262 throw new Error('Invalid list');
263 }
264 item.next.previous = undefined;
265 this._head = item.next;
266 }
267 else if (item === this._tail) {
268 // This can only happened if size === 1 which is handle
269 // by the case above.
270 if (!item.previous) {
271 throw new Error('Invalid list');
272 }
273 item.previous.next = undefined;
274 this._tail = item.previous;
275 }
276 else {
277 const next = item.next;
278 const previous = item.previous;
279 if (!next || !previous) {
280 throw new Error('Invalid list');
281 }
282 next.previous = previous;
283 previous.next = next;
284 }
285 item.next = undefined;
286 item.previous = undefined;
287 this._state++;
288 }
289 touch(item, touch) {
290 if (!this._head || !this._tail) {
291 throw new Error('Invalid list');
292 }
293 if ((touch !== Touch.First && touch !== Touch.Last)) {
294 return;
295 }
296 if (touch === Touch.First) {
297 if (item === this._head) {
298 return;
299 }
300 const next = item.next;
301 const previous = item.previous;
302 // Unlink the item
303 if (item === this._tail) {
304 // previous must be defined since item was not head but is tail
305 // So there are more than on item in the map
306 previous.next = undefined;
307 this._tail = previous;
308 }
309 else {
310 // Both next and previous are not undefined since item was neither head nor tail.
311 next.previous = previous;
312 previous.next = next;
313 }
314 // Insert the node at head
315 item.previous = undefined;
316 item.next = this._head;
317 this._head.previous = item;
318 this._head = item;
319 this._state++;
320 }
321 else if (touch === Touch.Last) {
322 if (item === this._tail) {
323 return;
324 }
325 const next = item.next;
326 const previous = item.previous;
327 // Unlink the item.
328 if (item === this._head) {
329 // next must be defined since item was not tail but is head
330 // So there are more than on item in the map
331 next.previous = undefined;
332 this._head = next;
333 }
334 else {
335 // Both next and previous are not undefined since item was neither head nor tail.
336 next.previous = previous;
337 previous.next = next;
338 }
339 item.next = undefined;
340 item.previous = this._tail;
341 this._tail.next = item;
342 this._tail = item;
343 this._state++;
344 }
345 }
346 toJSON() {
347 const data = [];
348 this.forEach((value, key) => {
349 data.push([key, value]);
350 });
351 return data;
352 }
353 fromJSON(data) {
354 this.clear();
355 for (const [key, value] of data) {
356 this.set(key, value);
357 }
358 }
359}
360exports.LinkedMap = LinkedMap;
361class LRUCache extends LinkedMap {
362 constructor(limit, ratio = 1) {
363 super();
364 this._limit = limit;
365 this._ratio = Math.min(Math.max(0, ratio), 1);
366 }
367 get limit() {
368 return this._limit;
369 }
370 set limit(limit) {
371 this._limit = limit;
372 this.checkTrim();
373 }
374 get ratio() {
375 return this._ratio;
376 }
377 set ratio(ratio) {
378 this._ratio = Math.min(Math.max(0, ratio), 1);
379 this.checkTrim();
380 }
381 get(key, touch = Touch.AsNew) {
382 return super.get(key, touch);
383 }
384 peek(key) {
385 return super.get(key, Touch.None);
386 }
387 set(key, value) {
388 super.set(key, value, Touch.Last);
389 this.checkTrim();
390 return this;
391 }
392 checkTrim() {
393 if (this.size > this._limit) {
394 this.trimOld(Math.round(this._limit * this._ratio));
395 }
396 }
397}
398exports.LRUCache = LRUCache;