Open In App

Least Frequently Used (LFU) Cache Implementation

Last Updated : 28 Dec, 2023
Improve
Improve
Like Article
Like
Save
Share
Report

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 LFU {
    int cacheSize;
    Map<Integer, Pair> cache;
    PriorityQueue<Pair> minHeap;
 
    public LFU(int cacheSize) {
        this.cacheSize = cacheSize;
        this.cache = new HashMap<>();
        this.minHeap = new PriorityQueue<>((a, b) -> a.frequency - b.frequency);
    }
 
    public void increment(int value) {
        if (cache.containsKey(value)) {
            Pair pair = cache.get(value);
            pair.frequency += 1;
            minHeap.remove(pair);
            minHeap.offer(pair);
        }
    }
 
    public void insert(int value) {
        if (cache.size() == cacheSize) {
            evictLFU();
        }
 
        Pair newPair = new Pair(value, 1);
        cache.put(value, newPair);
        minHeap.offer(newPair);
        System.out.println("Cache block " + value + " inserted.");
    }
 
    public void refer(int value) {
        if (!cache.containsKey(value)) {
            insert(value);
        } else {
            increment(value);
        }
    }
 
    public void evictLFU() {
        Pair lfuPair = minHeap.poll();
        cache.remove(lfuPair.value);
        System.out.println("Cache block " + lfuPair.value + " removed.");
    }
}
 
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);
    }
}
 
class Pair {
    int value, frequency;
 
    public Pair(int value, int frequency) {
        this.value = value;
        this.frequency = frequency;
    }
}


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.


Similar Reads

Page Faults in LFU | Implementation
In this, it is using the concept of paging for memory management, a page replacement algorithm is needed to decide which page needs to be replaced when the new page comes in. Whenever a new page is referred to and is not present in memory, the page fault occurs and the Operating System replaces one of the existing pages with a newly needed page. LF
12 min read
Most Frequently Used (MFU) Algorithm in Operating System
MFU Algorithm is a Page Replacement Algorithm in the Operating System that replaces the page accessed a maximum number of times in the past. If more than one page is accessed the same number of times, then the page which occupied the frame first will be replaced. Page ReplacementIt is a technique of replacing a data block (frame) of Main Memory wit
5 min read
Implementation of Least Recently Used (LRU) page replacement algorithm using Counters
Prerequisite - Least Recently Used (LRU) Page Replacement algorithm Least Recently Used page replacement algorithm replaces the page which is not used recently. Implementation: In this article, LRU is implemented using counters, a ctime (i.e., counter) variable is used to represent the current time, it is incremented for every page of the reference
8 min read
LRU Cache implementation using Double Linked Lists
Given an integer N represents the size of the doubly linked list and array arr[], where arr[i] is the element to be search within the linked list, the task is to use a doubly linked list to implement the Least Recently Used (LRU) algorithm. Examples: Input: N = 3, Arr = { 1, 2, 3 } Output: [0]-&gt;[0]-&gt;[0]-&gt;NULL [1]-&gt;[0]-&gt;[0]-&gt;NULL [
16 min read
Top 25 Frequently Asked Interview Questions in Technical Rounds
Here is the collection of the TOP 25 frequently asked questions based on the experience of interviews in multiple companies. 1Lowest common Ancestor2An unsorted array of integers is given; you must find the max product formed by multiplying three numbers. (You cannot sort the array, watch out when there are negative numbers)3Left View of a tree4Rev
1 min read
Some Frequently Asked Questions (FAQs) about Quick Sort
Below are some of the most frequently asked questions on Quick Sort: 1. Hoare's vs Lomuto PartitionPlease note that the above implementation is Lomuto Partition. A more optimized implementation of QuickSort is Hoare's partition which is more efficient than Lomuto's partition scheme because it does three times less swaps on average. 2. How to pick a
2 min read
Amazon’s most frequently asked interview questions | Set 2
Amazon's Most Frequently Asked Questions | Set 1 Level - Easy Get minimum element from the stack - Practice hereSerialize and deserialize a binary tree - Practice herePrint a binary tree in a vertical order - Practice hereCelebrity problem - Practice hereLevel order traversalSwap the kth element from starting and from the end position - Practice he
2 min read
Program for Least Recently Used (LRU) Page Replacement algorithm
Prerequisite: Page Replacement AlgorithmsIn operating systems that use paging for memory management, page replacement algorithm are needed to decide which page needed to be replaced when new page comes in. Whenever a new page is referred and not present in memory, page fault occurs and Operating System replaces one of the existing pages with newly
14 min read
Implementation of Chinese Remainder theorem (Inverse Modulo based implementation)
We are given two arrays num[0..k-1] and rem[0..k-1]. In num[0..k-1], every pair is coprime (gcd for every pair is 1). We need to find minimum positive number x such that: x % num[0] = rem[0], x % num[1] = rem[1], ....................... x % num[k-1] = rem[k-1] Example: Input: num[] = {3, 4, 5}, rem[] = {2, 3, 1} Output: 11 Explanation: 11 is the sm
11 min read
Count pairs (p, q) such that p occurs in array at least q times and q occurs at least p times
Given an array arr[], the task is to count unordered pairs of positive integers (p, q) such that p occurs in the array at least q times and q occurs at least p times. Examples: Input: arr[] = {1, 2, 3, 4, 5} Output: 1 (1, 1) is the only valid pair. Input: arr[] = {3, 3, 2, 2, 2} Output: 2 (2, 3) and (2, 2) are the only possible pairs. Approach: The
8 min read