Maps

Maps

Terms

Load Factor - the amount of data points vs the amount of storage (data.len / storage.capacity)

Key - a value that is consistently hashable and is used to look up data

Value - data associated with a key

Collision - when two or more keys map to the same cell

type map = {key, value}[][]
hash(key) -> number

take that number % capacity to produce index of item

if there is more than one item stored there (from collisions), linear search at that index

LRU Cache

Least recently used items get evicted

Under the hood it's a linked list supported by a hash map for lookup, if you lookup an item it gets moved to the front of the list.

Super handy because it has a fixed capacity.

type Node<T> = {
    value: T;
    next?: Node<T>;
    prev?: Node<T>;
};

class LRUCache<K, V> {
    private length: number;
    private lookup: Map<K, Node<V>>;
    private reverseLookup: Map<Node<V>, K>;
    private head?: Node<V>;
    private tail?: Node<V>;

    constructor(private capacity: number = 10) {
        this.length = 0;
        this.head = this.tail = undefined;
        this.lookup = new Map<K, Node<V>>();
        this.reverseLookup = new Map<Node<V>, K>();
    }

    update(key: K, value: V): void {
        let node = this.lookup.get(key);
        if (!node) {
            node = createNode(value);
            this.length++;
            this.prepend(node);
            this.trimCache();

            this.lookup.set(key, node);
            this.reverseLookup.set(node, key);
        } else {
            this.detach(node);
            this.prepend(node);
            node.value = value;
        }
    }

    get(key: K): V | undefined {
        const node = this.lookup.get(key);
        if (!node) return undefined;

        this.detach(node);
        this.prepend(node);

        return node.value;
    }

    private detach(node: Node<V>): void {
        if (node.prev) {
            node.prev.next = node.next;
        }

        if (node.next) {
            node.next.prev = node.prev;
        }

        if (this.head === node) {
            this.head = this.head.next;
        }

        if (this.tail === node) {
            this.tail = this.tail.prev;
        }

        node.next = undefined;
        node.prev = undefined;
    }

    private prepend(node: Node<V>): void {
        if (!this.head) {
            this.head = this.tail = node;
            return;
        }

        node.next = this.head;
        this.head.prev = node;
    }

    private trimCache(): void {
        if (this.length <= this.capacity) {
            return;
        }

        const tail = this.tail!;
        this.detach(this.tail);

        const key = this.reverseLookup.get(tail) as K;
        this.lookup.delete(key);
        this.reverseLookup.delete(tail);
        this.length--;
    }
}

function createNode<T>(value: T): Node<T> {
    return { value };
}