遍历哈希表条目
迭代 Lisp 中哈希表的现代方法
Section titled “迭代 Lisp 中哈希表的现代方法”哈希表是 Common Lisp 中用于高效存储键值对的基本数据结构。一个常见的任务是迭代所有条目以处理它们。现代 Lisp 提供了几种惯用的方法来完成此任务,每种方法都有其自身的优点。
创建哈希表::test 的重要性
Section titled “创建哈希表::test 的重要性”在我们迭代之前,让我们先创建一个哈希表。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)方法 1:loop 宏(推荐)
Section titled “方法 1:loop 宏(推荐)”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))输出(顺序不保证)
Section titled “输出(顺序不保证)”The price of orange is $1.50The price of apple is $1.25The price of banana is $0.75为什么 loop 很棒:它是声明性的,清晰地表达了意图,并为您处理了所有迭代机制。您还可以使用其他 loop 子句,如 collect、sum 或 when,以简洁地执行复杂操作。
方法 2:maphash(函数式风格)
Section titled “方法 2:maphash(函数式风格)”maphash 对哈希表中的每个键值对应用一个函数。它最适合用于需要对每个条目执行操作(副作用)的情况。它不返回有意义的值。
(maphash function hash-table);; 该函数必须接受两个参数:键和值。(maphash (lambda (fruit price) (format t "Key: ~a, Value: $~,2F~%" fruit price)) *fruit-prices*)输出(顺序不保证)
Section titled “输出(顺序不保证)”Key: orange, Value: $1.50Key: apple, Value: $1.25Key: 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))))) ; 当没有更多条目时停止您应该使用哪种方法?
Section titled “您应该使用哪种方法?”- 几乎所有情况都使用
loop。它是最清晰、最强大且最常用的方法。 - 当您希望以简单的函数式风格应用函数以产生副作用时,使用
maphash。 - 仅当
loop和maphash不能满足您的需求时,例如您需要将迭代器本身传递给另一个函数时,才使用with-hash-table-iterator。
测试您的代码
Section titled “测试您的代码”在编写处理哈希表的函数时,请记住对其进行测试。一个现代的 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)))))