如何在 elisp 中实现过期 LRU 缓存?

发布于 2024-11-19 23:54:15 字数 535 浏览 4 评论 0原文

我的数据由以下三个部分组成:

  • a_path
  • a_key
  • a_value =f(a_path, a_key)

a_value 计算起来很昂贵,所以我不想经常计算它。在理想的世界中,只有当情况发生变化时才会发生这种情况。因此,我对此缓存的要求如下:

  • 具有可配置最大大小的 LRU 缓存
  • Keyed on (a_path, a_key)
  • 能够根据年龄使条目过期(例如,每小时左右重新计算一次)
  • 能力使基于 expiry_func(a_path, a_key) 的条目过期

我的谷歌搜索在这里失败了;即使在搜索“elisp LRU cache”时,我也发现了很多 Java 站点。

I have data that consists of the following three components:

  • a_path
  • a_key
  • a_value =f(a_path, a_key)

a_value is expensive to calculate, so I want to calculate it infrequently. In an ideal world, that would be only when it's going to change. So, my requirements for this cache are as follows:

  • LRU cache with configurable maximum size
  • Keyed on (a_path, a_key)
  • Ability to expire an entry based on age (recalculate every hour or so, for example)
  • Ability to expire an entry based on expiry_func(a_path, a_key)

My googling has failed me here; I find a lot of Java sites even when searching for "elisp LRU cache".

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

弥枳 2024-11-26 23:54:15

这就是您想要的大部分内容:固定大小的最近最少使用的缓存,具有 O(1) 查找、O(1) 插入和 O(1) 删除。

让所有这些操作都达到 O(1) 有点棘手,因此这个实现稍微复杂一些。我将哈希表(用于快速查找)与双向链接的项目列表(用于快速删除、重新排序和查找最旧的元素)结合起来。

(require 'cl)

(defstruct lru-cache max-size size newest oldest table)

(defstruct lru-item key value next prev)

(defun lru-remove-item (item lru)
  (let ((next (lru-item-next item))
        (prev (lru-item-prev item)))
    (if next (setf (lru-item-prev next) prev) 
      (setf (lru-cache-newest lru) prev))
    (if prev (setf (lru-item-next prev) next)
      (setf (lru-cache-oldest lru) next))))

(defun lru-insert-item (item lru)
  (let ((newest (lru-cache-newest lru)))
    (setf (lru-item-next item) nil (lru-item-prev item) newest)
    (if newest (setf (lru-item-next newest) item)
      (setf (lru-cache-oldest lru) item))
    (setf (lru-cache-newest lru) item)))

;;; Public interface starts here.

(defun* lru-create (&key (size 65) (test 'eql))
  "Create a new least-recently-used cache and return it.
Takes keyword arguments
:SIZE the maximum number of entries (default: 65).
:TEST a hash table test (default 'EQL)."
  (make-lru-cache 
   :max-size size
   :size 0
   :newest nil
   :oldest nil
   :table (make-hash-table :size size :test test)))

(defun lru-get (key lru &optional default)
  "Look up KEY in least-recently-used cache LRU and return
its associated value.
If KEY is not found, return DEFAULT which defaults to nil."
  (let ((item (gethash key (lru-cache-table lru))))
    (if item
        (progn
          (lru-remove-item item lru)
          (lru-insert-item item lru)
          (lru-item-value item))
      default)))

(defun lru-rem (key lru)
  "Remove KEY from least-recently-used cache LRU."
  (let ((item (gethash key (lru-cache-table lru))))
    (when item
      (remhash (lru-item-key item) (lru-cache-table lru))
      (lru-remove-item item lru)
      (decf (lru-cache-size lru)))))

(defun lru-put (key value lru)
  "Associate KEY with VALUE in least-recently-used cache LRU.
If KEY is already present in LRU, replace its current value with VALUE."
  (let ((item (gethash key (lru-cache-table lru))))
    (if item
        (setf (lru-item-value item) value)
      (when (eql (lru-cache-size lru) (lru-cache-max-size lru))
        (lru-rem (lru-item-key (lru-cache-oldest lru)) lru))
      (let ((newitem (make-lru-item :key key :value value)))
        (lru-insert-item newitem lru)
        (puthash key newitem (lru-cache-table lru))
        (incf (lru-cache-size lru))))))

 ;;; Exercise for the reader: implement lru-clr and lru-map to complete the
 ;;; analogy with hash tables.

对于以对为键的应用程序,您可能需要向 lru-create 提供 :test 'equal。或者参见定义哈希比较如果你需要一些特别的东西。

我会让你弄清楚如何进行基于时间的到期;从这里开始应该很简单。

(如果有人知道一种更简单的方法来实现这一点,同时保持操作在恒定时间内运行,我将非常有兴趣看到它。)

Here's most of what you want: a fixed-size least-recently-used cache with O(1) lookup, O(1) insertion, and O(1) deletion.

It's slightly tricky to get all of these operations to be O(1), hence this slightly elaborate implementation. I combine a hash-table (for fast lookup) with a doubly-linked list of items (for fast removal, re-ordering, and finding the oldest element).

(require 'cl)

(defstruct lru-cache max-size size newest oldest table)

(defstruct lru-item key value next prev)

(defun lru-remove-item (item lru)
  (let ((next (lru-item-next item))
        (prev (lru-item-prev item)))
    (if next (setf (lru-item-prev next) prev) 
      (setf (lru-cache-newest lru) prev))
    (if prev (setf (lru-item-next prev) next)
      (setf (lru-cache-oldest lru) next))))

(defun lru-insert-item (item lru)
  (let ((newest (lru-cache-newest lru)))
    (setf (lru-item-next item) nil (lru-item-prev item) newest)
    (if newest (setf (lru-item-next newest) item)
      (setf (lru-cache-oldest lru) item))
    (setf (lru-cache-newest lru) item)))

;;; Public interface starts here.

(defun* lru-create (&key (size 65) (test 'eql))
  "Create a new least-recently-used cache and return it.
Takes keyword arguments
:SIZE the maximum number of entries (default: 65).
:TEST a hash table test (default 'EQL)."
  (make-lru-cache 
   :max-size size
   :size 0
   :newest nil
   :oldest nil
   :table (make-hash-table :size size :test test)))

(defun lru-get (key lru &optional default)
  "Look up KEY in least-recently-used cache LRU and return
its associated value.
If KEY is not found, return DEFAULT which defaults to nil."
  (let ((item (gethash key (lru-cache-table lru))))
    (if item
        (progn
          (lru-remove-item item lru)
          (lru-insert-item item lru)
          (lru-item-value item))
      default)))

(defun lru-rem (key lru)
  "Remove KEY from least-recently-used cache LRU."
  (let ((item (gethash key (lru-cache-table lru))))
    (when item
      (remhash (lru-item-key item) (lru-cache-table lru))
      (lru-remove-item item lru)
      (decf (lru-cache-size lru)))))

(defun lru-put (key value lru)
  "Associate KEY with VALUE in least-recently-used cache LRU.
If KEY is already present in LRU, replace its current value with VALUE."
  (let ((item (gethash key (lru-cache-table lru))))
    (if item
        (setf (lru-item-value item) value)
      (when (eql (lru-cache-size lru) (lru-cache-max-size lru))
        (lru-rem (lru-item-key (lru-cache-oldest lru)) lru))
      (let ((newitem (make-lru-item :key key :value value)))
        (lru-insert-item newitem lru)
        (puthash key newitem (lru-cache-table lru))
        (incf (lru-cache-size lru))))))

 ;;; Exercise for the reader: implement lru-clr and lru-map to complete the
 ;;; analogy with hash tables.

For your application that's keyed on pairs, you probably want to supply :test 'equal to lru-create. Or see Defining Hash Comparisons if you need something special.

I'll let you figure out how to do the time-based expiry; it should be straightforward from here.

(If anyone knows a simpler way to implement this while keeping the operations running in constant time, I'd be very interested to see it.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文