UNPKG

indexed-collection

Version:

A zero-dependency library of classes that make filtering, sorting and observing changes to arrays easier and more efficient.

135 lines (113 loc) 3.84 kB
import { CollectionNature } from '../core/CollectionNature'; import { ICollectionOption } from '../core/ICollectionOption'; import { KeyExtract } from '../core/KeyExtract'; import { defaultCollectionOption } from '../core/defaultCollectionOption'; import { IInternalList } from '../core/internals/IInternalList'; import { InternalList } from '../core/internals/InternalList'; import { InternalSetList } from '../core/internals/InternalSetList'; const emptyArray = Object.freeze([]); type SetOrArray<T> = Set<T> | T[]; type LeafMap<T, KeyT = unknown> = Map<KeyT, IInternalList<T>>; export abstract class IndexBase<T> { protected readonly _keyFns: readonly KeyExtract<T>[]; protected readonly option: Readonly<ICollectionOption>; public internalMap = new Map(); protected constructor( keyFns: readonly KeyExtract<T>[], option: Readonly<ICollectionOption> = defaultCollectionOption ) { this._keyFns = keyFns; this.option = Object.freeze(Object.assign({}, defaultCollectionOption, option)); } index(item: T): boolean { const keys = this.getKeys(item); const leafMaps = this.getLeafMaps(keys); const lastIndex = keys.length - 1; const lastKeys = keys[lastIndex]; let added = false; const isUsingSet = this.option.nature === CollectionNature.Set; for (const leafMap of leafMaps) { for (const key of lastKeys) { const result = addItemToMap(key, item, leafMap, isUsingSet); added = added || result; } } return added; } unIndex(item: T): boolean { const keys = this.getKeys(item); const leafMaps = this.getLeafMaps(keys); const lastKeys = keys[keys.length - 1]; let removed = false; for (const leafMap of leafMaps) { for (const key of lastKeys) { const result = removeItemFromMap(key, item, leafMap); removed = removed || result; } } return removed; } protected getValueInternal(keys: readonly unknown[]): readonly T[] { const convertedKeys = keys.map(k => [k]); const leafMaps = this.getLeafMaps(convertedKeys); const lastKey = keys[keys.length - 1]; const values = leafMaps[0]?.get(lastKey); if (values == null) { return emptyArray; } return values.output; } protected getKeys(item: T): SetOrArray<unknown>[] { // @ts-ignore return this._keyFns.map(keyFn => { return keyFn.isMultiple ? keyFn(item) : [keyFn(item)]; }); } protected getLeafMaps(keys: SetOrArray<unknown>[]): LeafMap<T>[] { if (keys.length === 1) { return [this.internalMap as LeafMap<T>]; } let currentLevelMap = [this.internalMap]; for (let i = 0; i < keys.length - 1; i++) { const maps = []; for (const key of keys[i]) { for (const map of currentLevelMap) { if (!map.has(key)) { map.set(key, new Map()); } maps.push(map.get(key)); } } currentLevelMap = maps; } return currentLevelMap as LeafMap<T>[]; } reset() { this.internalMap = new Map(); } } function getNewInternalList<T>(isUsingSet: boolean): IInternalList<T> { return isUsingSet ? new InternalSetList(new Set()) : new InternalList([]); } function addItemToMap<T>(key: unknown, item: T, map: LeafMap<T>, isUsingSet: boolean): boolean { if (!map.has(key)) { const content: IInternalList<T> = getNewInternalList(isUsingSet); content.add(item); map.set(key, content); return true; } const items = map.get(key) as IInternalList<T>; if (!items.exists(item)) { items.add(item); return true; } return false; } function removeItemFromMap<T>(key: unknown, item: T, map: LeafMap<T>): boolean { const items = map.get(key) as IInternalList<T>; if (items.exists(item)) { items.remove(item); return true; } return false; }