2021-03-16 23:38:59 +01:00
|
|
|
import * as blueslip from "./blueslip";
|
|
|
|
|
2020-02-03 07:48:50 +01:00
|
|
|
export class LazySet {
|
2021-12-28 19:22:10 +01:00
|
|
|
private data:
|
|
|
|
| {
|
|
|
|
arr: number[];
|
|
|
|
set: undefined;
|
|
|
|
}
|
|
|
|
| {
|
|
|
|
arr: undefined;
|
|
|
|
set: Set<number>;
|
|
|
|
};
|
|
|
|
|
2019-12-27 13:16:22 +01:00
|
|
|
/*
|
|
|
|
This class is optimized for a very
|
|
|
|
particular use case.
|
|
|
|
|
|
|
|
We often have lots of subscribers on
|
|
|
|
a stream. We get an array from the
|
|
|
|
backend, because it's JSON.
|
|
|
|
|
|
|
|
Often the only operation we need
|
|
|
|
on subscribers is to get the length,
|
|
|
|
which is plenty cheap as an array.
|
|
|
|
|
|
|
|
Making an array from a set is cheap
|
|
|
|
for one stream, but it's expensive
|
|
|
|
for all N streams at page load.
|
|
|
|
|
|
|
|
Once somebody does an operation
|
|
|
|
where sets are useful, such
|
2020-02-03 07:41:38 +01:00
|
|
|
as has/add/delete, we convert it over
|
2019-12-27 13:16:22 +01:00
|
|
|
to a set for a one-time cost.
|
|
|
|
*/
|
|
|
|
|
2021-12-28 19:22:10 +01:00
|
|
|
constructor(vals: number[]) {
|
2021-12-26 20:47:19 +01:00
|
|
|
this.data = {
|
|
|
|
arr: vals,
|
|
|
|
set: undefined,
|
|
|
|
};
|
2020-02-03 07:48:50 +01:00
|
|
|
}
|
|
|
|
|
2021-12-28 19:22:10 +01:00
|
|
|
get size(): number {
|
2021-12-28 19:20:15 +01:00
|
|
|
const {data} = this;
|
|
|
|
if (data.set !== undefined) {
|
|
|
|
return data.set.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
return data.arr.length;
|
|
|
|
}
|
|
|
|
|
2021-12-28 19:22:10 +01:00
|
|
|
keys(): IterableIterator<number> {
|
2021-12-26 20:47:19 +01:00
|
|
|
const {data} = this;
|
|
|
|
if (data.set !== undefined) {
|
|
|
|
return data.set.keys();
|
2019-12-27 13:16:22 +01:00
|
|
|
}
|
2021-12-26 20:47:19 +01:00
|
|
|
return data.arr.values();
|
2020-02-03 07:48:50 +01:00
|
|
|
}
|
2019-12-27 13:16:22 +01:00
|
|
|
|
2021-12-28 19:22:10 +01:00
|
|
|
_make_set(): Set<number> {
|
2021-12-26 20:47:19 +01:00
|
|
|
if (this.data.set !== undefined) {
|
2022-01-09 07:59:08 +01:00
|
|
|
return this.data.set;
|
2019-12-27 13:16:22 +01:00
|
|
|
}
|
2021-12-26 20:47:19 +01:00
|
|
|
|
|
|
|
this.data = {
|
|
|
|
arr: undefined,
|
|
|
|
set: new Set(this.data.arr),
|
|
|
|
};
|
2022-01-09 07:59:08 +01:00
|
|
|
|
|
|
|
return this.data.set;
|
2019-12-27 13:16:22 +01:00
|
|
|
}
|
|
|
|
|
2021-12-28 19:22:10 +01:00
|
|
|
map<T>(f: (v: number, k: number) => T): T[] {
|
2023-03-02 01:58:25 +01:00
|
|
|
return [...this.keys()].map((v, k) => f(v, k));
|
2020-02-03 07:48:50 +01:00
|
|
|
}
|
2019-12-27 13:16:22 +01:00
|
|
|
|
2021-12-28 19:22:10 +01:00
|
|
|
has(v: number): boolean {
|
|
|
|
const set = this._make_set();
|
2020-02-03 07:48:50 +01:00
|
|
|
const val = this._clean(v);
|
2021-12-28 19:22:10 +01:00
|
|
|
return set.has(val);
|
2020-02-03 07:48:50 +01:00
|
|
|
}
|
2019-12-27 13:16:22 +01:00
|
|
|
|
2021-12-28 19:22:10 +01:00
|
|
|
add(v: number): void {
|
2022-01-09 07:59:08 +01:00
|
|
|
const set = this._make_set();
|
2020-02-03 07:48:50 +01:00
|
|
|
const val = this._clean(v);
|
2022-01-09 07:59:08 +01:00
|
|
|
set.add(val);
|
2020-02-03 07:48:50 +01:00
|
|
|
}
|
2019-12-27 13:16:22 +01:00
|
|
|
|
2021-12-28 19:22:10 +01:00
|
|
|
delete(v: number): boolean {
|
2022-01-09 07:59:08 +01:00
|
|
|
const set = this._make_set();
|
2020-02-03 07:48:50 +01:00
|
|
|
const val = this._clean(v);
|
2022-01-09 07:59:08 +01:00
|
|
|
return set.delete(val);
|
2020-02-03 07:48:50 +01:00
|
|
|
}
|
2019-12-27 13:16:22 +01:00
|
|
|
|
2021-12-28 19:22:10 +01:00
|
|
|
_clean(v: number | string): number {
|
2020-07-15 01:29:15 +02:00
|
|
|
if (typeof v !== "number") {
|
2023-04-24 15:57:45 +02:00
|
|
|
blueslip.error("not a number", {v});
|
2020-10-07 09:17:30 +02:00
|
|
|
return Number.parseInt(v, 10);
|
2020-01-14 23:29:54 +01:00
|
|
|
}
|
|
|
|
return v;
|
2020-02-03 07:48:50 +01:00
|
|
|
}
|
|
|
|
}
|