Skip to content

遍历哈希表条目

哈希表是 Common Lisp 中用于高效存储键值对的基本数据结构。一个常见的任务是迭代所有条目以处理它们。现代 Lisp 提供了几种惯用的方法来完成此任务,每种方法都有其自身的优点。

在我们迭代之前,让我们先创建一个哈希表。make-hash-table 最重要的参数是 :test,它指定了键如何进行比较。

  • #'eq:按标识(内存位置)比较对象。速度快,但仅当键是完全相同的对象时才有效。适用于符号。
  • #'eql(默认):类似于 eq,但也适用于相同的字符和数字。
  • #'equal:按结构比较。用于字符串键或列表键。
  • #'equalp:类似于 equal,但对字符串和字符不区分大小写。
;; 字符串键的哈希表必须使用 #'equal 或 #'equalp
(defvar *fruit-prices* (make-hash-table :test #'equal))
;; 使用 SETF 和 GETHASH 填充哈希表
(setf (gethash "apple" *fruit-prices*) 1.25)
(setf (gethash "banana" *fruit-prices*) 0.75)
(setf (gethash "orange" *fruit-prices*) 1.50)

loop 宏是现代 Common Lisp 中迭代哈希表最灵活、可读性最高且最惯用的方式。它为此目的提供了专门的子句。

(loop for key being the hash-key of <hash-table>
using (hash-value value)
do (your-code-here))
(loop for fruit being the hash-key of *fruit-prices*
using (hash-value price)
do (format t "The price of ~a is $~,2F~%" fruit price))
The price of orange is $1.50
The price of apple is $1.25
The price of banana is $0.75

为什么 loop 很棒:它是声明性的,清晰地表达了意图,并为您处理了所有迭代机制。您还可以使用其他 loop 子句,如 collect、sum 或 when,以简洁地执行复杂操作。

maphash 对哈希表中的每个键值对应用一个函数。它最适合用于需要对每个条目执行操作(副作用)的情况。它不返回有意义的值。

(maphash function hash-table)
;; 该函数必须接受两个参数:键和值。
(maphash (lambda (fruit price)
(format t "Key: ~a, Value: $~,2F~%" fruit price))
*fruit-prices*)
Key: orange, Value: $1.50
Key: apple, Value: $1.25
Key: banana, Value: $0.75

方法 3:with-hash-table-iterator(低级控制)

Section titled “方法 3:with-hash-table-iterator(低级控制)”

这个宏提供了一种更低级、更手动化的迭代方式。它为您提供一个迭代器函数,您可以重复调用它来获取下一个条目。这是 loop 内部使用的机制。通常只在高度专门的迭代模式下才需要它。

(with-hash-table-iterator (iterator-name hash-table)
(loop (multiple-value-bind (entry-present-p key value) (iterator-name)
(if entry-present-p
(format t "Key: ~a, Value: ~a~%" key value)
(return))))) ; 当没有更多条目时停止
  • 几乎所有情况都使用 loop。它是最清晰、最强大且最常用的方法。
  • 当您希望以简单的函数式风格应用函数以产生副作用时,使用 maphash。
  • 仅当 loop 和 maphash 不能满足您的需求时,例如您需要将迭代器本身传递给另一个函数时,才使用 with-hash-table-iterator。

在编写处理哈希表的函数时,请记住对其进行测试。一个现代的 Lisp 测试框架,如 FiveAM,可用于确保正确性。

;; 使用测试框架的概念性测试
(fiveam:test find-expensive-fruits
(let ((prices (make-hash-table :test 'equal)))
(setf (gethash "apple" prices) 1.0)
(setf (gethash "mango" prices) 3.0)
(fiveam:is (equal '("mango") (find-expensive-fruits prices :min-price 2.0)))))