Design LRU Cache using C++
LRU Cache - C++ Implementation
LRU, or Least Recetly Used, is one of the Page Replacement Algorithms, in which the system manages a given amount of memory - by making decisions what pages to keep in memory, and which ones to remove when the memory is full.
Let’s say, the capacity of a given cache (memory) is C.
Our memory stores key, value pairs in it.
It should support the following operations:
get(key)- Get the value of the given key if it exists in the memory (else, let’s say -1)put(key, value)- Set, or insert the value if not present. If our cache reaches its capacity, we should remove the item which was least recently used.
Another constraint to the given problem is:
Both the operations must be done in constant Time Complexity, ie in O(1).
Both the operations must be done in constant Time Complexity, ie in O(1).
Now, we need to think of some of the data structures, which would allow us to perform the above operations in O(1).
Choice of data structures
- Queue - We should maintain a Queue (double ended queue), in which the most recently used pages (items) are in the front, and the least recently used pages are in the rear. This would allow to remove the least recently used item in O(1) time.
- Doubly Linked List - We should implement our Queue using a doubly linked list (instead of arrays), which would allow us to apply shifting operations in O(1) time. (like, when we need to shift a page to the front of the queue)
- HashMap - We should hash the key values to the location where the page is stored. This would allow
getoperation in O(1) time.
Design and Implementation
Now that we know which what all data structures to use, let’s look at the implementation.
Whenever a user
gets a page, we return its value, and also move that page to the front of our Queue.
Whenever a user
else we add a new page to our cache in the front of the Queue.
But if our cache has reached its capacity, we remove the least recently used page (ie the rear item in our Queue) from our memory.
sets a page, if the page is already present, we update its value and move that page to the front of our Queue,else we add a new page to our cache in the front of the Queue.
But if our cache has reached its capacity, we remove the least recently used page (ie the rear item in our Queue) from our memory.
class Node- Data members:
- key
- value
- next node address
- previous node address
- Data members:
class DoublyLinkedList- Data members:
- front node address
- rear node address
- Member functions:
- move_page_to_head(..)
- remove_rear_page()
- get_rear_page()
- add_page_to_head(..)
- Data members:
class LRU- Data members:
- capacity
- current size
- a DoublyLinkedList object
- Hashmap
- Member functions:
- get(key)
- put(key, value)
- Data members:
Let’s make the 3 classes.
Code
#include <iostream> #include <map> using namespace std; class Node { public: int key, value; Node *prev, *next; Node(int k, int v): key(k), value(v), prev(NULL), next(NULL) {} }; class DoublyLinkedList { Node *front, *rear; bool isEmpty() { return rear == NULL; } public: DoublyLinkedList(): front(NULL), rear(NULL) {} Node* add_page_to_head(int key, int value) { Node *page = new Node(key, value); if(!front && !rear) { front = rear = page; } else { page->next = front; front->prev = page; front = page; } return page; } void move_page_to_head(Node *page) { if(page==front) { return; } if(page == rear) { rear = rear->prev; rear->next = NULL; } else { page->prev->next = page->next; page->next->prev = page->prev; } page->next = front; page->prev = NULL; front->prev = page; front = page; } void remove_rear_page() { if(isEmpty()) { return; } if(front == rear) { delete rear; front = rear = NULL; } else { Node *temp = rear; rear = rear->prev; rear->next = NULL; delete temp; } } Node* get_rear_page() { return rear; } }; class LRUCache{ int capacity, size; DoublyLinkedList *pageList; map<int, Node*> pageMap; public: LRUCache(int capacity) { this->capacity = capacity; size = 0; pageList = new DoublyLinkedList(); pageMap = map<int, Node*>(); } int get(int key) { if(pageMap.find(key)==pageMap.end()) { return -1; } int val = pageMap[key]->value; // move the page to front pageList->move_page_to_head(pageMap[key]); return val; } void put(int key, int value) { if(pageMap.find(key)!=pageMap.end()) { // if key already present, update value and move page to head pageMap[key]->value = value; pageList->move_page_to_head(pageMap[key]); return; } if(size == capacity) { // remove rear page int k = pageList->get_rear_page()->key; pageMap.erase(k); pageList->remove_rear_page(); size--; } // add new page to head to Queue Node *page = pageList->add_page_to_head(key, value); size++; pageMap[key] = page; } ~LRUCache() { map<int, Node*>::iterator i1; for(i1=pageMap.begin();i1!=pageMap.end();i1++) { delete i1->second; } delete pageList; } };
#include <iostream> #include "LRUCache.cpp" using namespace std; int main() { LRUCache cache(2); // cache capacity 2 cache.put(2,2); cout << cache.get(2) << endl; cout << cache.get(1) << endl; cache.put(1,1); cache.put(1,5); cout << cache.get(1) << endl; cout << cache.get(2) << endl; cache.put(8,8); cout << cache.get(1) << endl; cout << cache.get(8) << endl; }
using namespace std;class lru{
list<int> el;//Doubly linked list to store the elements
unordered_map<int, list<int>::iterator> ref;//hashtable for storing the reference address
int n;//hashtable for storing the refernce address
public:
/*Constructor to initialize the capacity of cache*/
lru(int cap){
n=cap;
}
void insert(int);
void print();
};/*add a value to the list if new or move the value to the front if already present*/
void lru::insert(int val){
if(ref.find(val)==ref.end()){// if the value is not found in the list
if(ref.size()==n){//if the cache size is equal to the capacity
int x=el.back();
el.pop_back();//removing the last element from the queue
ref.erase(x); //erasing the key value pair from the hash table
}
}
else{
el.erase(ref.find(val)->second);//if present erasing it
}
el.push_front(val);//adding the value to the front
ref[val]=el.begin();//storing the address in the hashtable
}/*Iterating through the entire list and printing the elements*/
void lru::print(){
for(auto it=el.begin();it!=el.end();it++)
cout<<*it<<" ";
cout<<endl;
}int main(){lru cache(3); //to specify the capacity
/*Refer explanation*/cache.insert(1);//1
cache.insert(2);//2
cache.insert(3);//3
cache.insert(2);//4
cache.insert(4);//5
cache.insert(2);//6
cache.insert(5);//7
cache.print();
}
cache.insert(2);//2
cache.insert(3);//3
cache.insert(2);//4
cache.insert(4);//5
cache.insert(2);//6
cache.insert(5);//7
cache.print();
}
OUTPUT:
5 2 4
Explanation:
1. 1
2. 2, 1
3. 3, 2, 1
4. 2, 3, 1 (Position of 2 is changed)
5. 4, 2, 3 (least recently used 1 is deleted and 4 is inserted)
6. 2, 4, 3 (Position of 2 is changed)
7. 5, 2, 4 (least recently used 3 is deleted and 5 is inserted
Comments
Post a Comment