fastiterationmap
Version:
an ES6 typescript Map that can be iterate like an Array
176 lines (170 loc) • 6.2 kB
text/typescript
export { FastIterationMap, IFastIterationMap };
interface IFastIterationMap<K, V> {
keys: Map<K, number>;
readonly length: number;
readonly size: number;
values: V[];
clear();
delete(key: K): boolean;
get(key: K): V;
has(key: K): boolean;
insertAfter(key: K, value: V, keyRef: K): boolean;
insertBefore(key: K, value: V, keyRef: K): boolean;
swap(key1: K, key2: K): boolean;
// Provide for reading keys only, should not be modified outside the class
push(key: K, value: V);
set(key: K, value: V);
}
class FastIterationMap<K, V> implements IFastIterationMap<K, V> {
// _keys store the index of the element which is stored in the _values array
// keys are not in the same order as values, therefore it shouldn't be use outside the class
protected _keys: Map<K, number>;
protected _values: V[];
constructor() {
this._keys = new Map<K, number>();
this._values = [];
}
public clear(): void {
this._keys.clear();
this._values = [];
}
public delete(key: K): boolean {
const i = this._keys.get(key);
const r = this._keys.delete(key);
this.offsetIndexInKeys(i, -1);
const r2 = this._values.splice(i, 1);
if (r2.length > 0 && r) {
return true;
} else {
return false;
}
}
public get(key: K): V {
return this._values[this._keys.get(key)];
}
/**
* Return the index of an element in the value array
* @param key
*/
public getIndex(key: K): number {
return this._keys.get(key);
}
public has(key: K): boolean {
return this._keys.has(key);
}
/**
* Insert an item after another item
* @param key the key of the item to insert
* @param value the value of the item
* @param keyRef the key of the item to insert after
*/
public insertAfter(key: K, value: V, keyRef: K): boolean {
if (this._keys.get(key) !== undefined) { return false; }
const i = this._keys.get(keyRef);
if (i === undefined) { return false; }
this.insertValue(i + 1, value);
this.offsetIndexInKeys(i, 1);
this._keys.set(key, i + 1);
return true;
}
/**
* Insert 2 items around the another item
* @param keyRef the key of the item insert around
* @param firstK the key of the item to insert before
* @param firstV the value of the item to insert before
* @param secondK the key of the item to insert after
* @param secondV the value of the item to insert after
*/
public insertAround(keyRef: K, firstK: K, firstV: V, secondK: K, secondV: V): boolean {
if (this._keys.get(firstK) !== undefined || this._keys.get(secondK) !== undefined) { return false; }
const index = this._keys.get(keyRef);
if (index === undefined) { return false; }
// insert the 2 items after the item of reference
// offset index by 2 in the keys map of all element after the index of reference
// in the keys map set index of the 2 new items
// finally swap the item of reference with the first of the 2 items inserted
this.insertValue(index + 1, firstV, secondV);
this.offsetIndexInKeys(index, 2);
this._keys.set(firstK, index + 1);
this._keys.set(secondK, index + 2);
return this.swap(keyRef, firstK);
}
/**
* Insert an item before another item
* @param key the key of the item to insert
* @param value the value of the item
* @param keyRef the key of the item to insert before
*/
public insertBefore(key: K, value: V, keyRef: K): boolean {
if (this._keys.get(key) !== undefined) { return false; }
const i = this._keys.get(keyRef);
if (i === undefined) { return false; }
this.insertValue(i, value);
this.offsetIndexInKeys(i - 1, 1);
this._keys.set(key, i);
return true;
}
get keys(): Map<K, number> {
return this._keys;
}
get length(): number {
return this._values.length;
}
get size(): number {
return this._values.length;
}
get values(): V[] {
return this._values;
}
public push(key: K, value: V): number {
const e = this._keys.get(key);
// if the key doesn't exist add the element
if (e === undefined) {
const l = this._values.push(value);
this._keys.set(key, l - 1);
} else {
// if the key is already there, update the value
this._values[e] = value;
}
return this._values.length;
}
public set(key: K, value: V): number {
return this.push(key, value);
}
/**
* Swap position of 2 items in the values array and set the correct index in the keys Map
* @param key1
* @param key2
*/
public swap(key1: K, key2: K): boolean {
const index1 = this._keys.get(key1);
const index2 = this._keys.get(key2);
if (index1 === undefined || index2 === undefined) {return false; }
const tmp = this._values[index1];
this._values[index1] = this._values[index2];
this._values[index2] = tmp;
this._keys.set(key1, index2);
this._keys.set(key2, index1);
return true;
}
protected insertValue(index: number, ...values: V[]): V[] {
return this._values.splice(index, 0, ...values);
}
/**
* Offset indices in the keys Map from a position ([from] and [to] not included)
* @param from offset after this key
* @param offsetVal the amount to offset indices
* @param to if specidied offset until this key, otherwise offset to end of the collection
*/
protected offsetIndexInKeys(from: number, offsetVal: number, to?: number): void {
const mapIter = this._keys.entries();
const l = this._keys.size;
to = to || Number.MAX_VALUE;
for (let i = 0; i < l; ++i) {
const e = mapIter.next().value;
if (e[1] > from && e[1] < to) {
this._keys.set(e[0], e[1] += offsetVal);
}
}
}
}