Skip to content

File lru.hpp

File List > include > loon > lru.hpp

Go to the documentation of this file

// Copyright (c) 2026 Jorge Suarez-Rivaya
// SPDX-License-Identifier: MIT

#pragma once

#include <cstdint>
#include <optional>
#include <unordered_map>
#include <vector>

namespace loon {

template <typename K, typename V>
class LRU {
 public:
  explicit LRU(uint32_t size) : capacity(size), store(size) {
    lookup.reserve(size);
    for (uint32_t i = 0; i < size; ++i) {
      store[i].next = i + 1;
    }
    store[size - 1].next = NIL;
  }

  std::optional<std::reference_wrapper<V>> get(const K& key) {
    const auto it = lookup.find(key);
    if (it == lookup.end())
      return std::nullopt;

    set_mru(it->second);

    return std::ref(store[it->second].value);
  }

  void put(const K& key, const V& value) {
    auto [it, inserted] = lookup.try_emplace(key);
    if (inserted) { // key didnt exist
      if (capacity < lookup.size()) {
        evict();
      }

      auto idx = emplace_front(key, value);
      it->second = idx;
    } else {
      store[it->second].value = value;
      set_mru(it->second);
    }
  }

  bool exists(const K& key) const { return lookup.find(key) != lookup.end(); }

  void remove(const K& key) {
    const auto it = lookup.find(key);
    if (it == lookup.end())
      return;

    lookup.erase(it);
  }

  uint32_t size() const { return lookup.size(); }

 private:
  uint32_t capacity;

  struct Node {
    K key;
    V value;
    uint32_t prev;
    uint32_t next;
  };

  std::vector<Node> store;
  uint32_t front = NIL;    
  uint32_t back = NIL;     
  uint32_t free_front = 0; 
  std::unordered_map<K, uint32_t> lookup;

  // Moves node to the front (MRU position). Unlinks from current position,
  // updates back if it was the tail. No-op if already the front.
  void set_mru(uint32_t node) {
    if (node == front) {
      return;
    }

    auto prev = store[node].prev;
    auto next = store[node].next;

    if (prev != NIL) {
      store[prev].next = next;
      if (next != NIL) {
        store[next].prev = prev;
      } else {
        // we have to update
        back = store[node].prev;
      }
    }

    if (front == NIL && back == NIL) { // first insertion
      front = node;
      back = node;
      return;
    }
    store[node].next = front; // if first node front == NIL
    store[node].prev = NIL;

    store[front].prev = node;
    front = node;
  }

  // Sentinel: no-node. prev == NIL → head, next == NIL → tail, front/back == NIL → empty.
  static constexpr uint32_t NIL = UINT32_MAX;

  // Removes the LRU (tail) node, erases it from the map, and returns it to the free list.
  void evict() {
    lookup.erase(store[back].key);
    auto node = back;
    back = store[node].prev;
    store[back].next = NIL;        // Last element next is now NIL
    store[node].prev = NIL;        // move node to head of free nodes
    store[node].next = free_front; // move node to head of free nodes
    free_front = node;
  }

  // Pops a node from the free list, fills it, and links it at the front.
  uint32_t emplace_front(K key, const V& value) {
    auto node = free_front;
    free_front = store[node].next;

    store[node].key = key;
    store[node].value = value;
    store[node].prev = NIL;
    store[node].next = NIL;
    set_mru(node);

    return node;
  }
};

} // namespace loon