import _ from 'lodash'; function getFolderKey(key: KeyElementType[]) { return key.join(','); } type KeyElementType = string | number | boolean | undefined | null; type Entry = { key: KE[]; value: R }; export default class ListIndexedMap { private map: { [folderKey: string]: Entry[] } = {}; public entries(): Entry[] { const ret = []; for (const folderKey of Object.keys(this.map)) { for (const entry of this.map[folderKey]) { ret.push(entry); } } return ret; } public set(value: R, ...key: KE[]): boolean { // returns true if an entry was added, false if entry already present and modified const entry = this.getEntry(key); if (!entry) { this.getFolder(key).push({ key, value }); return true; } else { entry.value = value; return false; } } public get(...key: KE[]): R | undefined { const entry = this.getEntry(key); if (!entry) { return undefined; } else { return entry.value; } } public has(...key: KE[]): boolean { return !!this.getEntry(key); } public static from( objs: T[], key: (t: T) => string[] ): ListIndexedMap { const map = new ListIndexedMap(); for (const o of objs) { map.set(o, ...key(o)); } return map; } private getEntry(key: KE[]): Entry | undefined { return this.getFolder(key).find(entry => _.isEqual(entry.key, key)); } private getFolder(key: KE[]): Entry[] { const folderKey = getFolderKey(key); this.map[folderKey] = this.map[folderKey] || []; return this.map[folderKey]; } } export class ListIndexedSet { private map: ListIndexedMap = new ListIndexedMap< string, boolean >(); public static from(objs: T[], key: (t: T) => string[]): ListIndexedSet { const set = new ListIndexedSet(); for (const o of objs) { set.add(...key(o)); } return set; } public add(...key: string[]) { this.map.set(true, ...key); } public delete(...key: string[]) { this.map.set(false, ...key); } public has(...key: string[]) { return !!this.map.get(...key); } public clear() { this.map = new ListIndexedMap(); } } export class StringListIndexedMap extends ListIndexedMap {} export class ListIndexedMapOfCounts< KE extends KeyElementType > extends ListIndexedMap { public increment(...key: KE[]) { if (this.has(...key)) { this.set(this.get(...key)! + 1, ...key); } else { this.set(1, ...key); } } }