Skip to content

Commit f1260f0

Browse files
committed
docs: add detailed implementation notes for LRU Cache
- Explain hybrid architecture (Dictionary + Doubly Linked List) - Document the "why" behind O(1) time complexity requirements - Detail the space-vs-time trade-off used in the design - Summarize testing strategy for edge cases and eviction logic
1 parent becdd11 commit f1260f0

1 file changed

Lines changed: 45 additions & 0 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Changes Made: LRU Cache Implementation
2+
3+
## 1. Architectural Overview
4+
To achieve the requirement of **O(1) time complexity** for both `get` and `set` operations, I implemented a hybrid data structure combining a **Python Dictionary** and a **Doubly Linked List**.
5+
6+
- **The Dictionary (`self.lookup`)**: Provides constant time O(1) access to any node using its key.
7+
- **The Doubly Linked List (`self.order`)**: Maintains the "recency" of items. The **Head** represents the Most Recently Used (MRU) item, and the **Tail** represents the Least Recently Used (LRU) item.
8+
9+
## 2. Component Breakdown
10+
11+
### Node Class
12+
- **Key-Value Storage**: Unlike a standard linked list node, these nodes store both the `key` and the `value`.
13+
- **The "Why"**: Storing the `key` is essential during eviction. When we remove the tail node from the list, we must also delete its corresponding entry from the dictionary. The node must "know" its key so we can perform this reverse lookup.
14+
15+
### LinkedList Class
16+
- **Pointer Management**: Manages `head` and `tail` pointers.
17+
- **Methods**:
18+
- `push_head(node)`: Places a node at the front (MRU position).
19+
- `pop_tail()`: Removes the oldest node (LRU position) and returns the node object so the Cache can identify which key to delete.
20+
- `remove(node)`: Disconnects a node from its current position. This is used when an existing item is accessed or updated and needs to be moved to the front.
21+
22+
### LruCache Class
23+
- **`get(key)`**:
24+
1. Checks the dictionary for the key.
25+
2. If found, it uses `remove()` and `push_head()` to move the node to the front of the list, marking it as recently used.
26+
- **`set(key, value)`**:
27+
1. **If key exists**: Updates the value and moves the node to the front.
28+
2. **If key is new**:
29+
- Checks if the cache is at its `limit`.
30+
- If full, it calls `pop_tail()` and deletes that node's key from the dictionary.
31+
- Adds the new node to both the dictionary and the front of the list.
32+
33+
## 3. Complexity & Trade-offs
34+
- **Time Complexity**: Every operation (`get` and `set`) is **O(1)** because dictionary lookups and linked list pointer updates do not depend on the size of the cache.
35+
- **Space Complexity**: I traded **Space for Time**. I used extra memory to store:
36+
1. A dictionary entry for every item.
37+
2. Two pointers (`next` and `previous`) for every node.
38+
- **Trade-off Choice**: This extra memory usage is worth the benefit of having a cache that never slows down, even as the limit increases to thousands of items.
39+
40+
## 4. Testing Suite
41+
I expanded the test suite to include several critical edge cases:
42+
- **Update Existing Key**: Verified that re-setting a key updates the value and refreshes its "recency" status.
43+
- **Limit of One**: Confirmed that a cache with a limit of 1 correctly evicts the old item every time a new one is added.
44+
- **Non-existent Keys**: Ensured that the cache gracefully returns `None` for missing keys.
45+
- **Complex Values**: Verified that the cache can store non-primitive types like lists, proving it stores data by reference.

0 commit comments

Comments
 (0)