Least Recently Used (LRU) Cache, by Google

Today's Daily Coding Problem:

This problem was asked by Google.
Implement an LRU (Least Recently Used) cache. It should be able to be initialized with a cache size n, and contain the following methods:
  • set(key, value): sets key to value. If there are already n items in the cache and we are adding a new item, then it should also remove the least recently used item.
  • get(key): gets the value at key. If no such key exists, return null.
Each operation should run in O(1) time.

The O(1) means that both set and get operations must be performed in constant time, hence forget about using heaps or trees.
The key here is to use extra space. There is no requirements on the space utilization.
Here is the idea:

  • Have a list to implement the cache. The list will be a list of <key, value>
  • But also have a hash table point a key to each node in the list
  • When setting, set an existing value or add a new one. Move it to the top of the list since it has been used
  • If the list count is greater than N, remove the last
  • On getting it, check the hash table and return that element
  • Move it to the front of the list
By doing that you accomplish O(1) time for both operations with O(n) space. Hope you enjoy it, Marcelo.


Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections;

namespace DailyCodingProblem
{
 class DailyCodingProblem11052018
 {
  private LinkedList cache = null;
  private Hashtable pointerToItem = null;
  private int N = 0;

  public DailyCodingProblem11052018(int n)
  {
   this.N = n;
   cache = new LinkedList();
   pointerToItem = new Hashtable();
  }

  public void Set(string key, string value)
  {
   Console.WriteLine("Adding key = {0}, value = {1}", key, value);
   if (pointerToItem.ContainsKey(key))
   {
    //Reset the value
    LinkedListNode node = (LinkedListNode)pointerToItem[key];
    node.Value.value = value;

    //Move node up since it just got "used"
    cache.Remove(node);
    LRUCacheItem newItem = new LRUCacheItem(key, value);
    cache.AddFirst(newItem);
    pointerToItem[key] = cache.First;
   }
   else
   {
    //Add first
    LRUCacheItem newItem = new LRUCacheItem(key, value);
    cache.AddFirst(newItem);
    pointerToItem.Add(key, cache.First);

    //Remove last if needed
    if (cache.Count > N)
    {
     Console.WriteLine("Debugging: removing from cache!");
     string keyToRemove = cache.Last.Value.key;
     cache.RemoveLast();
     pointerToItem.Remove(keyToRemove);
    }
   }
  }

  public string Get(string key)
  {
   if (!pointerToItem.ContainsKey(key)) return null;

   //Get the return val
   string value = ((LinkedListNode)pointerToItem[key]).Value.value;

   //Move it up since it just got "used"
   cache.Remove((LinkedListNode)pointerToItem[key]);
   LRUCacheItem newItem = new LRUCacheItem(key, value);
   cache.AddFirst(newItem);
   pointerToItem[key] = cache.First;

   return value;
  }
 }

 public class LRUCacheItem
 {
  public string key;
  public string value;

  public LRUCacheItem(string key, string value)
  {
   this.key = key;
   this.value = value;
  }
 }
}

Comments

  1. Another great problem from LeetCode - https://leetcode.com/problems/lru-cache

    My C++ solution is below:

    class LRUCache {
    private:
    using KV = pair;
    list values;
    unordered_map::iterator> table;
    int capacity;
    public:
    LRUCache(int capacity): capacity(capacity) {}

    int get(int key) {
    auto found = table.find(key);
    if (found == table.end()) return -1;
    int value = found->second->second;
    if (found->second != values.begin()) {
    values.erase(found->second);
    values.emplace_front(key, value);
    found->second = values.begin();
    }
    return found->second->second;
    }

    void put(int key, int value) {
    auto found = table.find(key);
    if (found != table.end()) {
    if (found->second->second != value) found->second->second = value;
    if (found->second != values.begin()) {
    values.erase(found->second);
    values.emplace_front(key, value);
    found->second = values.begin();
    }
    return;
    }
    if (table.size() == capacity) {
    int key_to_evict = values.back().first;
    table.erase(key_to_evict);
    values.pop_back();
    }
    values.emplace_front(key, value);
    table[key] = values.begin();
    }
    };

    Btw, you should probably remove Console.WriteLine statements, since IO is pretty slow and will not help with online judge time limit :)

    Cheers,
    Taras

    ReplyDelete

Post a Comment

Popular posts from this blog

Advent of Code - Day 6, 2024: BFS and FSM

Golang vs. C#: performance characteristics (simple case study)

Advent of Code - Day 7, 2024: Backtracking and Eval