Skip to content

Instantly share code, notes, and snippets.

@williamyang98
Created May 18, 2025 06:46
Show Gist options
  • Save williamyang98/e7592a8ddc5cd7472973c8ddf5c57609 to your computer and use it in GitHub Desktop.
Save williamyang98/e7592a8ddc5cd7472973c8ddf5c57609 to your computer and use it in GitHub Desktop.
Indexed list in typescript
export class IndexedList<K extends string | number | symbol, V> {
_index: Partial<Record<K, V>>;
_list: V[];
_get_key: (v: V) => K;
static make_with_id_field<K extends string | number | symbol, V extends { id: K }>() {
return new IndexedList((v: V) => v.id);
}
constructor(get_key: (v: V) => K) {
this._index = {};
this._list = [];
this._get_key = get_key;
}
push(v: V): number {
const k = this._get_key(v);
if (k in this._index) {
throw Error(`Key conflict in indexed list: ${k.toString()}`);
}
const new_length = this._list.push(v);
this._index[k] = v;
return new_length;
}
pop(): V | undefined {
const v = this._list.pop();
if (v === undefined) return undefined;
const k = this._get_key(v);
delete this._index[k]
return v;
}
sort(compare_fn?: (a: V, b: V) => number): readonly V[] {
return this._list.sort(compare_fn);
}
reverse(): readonly V[] {
return this._list.reverse();
}
splice(start: number, delete_count?: number, ...new_values: V[]): V[] {
if (delete_count === undefined || delete_count < 0) {
delete_count = 0;
}
// check for key conflicts
const delete_values_shallow = this._list.slice(start, start+delete_count);
const delete_keys = new Set(delete_values_shallow.map(v => this._get_key(v)));
const pending_add_keys = new Set<K>();
for (const v of new_values) {
const k = this._get_key(v);
if ((k in this._index) && !(k in delete_keys)) {
throw Error(`Attempted to splice existing key ${k.toString()} that isn't going to be removed`);
}
if (k in pending_add_keys) {
throw Error(`Duplicate key in spliced values ${k.toString()}`);
}
pending_add_keys.add(k);
}
// perform splice
const delete_values = this._list.splice(start, delete_count, ...new_values);
for (const v of new_values) {
const k = this._get_key(v);
this._index[k] = v;
}
return delete_values;
}
slice(start?: number, end?: number): readonly V[] {
return this._list.slice(start, end);
}
get_key(v: V): K {
return this._get_key(v);
}
get length(): number {
return this._list.length;
}
get list(): readonly V[] {
return this._list;
}
get record(): Readonly<Partial<Record<K,V>>> {
return this._index;
}
*keys(): Iterator<K> {
for (const v of this._list) {
yield this._get_key(v);
}
}
*values(): Iterator<V> {
for (const v of this._list) {
yield v;
}
}
*entries(): Iterator<[K,V]> {
for (const v of this._list) {
const k = this._get_key(v);
yield [k,v];
}
}
has(k: K): boolean {
return (k in this._index);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment