Least Frequently Used (LFU) Cache Implementation
Least Frequently Used (LFU) is a caching algorithm in which the least frequently used cache block is removed whenever the cache is overflowed. In LFU we check the old page as well as the frequency of that page and if the frequency of the page is larger than the old page we cannot remove it and if all the old pages are having same frequency then take last i.e FIFO method for that and remove that page.
Min-heap data structure is a good option to implement this algorithm, as it handles insertion, deletion, and update in logarithmic time complexity. A tie can be resolved by removing the least recently used cache block. The following two containers have been used to solve the problem:
- A vector of integer pairs has been used to represent the cache, where each pair consists of the block number and the number of times it has been used. The vector is ordered in the form of a min-heap, which allows us to access the least frequently used block in constant time.
- A hashmap has been used to store the indices of the cache blocks which allows searching in constant time.
Below is the implementation of the above approach:
C++
// C++ program for LFU cache implementation #include <bits/stdc++.h> using namespace std; // Generic function to swap two pairs void swap(pair< int , int >& a, pair< int , int >& b) { pair< int , int > temp = a; a = b; b = temp; } // Returns the index of the parent node inline int parent( int i) { return (i - 1) / 2; } // Returns the index of the left child node inline int left( int i) { return 2 * i + 1; } // Returns the index of the right child node inline int right( int i) { return 2 * i + 2; } // Self made heap to Rearranges // the nodes in order to maintain the heap property void heapify(vector<pair< int , int > >& v, unordered_map< int , int >& m, int i, int n) { int l = left(i), r = right(i), minim; if (l < n) minim = ((v[i].second < v[l].second) ? i : l); else minim = i; if (r < n) minim = ((v[minim].second < v[r].second) ? minim : r); if (minim != i) { m[v[minim].first] = i; m[v[i].first] = minim; swap(v[minim], v[i]); heapify(v, m, minim, n); } } // Function to Increment the frequency // of a node and rearranges the heap void increment(vector<pair< int , int > >& v, unordered_map< int , int >& m, int i, int n) { ++v[i].second; heapify(v, m, i, n); } // Function to Insert a new node in the heap void insert(vector<pair< int , int > >& v, unordered_map< int , int >& m, int value, int & n) { if (n == v.size()) { m.erase(v[0].first); cout << "Cache block " << v[0].first << " removed.\n" ; v[0] = v[--n]; heapify(v, m, 0, n); } v[n++] = make_pair(value, 1); m.insert(make_pair(value, n - 1)); int i = n - 1; // Insert a node in the heap by swapping elements while (i && v[parent(i)].second > v[i].second) { m[v[i].first] = parent(i); m[v[parent(i)].first] = i; swap(v[i], v[parent(i)]); i = parent(i); } cout << "Cache block " << value << " inserted.\n" ; } // Function to refer to the block value in the cache void refer(vector<pair< int , int > >& cache, unordered_map< int , int >& indices, int value, int & cache_size) { if (indices.find(value) == indices.end()) insert(cache, indices, value, cache_size); else increment(cache, indices, indices[value], cache_size); } // Driver Code int main() { int cache_max_size = 4, cache_size = 0; vector<pair< int , int > > cache(cache_max_size); unordered_map< int , int > indices; refer(cache, indices, 1, cache_size); refer(cache, indices, 2, cache_size); refer(cache, indices, 1, cache_size); refer(cache, indices, 3, cache_size); refer(cache, indices, 2, cache_size); refer(cache, indices, 4, cache_size); refer(cache, indices, 5, cache_size); return 0; } |
Java
import java.util.HashMap; import java.util.Map; import java.util.PriorityQueue; class Pair { int value, frequency; public Pair( int value, int frequency) { this .value = value; this .frequency = frequency; } } class LFU { int cacheSize; Map<Integer, Pair> cache; public LFU( int cacheSize) { this .cacheSize = cacheSize; this .cache = new HashMap<Integer, Pair>(); } // Self made heap to Rearranges // the nodes in order to maintain the heap property public void increment( int value) { if (cache.containsKey(value)) { cache.get(value).frequency += 1 ; } } // Function to Insert a new node in the heap public void insert( int value) { if (cache.size() == cacheSize) { // remove least frequently used int lfuKey = findLFU(); System.out.println( "Cache block " + lfuKey + " removed." ); cache.remove(lfuKey); } Pair newPair = new Pair(value, 1 ); cache.put(value, newPair); System.out.println( "Cache block " + value + " inserted." ); } // Function to refer to the block value in the cache public void refer( int value) { if (!cache.containsKey(value)) { insert(value); } else { increment(value); } } // Function to find the LFU block public int findLFU() { int lfuKey = 0 ; int minFrequency = Integer.MAX_VALUE; for (Map.Entry<Integer, Pair> entry : cache.entrySet()) { if (entry.getValue().frequency < minFrequency) { minFrequency = entry.getValue().frequency; lfuKey = entry.getKey(); } } return lfuKey; } } public class Main { public static void main(String[] args) { LFU lfuCache = new LFU( 4 ); lfuCache.refer( 1 ); lfuCache.refer( 2 ); lfuCache.refer( 1 ); lfuCache.refer( 3 ); lfuCache.refer( 2 ); lfuCache.refer( 4 ); lfuCache.refer( 5 ); } } // This code is contributed by ishankhandelwals. |
Python3
# Python code for LFU cache implementation cache_max_size = 4 cache_size = 0 cache = [ None ] * cache_max_size indices = {} # Generic function to swap two pairs def swap(a, b): temp = a a = b b = temp # Returns the index of the parent node def parent(i): return (i - 1 ) / / 2 # Returns the index of the left child node def left(i): return 2 * i + 1 # Returns the index of the right child node def right(i): return 2 * i + 2 # Self made heap to Rearranges # the nodes in order to maintain the heap property def heapify(v, m, i, n): l = left(i) r = right(i) minim = i if l < n: minim = (i if v[i][ 1 ] < v[l][ 1 ] else l) if r < n: minim = (minim if v[minim][ 1 ] < v[r][ 1 ] else r) if minim ! = i: m[v[minim][ 0 ]] = i m[v[i][ 0 ]] = minim swap(v[minim], v[i]) heapify(v, m, minim, n) # Function to Increment the frequency # of a node and rearranges the heap def increment(v, m, i, n): v[i][ 1 ] + = 1 heapify(v, m, i, n) # Function to Insert a new node in the heap def insert(v, m, value, n): if n = = len (v): del m[v[ 0 ][ 0 ]] print ( "Cache block " + str (v[ 0 ][ 0 ]) + " removed." ) v[ 0 ] = v[n - 1 ] heapify(v, m, 0 , n - 1 ) v[n] = [value, 1 ] m[value] = n i = n # Insert a node in the heap by swapping elements while i and v[parent(i)][ 1 ] > v[i][ 1 ]: m[v[i][ 0 ]] = parent(i) m[v[parent(i)][ 0 ]] = i swap(v[i], v[parent(i)]) i = parent(i) print ( "Cache block " + str (value) + " inserted." ) # Function to refer to the block value in the cache def refer(cache, indices, value, cache_size): if not value in indices: insert(cache, indices, value, cache_size) else : increment(cache, indices, indices[value], cache_size) # Driver Code def main(): refer(cache, indices, 1 , cache_size) refer(cache, indices, 2 , cache_size) refer(cache, indices, 1 , cache_size) refer(cache, indices, 3 , cache_size) refer(cache, indices, 2 , cache_size) refer(cache, indices, 4 , cache_size) refer(cache, indices, 5 , cache_size) return 0 main() # This code is contributed by ishankhandelwals. |
C#
using System; using System.Collections.Generic; class Pair { public int value { get ; set ; } public int frequency { get ; set ; } public Pair( int value, int frequency) { this .value = value; this .frequency = frequency; } } class LFU { public int cacheSize { get ; set ; } public Dictionary< int , Pair> cache { get ; set ; } public LFU( int cacheSize) { this .cacheSize = cacheSize; this .cache = new Dictionary< int , Pair>(); } // Self made heap to Rearranges // the nodes in order to maintain the heap property public void increment( int value) { if (cache.ContainsKey(value)) { cache[value].frequency += 1; } } // Function to Insert a new node in the heap public void insert( int value) { if (cache.Count == cacheSize) { // remove least frequently used int lfuKey = findLFU(); Console.WriteLine( "Cache block " + lfuKey + " removed." ); cache.Remove(lfuKey); } Pair newPair = new Pair(value, 1); cache.Add(value, newPair); Console.WriteLine( "Cache block " + value + " inserted." ); } // Function to refer to the block value in the cache public void refer( int value) { if (!cache.ContainsKey(value)) { insert(value); } else { increment(value); } } // Function to find the LFU block public int findLFU() { int lfuKey = 0; int minFrequency = Int32.MaxValue; foreach (KeyValuePair< int , Pair> entry in cache) { if (entry.Value.frequency < minFrequency) { minFrequency = entry.Value.frequency; lfuKey = entry.Key; } } return lfuKey; } } public class GFG { public static void Main( string [] args) { LFU lfuCache = new LFU(4); lfuCache.refer(1); lfuCache.refer(2); lfuCache.refer(1); lfuCache.refer(3); lfuCache.refer(2); lfuCache.refer(4); lfuCache.refer(5); } } // This code is contributed by ishankhandelwals. |
Javascript
// JavaScript code for LFU cache implementation const cache_max_size = 4; let cache_size = 0; let cache = new Array(cache_max_size); let indices = {}; // Generic function to swap two pairs function swap(a, b) { let temp = a; a = b; b = temp; } // Returns the index of the parent node function parent(i) { return (i - 1) / 2; } // Returns the index of the left child node function left(i) { return 2 * i + 1; } // Returns the index of the right child node function right(i) { return 2 * i + 2; } // Self made heap to Rearranges // the nodes in order to maintain the heap property function heapify(v, m, i, n) { let l = left(i), r = right(i), minim; if (l < n) minim = ((v[i][1] < v[l][1]) ? i : l); else minim = i; if (r < n) minim = ((v[minim][1] < v[r][1]) ? minim : r); if (minim != i) { m[v[minim][0]] = i; m[v[i][0]] = minim; swap(v[minim], v[i]); heapify(v, m, minim, n); } } // Function to Increment the frequency // of a node and rearranges the heap function increment(v, m, i, n) { ++v[i][1]; heapify(v, m, i, n); } // Function to Insert a new node in the heap function insert(v, m, value, n) { if (n == v.length) { delete m[v[0][0]]; console.log( "Cache block " + v[0][0] + " removed." ); v[0] = v[--n]; heapify(v, m, 0, n); } v[n++] = [value, 1]; m[value] = n - 1; let i = n - 1; // Insert a node in the heap by swapping elements while (i && v[parent(i)][1] > v[i][1]) { m[v[i][0]] = parent(i); m[v[parent(i)][0]] = i; swap(v[i], v[parent(i)]); i = parent(i); } console.log( "Cache block " + value + " inserted." ); } // Function to refer to the block value in the cache function refer(cache, indices, value, cache_size) { if (!indices.hasOwnProperty(value)) insert(cache, indices, value, cache_size); else increment(cache, indices, indices[value], cache_size); } // Driver Code function main() { refer(cache, indices, 1, cache_size); refer(cache, indices, 2, cache_size); refer(cache, indices, 1, cache_size); refer(cache, indices, 3, cache_size); refer(cache, indices, 2, cache_size); refer(cache, indices, 4, cache_size); refer(cache, indices, 5, cache_size); return 0; } main(); // This code is contributed by ishankhandelwals. |
Output
Cache block 1 inserted. Cache block 2 inserted. Cache block 3 inserted. Cache block 4 inserted. Cache block 3 removed. Cache block 5 inserted.
Please Login to comment...