Skip to content

Commit 70a128d

Browse files
MeekValueMap getOrInsert and getOrInsertComputed
1 parent f093a82 commit 70a128d

2 files changed

Lines changed: 108 additions & 34 deletions

File tree

valuemap.test.ts

Lines changed: 53 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,48 @@ Deno.test('MeekValueMap: get', () => {
9494
assert(pairs);
9595
});
9696

97+
Deno.test('MeekValueMap: getOrInsert', () => {
98+
const pairs: readonly [number, { i: number }][] = new Array(100).fill(0)
99+
.map((_, i) => [i, { i }]);
100+
const map = new MeekValueMap();
101+
for (let i = 0; i < pairs.length; i++) {
102+
const [k, v] = pairs[i];
103+
assertStrictEquals(map.has(k), false);
104+
assertStrictEquals(map.getOrInsert(k, v), v);
105+
assertStrictEquals(map.has(k), true);
106+
assertStrictEquals(map.get(k), v);
107+
const o = { i: -i };
108+
assertStrictEquals(map.getOrInsert(k, o), v);
109+
assertStrictEquals(map.get(k), v);
110+
}
111+
assert(pairs);
112+
});
113+
114+
Deno.test('MeekValueMap: getOrInsertComputed', () => {
115+
const uncalled = () => {
116+
throw new Error('Should not be called');
117+
};
118+
const pairs: readonly [number, { i: number }][] = new Array(100).fill(0)
119+
.map((_, i) => [i, { i }]);
120+
const map = new MeekValueMap();
121+
for (let i = 0; i < pairs.length; i++) {
122+
const [k, v] = pairs[i];
123+
assertStrictEquals(map.has(k), false);
124+
assertStrictEquals(
125+
map.getOrInsertComputed(k, (key) => {
126+
assertStrictEquals(key, k);
127+
return v;
128+
}),
129+
v,
130+
);
131+
assertStrictEquals(map.has(k), true);
132+
assertStrictEquals(map.get(k), v);
133+
assertStrictEquals(map.getOrInsertComputed(k, uncalled), v);
134+
assertStrictEquals(map.get(k), v);
135+
}
136+
assert(pairs);
137+
});
138+
97139
Deno.test('MeekValueMap: set', () => {
98140
const pairs: readonly [number, { i: number }][] = new Array(100).fill(0)
99141
.map((_, i) => [i, { i }]);
@@ -242,36 +284,22 @@ Deno.test('MeekValueMap: Symbol.toStringTag', () => {
242284
);
243285
});
244286

245-
Deno.test('MeekValueMap: GC', async () => {
287+
Deno.test('MeekValueMap: GC set', async () => {
246288
await forceGC((map, key, value) => {
247289
map.set(key, value);
248290
});
249291
});
250292

251-
Deno.test('MeekValueMap: modify while iterating', () => {
252-
const pairs: readonly [number, { i: number }][] = new Array(100).fill(0)
253-
.map((_, i) => [i, { i }]);
254-
const mapExpt = new Map(pairs.slice(0, 60));
255-
const mapTest = new MeekValueMap(pairs.slice(0, 60));
256-
const readWhileModify = (map: Map<number, WeakKey> | MeekValueMap) => {
257-
const r: number[] = [];
258-
let i = 0;
259-
for (const [k] of map) {
260-
r.push(k);
261-
map.delete(pairs[k + 1][0]);
262-
if (i++ === 10) {
263-
map.clear();
264-
for (const [k, v] of pairs.slice(50)) {
265-
map.set(k, v);
266-
}
267-
}
268-
}
269-
return r;
270-
};
271-
const valExpt = readWhileModify(mapExpt);
272-
const valTest = readWhileModify(mapTest);
273-
assertEquals(valExpt, valTest);
274-
assert(pairs);
293+
Deno.test('MeekValueMap: GC getOrInsert', async () => {
294+
await forceGC((map, key, value) => {
295+
map.getOrInsert(key, value);
296+
});
297+
});
298+
299+
Deno.test('MeekValueMap: GC getOrInsertComputed', async () => {
300+
await forceGC((map, key, value) => {
301+
map.getOrInsertComputed(key, () => value);
302+
});
275303
});
276304

277305
/*

valuemap.ts

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ interface Pri<K = any, V extends WeakKey = WeakKey> {
2424

2525
const pri = new WeakMap<MeekValueMap, Pri>();
2626

27+
const unregister = (
28+
fr: FinalizationRegistry<unknown>,
29+
wr?: WeakRef<any>,
30+
): unknown => wr && fr.unregister(wr);
31+
2732
/**
2833
* Like WeakValueMap.
2934
*
@@ -45,11 +50,8 @@ export class MeekValueMap<K = any, V extends WeakKey = WeakKey> {
4550
const kwv = new Map<K, WeakRef<V>>();
4651
const fr = new FinalizationRegistry(kwv.delete.bind(kwv));
4752
for (const [key, value] of iterable ?? []) {
48-
let ref = kwv.get(key);
49-
if (ref) {
50-
fr.unregister(ref);
51-
}
52-
ref = new WeakRef(value);
53+
unregister(fr, kwv.get(key));
54+
const ref = new WeakRef(value);
5355
fr.register(value, key, ref);
5456
kwv.set(key, ref);
5557
}
@@ -139,6 +141,53 @@ export class MeekValueMap<K = any, V extends WeakKey = WeakKey> {
139141
return (pri.get(this) as Pri<K, V>).kwv.get(key)?.deref();
140142
}
141143

144+
/**
145+
* Get the value for a key from this map or insert the default value.
146+
*
147+
* @param key Key to get.
148+
* @param defaultValue Default value.
149+
* @returns Value for the key.
150+
*/
151+
public getOrInsert(key: K, defaultValue: V): V {
152+
const { fr, kwv } = pri.get(this) as Pri<K, V>;
153+
const old = kwv.get(key);
154+
if (old) {
155+
const r = old.deref();
156+
if (r) {
157+
return r;
158+
}
159+
}
160+
const ref = new WeakRef(defaultValue);
161+
unregister(fr, old);
162+
fr.register(defaultValue, key, ref);
163+
kwv.set(key, ref);
164+
return defaultValue;
165+
}
166+
167+
/**
168+
* Get the value for a key from this map or insert the default value.
169+
*
170+
* @param key Key to get.
171+
* @param callback Compute the default value.
172+
* @returns Value for the key.
173+
*/
174+
public getOrInsertComputed(key: K, callback: (key: K) => V): V {
175+
const { fr, kwv } = pri.get(this) as Pri<K, V>;
176+
const old = kwv.get(key);
177+
if (old) {
178+
const r = old.deref();
179+
if (r) {
180+
return r;
181+
}
182+
}
183+
const value = callback(key);
184+
const ref = new WeakRef(value);
185+
unregister(fr, old);
186+
fr.register(value, key, ref);
187+
kwv.set(key, ref);
188+
return value;
189+
}
190+
142191
/**
143192
* Has a key in this map.
144193
*
@@ -172,10 +221,7 @@ export class MeekValueMap<K = any, V extends WeakKey = WeakKey> {
172221
public set(key: K, value: V): this {
173222
const { fr, kwv } = pri.get(this) as Pri<K, V>;
174223
const ref = new WeakRef(value);
175-
const old = kwv.get(key);
176-
if (old) {
177-
fr.unregister(old);
178-
}
224+
unregister(fr, kwv.get(key));
179225
fr.register(value, key, ref);
180226
kwv.set(key, ref);
181227
return this;

0 commit comments

Comments
 (0)