reselect icon indicating copy to clipboard operation
reselect copied to clipboard

Why the LRUCache implementation is using Array over the Doubly Linked List with Map?

Open kuldeepsingh-d11 opened this issue 1 year ago • 8 comments

Is there any specific reason for using Array instead of Doubly Linked List implementation. Right now the time complexities for get function is O(n), and not sure about the set function complexity as unshift functions complexity can be O(n) or O(1) depending on the JS engine.

kuldeepsingh-d11 avatar Feb 18 '24 19:02 kuldeepsingh-d11

Because it was copied from another package, relatively simple code, and relatively small bundle size.

markerikson avatar Feb 18 '24 19:02 markerikson

but the Doubly linked List code is not very large, and it is also simple code

class LRUCache {
    constructor(capacity) {
        this.capacity = capacity;
        this.cache = new Map();
        this.head = { key: null, value: null, prev: null, next: null };
        this.tail = { key: null, value: null, prev: this.head, next: null };
        this.head.next = this.tail;
    }
    get(key) {
        if (!this.cache.has(key)) return -1;

        const node = this.cache.get(key);
        this.moveToHead(node);
        return node.value;
    }

    put(key, value) {
        if (this.cache.has(key)) {
            const node = this.cache.get(key);
            node.value = value;
            this.moveToHead(node);
        } else {
            const newNode = { key, value, prev: this.head, next: this.head.next };
            this.head.next.prev = newNode;
            this.head.next = newNode;
            this.cache.set(key, newNode);

            if (this.cache.size > this.capacity) {
                const tailKey = this.removeTail();
                this.cache.delete(tailKey);
            }
        }
    }

    moveToHead(node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
        node.prev = this.head;
        node.next = this.head.next;
        this.head.next.prev = node;
        this.head.next = node;
    }

    removeTail() {
        const key = this.tail.prev.key;
        this.tail.prev.prev.next = this.tail;
        this.tail.prev = this.tail.prev.prev;
        return key;
    }
}

kuldeepsingh-d11 avatar Feb 18 '24 19:02 kuldeepsingh-d11

Reworking this isn't a priority right now, but if you'd like to submit that as a PR we can take a look.

Note that one issue with your implementation is that it uses a Map and expects a single key, whereas our LRU implementation has an entire set of arguments and does equality checks on that.

markerikson avatar Feb 18 '24 19:02 markerikson

@markerikson The LRU implementation with a doubly linked list and hashmap can not be achieved, As the library provides a custom equality check comparator, hashmap works with an exact value match (reference/value).

I tried but 2 tests are failing because of it. https://github.com/reduxjs/reselect/pull/701

@markerikson @aryaemami59, Do you have any idea, if we can achieve this?

kuldeepsinghborana avatar Feb 19 '24 18:02 kuldeepsinghborana

@kuldeepsinghborana I can take a look, just out of curiosity are you trying to modify the exisiting implementation for lruMemoize or are you trying to create a new standalone mamoize function?

aryaemami59 avatar Feb 19 '24 18:02 aryaemami59

@aryaemami59 I am trying to modify the existing implementation

kuldeepsinghborana avatar Feb 19 '24 18:02 kuldeepsinghborana

@kuldeepsinghborana Ok I'll take a look.

aryaemami59 avatar Feb 19 '24 19:02 aryaemami59

fwiw if it's not possible to replicate the existing behaviour with a linked list, I'm sure we'd be open to a new memoiser instead

EskiMojo14 avatar Feb 19 '24 19:02 EskiMojo14