Skip to content

Instantly share code, notes, and snippets.

@pastelmind
Last active August 3, 2020 00:41
Show Gist options
  • Save pastelmind/d0a824ead6425b0a1efa11e00b154a04 to your computer and use it in GitHub Desktop.
Save pastelmind/d0a824ead6425b0a1efa11e00b154a04 to your computer and use it in GitHub Desktop.
IndexedCollection implementation in TypeScript/JavaScript
/**
* @license MIT
*
* MIT License
*
* Copyright (c) 2020 Yehyoung Kang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
// JavaScript version of IndexedCollection with JSDoc comments
/**
* Collection of objects of type `T`, indexed by the _value_ of the key `K`.
* Each item must have a unique value in `T[K]`.
* @template {object} T
* @template {keyof T} K
*/
export class IndexedCollection {
/**
* @param {K} indexKey Index key to use
* @param {T[]} items Objects to store
*/
constructor(indexKey, items) {
/** @private @readonly */
this.indexKey_ = indexKey;
/**
* @type {Map<T[K], T>}
* @private @readonly
*/
this.items_ = new Map();
for (const it of items) {
const index = it[indexKey];
if (this.items_.has(index)) {
const keyName = String(indexKey);
const typeName = it.constructor?.name ?? "entry";
const rawValue = JSON.stringify(index);
throw new Error(
`Another ${typeName} already has '${keyName}' of ${rawValue}`
);
}
this.items_.set(index, it);
}
}
/**
* Retrieves an object by its index value in `T[K]`.
* @param {T[K]} index
* @returns {T} Value if found
*/
get(index) {
const value = this.items_.get(index);
if (value !== undefined) {
return value;
}
const keyName = String(this.indexKey_);
const rawValue = JSON.stringify(index);
throw new Error(`Cannot find entry with '${keyName}' of ${rawValue}`);
}
}
/**
* @license MIT
*
* MIT License
*
* Copyright (c) 2020 Yehyoung Kang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/**
* Collection of objects of type `T`, indexed by the _value_ of the key `K`.
* Each item must have a unique value in `T[K]`.
*/
export class IndexedCollection<T extends object, K extends keyof T> {
private readonly items_: Map<T[K], T>;
private readonly indexKey_: K;
/**
* @param indexKey Index key to use
* @param items Objects to store
*/
constructor(indexKey: K, items: T[]) {
this.indexKey_ = indexKey;
this.items_ = new Map();
for (const it of items) {
const index = it[indexKey];
if (this.items_.has(index)) {
const keyName = String(indexKey);
const typeName = it.constructor?.name ?? "entry";
const rawValue = JSON.stringify(index);
throw new Error(
`Another ${typeName} already has '${keyName}' of ${rawValue}`
);
}
this.items_.set(index, it);
}
}
/**
* Retrieves an object by its index value in `T[K]`.
* @param index
* @returns Value if found
*/
get(index: T[K]): T {
const value = this.items_.get(index);
if (value !== undefined) {
return value;
}
const keyName = String(this.indexKey_);
const rawValue = JSON.stringify(index);
throw new Error(`Cannot find entry with '${keyName}' of ${rawValue}`);
}
}
import { IndexedCollection } from "./indexed-collection";
// Class with properties that can be used as index keys
class Dog {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
// Pass the index key as the first argument to the constructor, and an array of
// objects as the second argument
let dogs = new IndexedCollection("name", [
new Dog("bella", 10),
new Dog("buddy", 5),
]);
let myDog = dogs.get("bella");
// -> Dog("bella")
let yourDog = dogs.get("maximilian");
// -> Error: Cannot find entry with 'name' of "maximilian"
// (Note: IndexedCollection.get() can be modified to return undefined instead of
// throwing an error here)
// Attempting to use a index key ("age") with duplicate values will throw an
// error
let dogs2 = new IndexedCollection("age", [
new Dog("bella", 5), // Duplicate age!
new Dog("buddy", 10),
new Dog("max", 5), // Duplicate age!
]);
// -> Error: Another Dog already has 'age' of 5
// Since "breed" is not a key of Dog, TypeScript rejects this code.
// @ts-expect-error ts(2345): Argument of type '"breed"' is not assignable to parameter of type '"name" | "age"'.
let dogs3 = new IndexedCollection("breed", [
new Dog("bella", 5),
new Dog("buddy", 10),
]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment