8.3 定义哈希表比较方式

你可以通过 define-hash-table-test 定义新的键查找方法。要使用该功能,你需要理解哈希表的工作原理,以及 哈希码(hash code) 的含义。

可以从概念上把哈希表看作一个拥有很多位置的大型数组,每个位置都能存放一个键值对。查找键时,gethash 首先根据键计算出一个整数,即哈希码。它会将该整数对数组长度取模,得到数组中的下标。然后在该位置查找,必要时再检查附近的位置,判断是否找到了目标键。

因此,要定义一种新的键查找方式,你需要同时指定两个函数:一个根据键计算哈希码的函数;一个直接比较两个键是否相等的函数。这两个函数必须保持一致:也就是说,如果两个键比较结果相等,那么它们的哈希码也必须相同。此外,由于这两个函数可能在任意时刻被调用(例如被垃圾回收器调用),它们应当无副作用、执行迅速,并且行为只依赖于键不会改变的属性。

Function: define-hash-table-test name test-fn hash-fn

该函数定义一个名为 name 的新哈希表比较规则。

用这种方式定义好 name 之后,你就可以在 make-hash-table 中把它用作 test 参数。这样一来,该哈希表就会使用 test-fn 来比较键,并用 hash-fn 从键计算哈希码。

函数 test-fn 应接收两个参数(两个键),如果认为它们相同,则返回非 nil

函数 hash-fn 应接收一个参数(一个键),并返回一个整数作为该键的哈希码。为了达到较好的效果,该函数应使用完整的定长整数范围来生成哈希码,包括负整数。

指定的这两个函数会被存放在 name 的属性列表中,属性名为 hash-table-test;属性值的形式为 (test-fn hash-fn)

函数 test-fn 应接受两个参数(两个键),如果认为这两个键相同,则返回非 nil

函数 hash-fn 应接受一个参数(一个键),并返回一个整数作为该键的哈希码。为了获得良好效果,该函数应使用完整的 fixnum(定长整数)范围来生成哈希码,包括负整数。

指定的这两个函数会被存放在 name 的属性列表中,属性名为 hash-table-test;属性值的格式为 (test-fn hash-fn)

Function: sxhash-equal obj

该函数为 Lisp 对象 obj 返回一个哈希码。这是一个反映 obj 内容及其指向的其他 Lisp 对象的整数。

如果两个对象 obj1obj2 满足 equal,那么 (sxhash-equal obj1)(sxhash-equal obj2) 是相同的整数。

如果两个对象不满足 equalsxhash-equal 返回的值通常不同,但并非绝对。sxhash-equal 被设计为速度适中(因为它用于哈希表索引),因此不会深度递归遍历嵌套结构。此外,极少数情况下,你可能会遇到两个看起来不同的简单对象,却被 sxhash-equal 算出相同的哈希码。因此,通常不能用 sxhash-equal 来检测一个对象是否发生了变化。

Common Lisp 注意: 在 Common Lisp 中,类似的函数名为 sxhash。Emacs 提供了这个名字,作为 sxhash-equal 的兼容别名。

Function: sxhash-eq obj

该函数为 Lisp 对象 obj 返回一个哈希码。其返回结果反映的是 obj 的标识(identity),而非其内容。

如果两个对象 obj1obj2 满足 eq(标识相等),那么 (sxhash-eq obj1)(sxhash-eq obj2) 会返回相同的整数。

Function: sxhash-eql obj

该函数为 Lisp 对象 obj 返回一个适用于 eql 比较的哈希码。换句话说,它反映的是 obj 的标识,但有一个例外:当对象是大整数(bignum)或浮点数时,哈希码会根据其数值生成。

如果两个对象 obj1obj2 满足 eql,那么 (sxhash-eql obj1)(sxhash-eql obj2) 会返回相同的整数。

以下示例创建了一个哈希表,其键为字符串,且字符串的比较是大小写不敏感的。

(defun string-hash-ignore-case (a)
  (sxhash-equal (upcase a)))

(define-hash-table-test 'ignore-case
  'string-equal-ignore-case 'string-hash-ignore-case)

(make-hash-table :test 'ignore-case)

下面是你可以如何定义一个与预定义比较规则 equal 等价的哈希表比较方式。键可以是任意 Lisp 对象,看起来内容相等的对象会被视为同一个键。

(define-hash-table-test 'contents-hash 'equal 'sxhash-equal)

(make-hash-table :test 'contents-hash)

Lisp 程序 不应该 依赖哈希码在不同 Emacs 会话之间保持不变,因为哈希函数的实现会使用对象存储的某些细节,这些细节在不同会话、不同架构之间可能发生变化。


emacs

Emacs

org-mode

Orgmode

Donations

打赏

Copyright

© 2025 Jasper Hsu

Creative Commons

Creative Commons

Attribute

Attribute

Noncommercial

Noncommercial

Share Alike

Share Alike