实现基于LRU的Cache

Posted on
Algorithm

LRU需要满足:

  • O(1)时间的读写
  • 将最久未被使用的数据剔除
  • 剔除操作在写入的时候进行

要满足这些条件,需要具备的数据结构特点:

  • 哈希表:O(1)读取
  • 链表:最近被使用的在表头

所以使用双向链表+哈希表的组合作为cache结构。

Python简易实现

使用Python快速实现上述功能,包括数据结点、双向链表和Cache的实现。

数据结点

class Node:
    def __init__(self, key, val):
        self.next = None
        self.prev = None
        self.key = key
        self.val = val

双向链表

class BidirectionalList:
    def __init__(self):
        self.head = None
        self.tail = None

    # 添加结点到链表头
    def add_node(self, node):
        # 链表为空时设置头尾节点
        if not self.head:
            self.head = node
            self.tail = node
            return
        node.next = self.head
        self.head.prev = node
        self.head = node

    # 移除结点并返回
    def remove_node(self, node):
        if node.prev:
            node.prev.next = node.next
        else:
            self.head = node.next
        if node.next:
            node.next.prev = node.prev
        else:
            self.tail = node.prev
        node.prev, node.next = None, None
        return node

Cache实现

class Cache:
    def __init__(self, capacity):
        self.capacity = capacity
        self.b_list = BidirectionalList()
        self.map = {}

    def set(self, key, value):
        node = Node(key=key, val=value)
				if key in self.map:
            self.b_list.add_node(self.b_list.remove_node(node))
        self.map[key] = node
        self.b_list.add_node(node)
        if len(self.map) > self.capacity:
            node = self.b_list.remove_node(self.b_list.tail)
            del self.map[node.key]

    def get(self, key):
        if key not in self.map:
            return None
        node = self.map[key]
        self.b_list.remove_node(node)
        self.b_list.add_node(node)
        return node.val

Swift实现

基于LRU算法,支持范型的Cache实现

Node

import Cocoa

final class Node<T>: NSObject {
    var key: String
    var val: T
    var next: Node?
    var prev: Node?
    
    init(key: String, value: T) {
        self.key = key
        self.val = value
        self.next = nil
        self.prev = nil
        super.init()
    }
}

Bidirectional List

import Cocoa

final class BidirectionalList<T>: NSObject {
    var head: Node<T>?
    var tail: Node<T>?
    var size = 0
    
    final func addFront(node: Node<T>) {
        guard self.size > 0,
              let head = self.head
        else {
            self.head = node
            self.tail = node
            self.size = 1
            return
        }
        head.prev = node
        node.next = head
        self.head = node
        self.size += 1
    }
    
    final func remove(node: Node<T>) -> Node<T>{
        if let prev = node.prev, let next = node.next {
            prev.next = next
            next.prev = prev
        } else if let prev = node.prev {
            prev.next = nil
            self.tail = prev
        } else if let next = node.next {
            next.prev = nil
            self.head = next
        }
        node.prev = nil
        node.next = nil
        return node
    }
    
    final func hit(node: Node<T>) {
        self.addFront(node: self.remove(node: node))
    }
}

Cache

import Cocoa

final class Cache<T>: NSObject {
    let capacity: Int
    var bList = BidirectionalList<T>()
    var map: [String: Node<T>] = [:]
    
    init(capacity: Int) {
        self.capacity = capacity
    }
    
    final func set(value: T, for key:String) {
        let node = Node(key: key, value: value)
        if self.map.keys.contains(key) {
            self.bList.hit(node: node)
            return
        }
        self.bList.addFront(node: node)
        self.map[key] = node
    }
    
    final func get(key:String) -> T? {
        guard let node = self.map[key] else {
            return nil
        }
        self.bList.hit(node: node)
        return node.val
    }
    
}

Usage

let cache = Cache<Int>(capacity: 4)

cache.set(value: 3, for: "3")
cache.set(value: 4, for: "4")
print(cache.get(key: "4") ?? "nil")

参考文章: halfrost的文章