bigbigmapset
Version:
Extension classes of Map and Set that don't have size limits.
275 lines (274 loc) • 7.78 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BigSet = exports.BigMap = void 0;
/**
* Extension of {@link Map} with no size limit.
*/
class BigMap extends Map {
/** Where the entries are actually stored. The base {@link Map} this {@link BigMap} extends is left empty. */
chunks = [];
get [Symbol.toStringTag]() {
return "BigMap";
}
clear() {
this.chunks.length = 0;
}
constructor(entries) {
super();
if (entries instanceof BigMap) {
// clone
for (const chunk of entries.chunks) {
this.chunks.push(new Map(chunk));
}
}
else if (entries instanceof Map) {
// clone
this.chunks.push(new Map(entries));
}
else if (entries !== undefined) {
for (const [key, value] of entries) {
this.set(key, value);
// TODO check performance and optimize?
}
}
}
get size() {
// TODO? cache
let result = 0;
for (const chunk of this.chunks) {
result += chunk.size;
}
return result;
}
get(key) {
// TODO check performance difference between has and !== undefined
for (const chunk of this.chunks) {
if (chunk.has(key)) {
return chunk.get(key);
}
}
return undefined;
}
has(key) {
for (const chunk of this.chunks) {
if (chunk.has(key)) {
return true;
}
}
return false;
}
delete(key) {
for (let i = 0; i < this.chunks.length; i++) {
const chunk = this.chunks[i];
if (chunk.delete(key)) {
// delete chunk if empty
if (chunk.size === 0) {
this.chunks.splice(i, 1);
// deleting from chunks messes up iteration
// but it's fine because the function is exiting now anyway.
}
return true;
}
}
return false;
}
set(key, value) {
if (this.chunks.length > 0) {
const finalIndex = this.chunks.length - 1;
// replace entry if found in any chunk other than the final one
for (let i = 0; i < finalIndex; i++) {
const chunk = this.chunks[i];
if (chunk.has(key)) {
chunk.set(key, value);
return this;
}
}
// try to add the entry to the final chunk
const finalChunk = this.chunks[finalIndex];
try {
finalChunk.set(key, value);
return this;
}
catch (e) {
if (e instanceof RangeError) {
// final chunk is out of space
}
else
throw e;
}
}
// out of space. make new chunk
const newChunk = new Map();
newChunk.set(key, value);
this.chunks.push(newChunk);
return this;
}
*entries() {
for (const chunk of this.chunks) {
yield* chunk.entries();
}
return undefined;
}
*keys() {
for (const chunk of this.chunks) {
yield* chunk.keys();
}
return undefined;
}
*values() {
for (const chunk of this.chunks) {
yield* chunk.values();
}
return undefined;
}
forEach(callbackfn, thisArg) {
if (arguments.length > 1) {
for (const chunk of this.chunks) {
chunk.forEach(callbackfn, thisArg);
}
}
else {
for (const chunk of this.chunks) {
chunk.forEach(callbackfn);
}
}
}
*[Symbol.iterator]() {
for (const chunk of this.chunks) {
yield* chunk;
}
return undefined;
}
}
exports.BigMap = BigMap;
/**
* Extension of {@link Set} with no size limit.
*/
class BigSet extends Set {
/** Where the values are actually stored. The base {@link Set} this {@link BigSet} extends is left empty. */
chunks = [];
get [Symbol.toStringTag]() {
return "BigSet";
}
clear() {
this.chunks.length = 0;
}
constructor(values) {
super();
if (values instanceof BigSet) {
// clone
for (const chunk of values.chunks) {
this.chunks.push(new Set(chunk));
}
}
else if (values instanceof Set) {
// clone
this.chunks.push(new Set(values));
}
else if (values !== undefined) {
for (const value of values) {
this.add(value);
// TODO check performance and optimize?
}
}
}
get size() {
// TODO? cache
let result = 0;
for (const chunk of this.chunks) {
result += chunk.size;
}
return result;
}
has(value) {
for (const chunk of this.chunks) {
if (chunk.has(value)) {
return true;
}
}
return false;
}
delete(value) {
for (let i = 0; i < this.chunks.length; i++) {
const chunk = this.chunks[i];
if (chunk.delete(value)) {
// delete chunk if empty
if (chunk.size === 0) {
this.chunks.splice(i, 1);
// deleting from chunks messes up iteration
// but it's fine because the function is exiting now anyway.
}
return true;
}
}
return false;
}
add(value) {
if (this.chunks.length > 0) {
const finalIndex = this.chunks.length - 1;
// check if found in any chunk other than the final one
for (let i = 0; i < finalIndex; i++) {
const chunk = this.chunks[i];
if (chunk.has(value)) {
chunk.add(value); // just in case
return this;
}
}
// try to add the value to the final chunk
const finalChunk = this.chunks[finalIndex];
try {
finalChunk.add(value);
return this;
}
catch (e) {
if (e instanceof RangeError) {
// final chunk is out of space
}
else
throw e;
}
}
// out of space. make new chunk
const newChunk = new Set();
newChunk.add(value);
this.chunks.push(newChunk);
return this;
}
*entries() {
for (const chunk of this.chunks) {
yield* chunk.entries();
}
return undefined;
}
*keys() {
for (const chunk of this.chunks) {
yield* chunk.keys();
}
return undefined;
}
*values() {
for (const chunk of this.chunks) {
yield* chunk.values();
}
return undefined;
}
forEach(callbackfn, thisArg) {
if (arguments.length > 1) {
for (const chunk of this.chunks) {
chunk.forEach(callbackfn, thisArg);
}
}
else {
for (const chunk of this.chunks) {
chunk.forEach(callbackfn);
}
}
}
*[Symbol.iterator]() {
for (const chunk of this.chunks) {
yield* chunk;
}
return undefined;
}
}
exports.BigSet = BigSet;