Key Concepts

  1. Cache Operations:
    • Get: Retrieve an item from the cache. If the item is present, it should be marked as recently used.
    • Put: Add a new item to the cache. If the cache is full, remove the least recently used item.
  2. Data Structures:
    • A Doubly Linked List is used to keep track of the order of usage. The most recently used item will be at the front, and the least recently used item will be at the back.
    • A Hash Map is used to store key-value pairs for quick access.

Implementation Plan

  1. Node Structure: Create a structure for the nodes in the doubly linked list.
  2. LRU Cache Class: Create a class that encapsulates the cache logic, using a hash map and a doubly linked list.
  3. Functions:
    • get(key): Retrieve the value for a given key.
    • put(key, value): Add a key-value pair to the cache.
    • Helper functions to add and remove nodes from the linked list.

C Program for LRU Cache

#include <stdio.h>
#include <stdlib.h>

typedef struct Node {
    int key;
    int value;
    struct Node* prev;
    struct Node* next;
} Node;

typedef struct LRUCache {
    int capacity;
    Node* head; // Most recently used
    Node* tail; // Least recently used
    Node** map; // Hash map for fast access
} LRUCache;

// Create a new node
Node* createNode(int key, int value) {
    Node* newNode = (Node*)malloc(sizeof(Node));
    newNode->key = key;
    newNode->value = value;
    newNode->prev = NULL;
    newNode->next = NULL;
    return newNode;
}

// Initialize the LRU Cache
LRUCache* lruCreate(int capacity) {
    LRUCache* cache = (LRUCache*)malloc(sizeof(LRUCache));
    cache->capacity = capacity;
    cache->head = NULL;
    cache->tail = NULL;
    cache->map = (Node**)calloc(capacity, sizeof(Node*));
    return cache;
}

// Move a node to the head (most recently used)
void moveToHead(LRUCache* cache, Node* node) {
    if (cache->head == node) return; // Already at head

    // Remove node from current position
    if (node->prev) node->prev->next = node->next;
    if (node->next) node->next->prev = node->prev;

    // Update tail if needed
    if (cache->tail == node) {
        cache->tail = node->prev;
        if (cache->tail) cache->tail->next = NULL;
    }

    // Move to head
    node->next = cache->head;
    node->prev = NULL;
    if (cache->head) cache->head->prev = node;
    cache->head = node;

    // If tail is NULL, set it to head
    if (!cache->tail) {
        cache->tail = node;
    }
}

// Remove the tail (least recently used)
Node* removeTail(LRUCache* cache) {
    if (!cache->tail) return NULL;
    Node* node = cache->tail;
    if (cache->tail->prev) {
        cache->tail = cache->tail->prev;
        cache->tail->next = NULL;
    } else {
        cache->head = NULL;
        cache->tail = NULL;
    }
    return node;
}

// Get value from the cache
int lruGet(LRUCache* cache, int key) {
    int index = key % cache->capacity; // Simple hash function
    Node* node = cache->map[index];

    if (!node) return -1; // Not found

    // Move accessed node to head
    moveToHead(cache, node);
    return node->value;
}

// Put a value into the cache
void lruPut(LRUCache* cache, int key, int value) {
    int index = key % cache->capacity;
    Node* node = cache->map[index];

    if (node) {
        // Key exists, update the value and move to head
        node->value = value;
        moveToHead(cache, node);
    } else {
        // Key doesn't exist
        Node* newNode = createNode(key, value);
        if (cache->head == NULL) {
            cache->head = newNode;
            cache->tail = newNode;
        } else {
            moveToHead(cache, newNode);
        }

        cache->map[index] = newNode; // Add to hash map

        // Check capacity
        if (!cache->map[index]) {
            Node* removed = removeTail(cache);
            if (removed) {
                cache->map[removed->key % cache->capacity] = NULL;
                free(removed);
            }
        }
    }
}

// Free the cache
void lruFree(LRUCache* cache) {
    Node* current = cache->head;
    while (current) {
        Node* next = current->next;
        free(current);
        current = next;
    }
    free(cache->map);
    free(cache);
}

// Main function to test LRU Cache
int main() {
    LRUCache* cache = lruCreate(3); // Create LRU cache with capacity 3

    lruPut(cache, 1, 1); // Cache is {1=1}
    lruPut(cache, 2, 2); // Cache is {1=1, 2=2}
    printf("Get 1: %d\n", lruGet(cache, 1)); // Returns 1, Cache is {2=2, 1=1}
    lruPut(cache, 3, 3); // Cache is {2=2, 1=1, 3=3}
    lruPut(cache, 4, 4); // Evicts key 2, Cache is {1=1, 3=3, 4=4}
    printf("Get 2: %d\n", lruGet(cache, 2)); // Returns -1 (not found)
    lruPut(cache, 5, 5); // Evicts key 1, Cache is {3=3, 4=4, 5=5}

    printf("Get 1: %d\n", lruGet(cache, 1)); // Returns -1 (not found)
    printf("Get 3: %d\n", lruGet(cache, 3)); // Returns 3
    printf("Get 4: %d\n", lruGet(cache, 4)); // Returns 4
    printf("Get 5: %d\n", lruGet(cache, 5)); // Returns 5

    lruFree(cache); // Clean up the cache
    return 0;
}

Explanation of the Code

  1. Node Structure: Each node contains a key-value pair along with pointers to the previous and next nodes, allowing for efficient insertion and deletion in a doubly linked list.
  2. Cache Structure: The LRUCache struct contains the capacity, pointers to the head and tail of the doubly linked list, and an array of pointers (map) for fast access.
  3. Basic Operations:
    • Creating a Node: The createNode function initializes a new node.
    • Cache Initialization: The lruCreate function initializes the cache structure.
    • Move to Head: The moveToHead function rearranges the linked list to mark a node as recently used.
    • Remove Tail: The removeTail function evicts the least recently used node from the cache.
    • Get Operation: The lruGet function retrieves a value and updates the usage order.
    • Put Operation: The lruPut function adds or updates an entry in the cache, evicting the least recently used entry if needed.
  4. Memory Management: The lruFree function deallocates memory used by the cache.
  5. Main Function: Demonstrates how to use the cache by adding and retrieving items.

Input and Output Example

Input

The operations performed in the main function can be considered the input to the cache:

 codelruPut(cache, 1, 1); // Cache is {1=1}
lruPut(cache, 2, 2); // Cache is {1=1, 2=2}
printf("Get 1: %d\n", lruGet(cache, 1)); // Returns 1
lruPut(cache, 3, 3); // Cache is {1=1, 2=2, 3=3}
lruPut(cache, 4, 4); // Evicts key 2
printf("Get 2: %d\n", lruGet(cache, 2)); // Returns -1

Output

codeGet 1: 1
Get 2: -1
Get 1: -1
Get 3: 3
Get 4: 4
Get 5: 5

Explanation of the Output

The output shows the results of the get operations, demonstrating that when an item is accessed, it moves to the front of the cache, and when the cache exceeds its capacity, the least recently used item is removed.

This code should compile and run without issues, providing a working implementation of an LRU Cache. If you have further requirements or modifications, feel free to ask!

Leave a Reply

Your email address will not be published. Required fields are marked *

Verified by MonsterInsights