Lisp 非破坏性修改哈希表

Lisp 非破坏性修改哈希表,lisp,common-lisp,hashtable,sbcl,setf,Lisp,Common Lisp,Hashtable,Sbcl,Setf,是否可以无破坏性地向公共Lisp(SBCL)哈希表添加新的键值对?向哈希表添加新元素的标准方法是调用: (setf (gethash key *hash-table*) value) 但是对setf的调用会修改*哈希表*,从而破坏原始文件。我有一个应用程序,我希望利用哈希表查找的效率,但我也希望以非破坏性方式修改它们。我看到的解决方法是先复制原始哈希表,然后再对其进行操作,但这在我的情况下并不实用,因为我处理的哈希表包含数千个元素,并复制大型哈希表,在循环中,首先使用它们会抵消计算效率的优势。

是否可以无破坏性地向公共Lisp(SBCL)哈希表添加新的键值对?向哈希表添加新元素的标准方法是调用:

(setf (gethash key *hash-table*) value)

但是对
setf
的调用会修改
*哈希表*
,从而破坏原始文件。我有一个应用程序,我希望利用哈希表查找的效率,但我也希望以非破坏性方式修改它们。我看到的解决方法是先复制原始哈希表,然后再对其进行操作,但这在我的情况下并不实用,因为我处理的哈希表包含数千个元素,并复制大型哈希表,在循环中,首先使用它们会抵消计算效率的优势。

我不认为可以通过引用将一个哈希表传递给common lisp中的另一个哈希表。 但我想到的是如何避免复制整个哈希表, 然而,通过一次调用得到的结果是使用默认值参数位置 属于
gethash

(gethash key ht default value)
ht
中不存在
key
时,返回为默认值给定的值

;; prepare three example hash-tables, where *h3* and *h2* gets the additional keys
;; and if a key is not present in *h3*, one should look up in *h2*, and if not there too, in *h1*.
(defparameter *h1* (make-hash-table))
(setf (gethash 'a *h1*) 1)
(setf (gethash 'b *h1*) 2)
(setf (gethash 'c *h1*) 3)

(defparameter *h2* (make-hash-table))
(setf (gethash 'd *h2*) 4)
(setf (gethash 'e *h2*) 5)

(defparameter *h3* (make-hash-table))
(setf (gethash 'f *h3*) 6)

;; the call
(gethash 'a *h3* (gethash 'a *h2* (gethash 'a *h1*)))
;; would give the desired result `1`.

;; let us assume, there is a chain of hash-tables *hk* *h(k-1)* ... *h2* *h1*
;; in which one should look up into that order.
;; Then it is to us to build the code
;; (gethash 'a *hk* (gethash 'a *h(k-1)* ...(gethash 'a *h2* (gethash 'a *h1*))...))
;; automatically for every lookup.


;; this macro does it:
(defmacro mget (key hash-tables-list)
  (flet ((inject-last (e1 e2) `(,@e1 ,e2)))
    (reduce #'inject-last 
            (mapcar (lambda (ht) `(gethash ,key ,ht)) 
                    (nreverse hash-tables-list)))))

;; let's see its macroexpansion:
(macroexpand-1 '(mget 'a (*h3* *h2* *h1*)))
;; (GETHASH 'A *H3* (GETHASH 'A *H2* (GETHASH 'A *H1*))) ;
;; T

;; and run the code:
(mget 'a (*h2* *h1*))
;; 1 ;
;; NIL
可以附加信息,这些信息是下一个要查找的哈希表 在哈希表对象中。甚至可以自动生成列表
(*h3**h2**h1*)
,这样就可以只写
(gethash*key ht)
然后调用
mget

当然,通过这一切,散列访问速度变慢了

这是复制整个哈希表或在每次调用时支付性能成本之间的折衷

自动查找扩展为
*h3*

我认为在CommonLisp中不能通过引用将一个哈希表传递给另一个哈希表。 但我想到的是如何避免复制整个哈希表, 然而,通过一次调用得到的结果是使用默认值参数位置 属于
gethash

(gethash key ht default value)
ht
中不存在
key
时,返回为默认值给定的值

;; prepare three example hash-tables, where *h3* and *h2* gets the additional keys
;; and if a key is not present in *h3*, one should look up in *h2*, and if not there too, in *h1*.
(defparameter *h1* (make-hash-table))
(setf (gethash 'a *h1*) 1)
(setf (gethash 'b *h1*) 2)
(setf (gethash 'c *h1*) 3)

(defparameter *h2* (make-hash-table))
(setf (gethash 'd *h2*) 4)
(setf (gethash 'e *h2*) 5)

(defparameter *h3* (make-hash-table))
(setf (gethash 'f *h3*) 6)

;; the call
(gethash 'a *h3* (gethash 'a *h2* (gethash 'a *h1*)))
;; would give the desired result `1`.

;; let us assume, there is a chain of hash-tables *hk* *h(k-1)* ... *h2* *h1*
;; in which one should look up into that order.
;; Then it is to us to build the code
;; (gethash 'a *hk* (gethash 'a *h(k-1)* ...(gethash 'a *h2* (gethash 'a *h1*))...))
;; automatically for every lookup.


;; this macro does it:
(defmacro mget (key hash-tables-list)
  (flet ((inject-last (e1 e2) `(,@e1 ,e2)))
    (reduce #'inject-last 
            (mapcar (lambda (ht) `(gethash ,key ,ht)) 
                    (nreverse hash-tables-list)))))

;; let's see its macroexpansion:
(macroexpand-1 '(mget 'a (*h3* *h2* *h1*)))
;; (GETHASH 'A *H3* (GETHASH 'A *H2* (GETHASH 'A *H1*))) ;
;; T

;; and run the code:
(mget 'a (*h2* *h1*))
;; 1 ;
;; NIL
可以附加信息,这些信息是下一个要查找的哈希表 在哈希表对象中。甚至可以自动生成列表
(*h3**h2**h1*)
,这样就可以只写
(gethash*key ht)
然后调用
mget

当然,通过这一切,散列访问速度变慢了

这是复制整个哈希表或在每次调用时支付性能成本之间的折衷

自动查找扩展为
*h3*

根据您的需要,您可以只使用关联列表,使用和其他函数在现有绑定的基础上建立新绑定。
assoc
返回第一个匹配元素这一事实意味着您可以对绑定进行阴影处理:

(let ((list '((:a . 1) (:b . 2))))
  (acons :b 3 list))

=> ((:b . 3) (:a . 1) (:b . 2))
如果在结果列表中调用
(assoc:b list)
,则条目将是
(:b.3)
,但原始列表未被修改

FSet 如果关联列表还不够,那么FSet库为公共Lisp提供纯粹的功能性数据结构,比如映射,它们是不可变的哈希表。它们被实现为平衡树,这比简单的方法要好。还有其他更高效的数据结构,但您可能需要自己实现它们()。这就是说,FSet总体上已经足够好了

FSet可通过Quicklisp获得

USER> (ql:quickload :fset)
创建地图;请注意,如果安装了相应的读卡器宏,打印的表达将被重新读取。但是,您可以在不修改语法表的情况下完美地使用该库

USER> (fset:map (:a 0) (:b 1))
#{| (:A 0) (:B 1) |}
使用
:c
的新绑定更新以前的映射:

USER> (fset:with * :c 3)
#{| (:A 0) (:B 1) (:C 3) |}
使用新的
:b
绑定更新上一个映射,该绑定会遮挡上一个映射:

USER> (fset:with * :b 4)
#{| (:A 0) (:B 4) (:C 3) |}
所有中间贴图均未修改:

USER> (list * ** *** )
(#{| (:A 0) (:B 4) (:C 3) |}
 #{| (:A 0) (:B 1) (:C 3) |} 
 #{| (:A 0) (:B 1) |})

根据您的需要,您可以只使用关联列表,使用和其他函数在现有绑定的基础上建立新绑定。
assoc
返回第一个匹配元素这一事实意味着您可以对绑定进行阴影处理:

(let ((list '((:a . 1) (:b . 2))))
  (acons :b 3 list))

=> ((:b . 3) (:a . 1) (:b . 2))
如果在结果列表中调用
(assoc:b list)
,则条目将是
(:b.3)
,但原始列表未被修改

FSet 如果关联列表还不够,那么FSet库为公共Lisp提供纯粹的功能性数据结构,比如映射,它们是不可变的哈希表。它们被实现为平衡树,这比简单的方法要好。还有其他更高效的数据结构,但您可能需要自己实现它们()。这就是说,FSet总体上已经足够好了

FSet可通过Quicklisp获得

USER> (ql:quickload :fset)
创建地图;请注意,如果安装了相应的读卡器宏,打印的表达将被重新读取。但是,您可以在不修改语法表的情况下完美地使用该库

USER> (fset:map (:a 0) (:b 1))
#{| (:A 0) (:B 1) |}
使用
:c
的新绑定更新以前的映射:

USER> (fset:with * :c 3)
#{| (:A 0) (:B 1) (:C 3) |}
使用新的
:b
绑定更新上一个映射,该绑定会遮挡上一个映射:

USER> (fset:with * :b 4)
#{| (:A 0) (:B 4) (:C 3) |}
所有中间贴图均未修改:

USER> (list * ** *** )
(#{| (:A 0) (:B 4) (:C 3) |}
 #{| (:A 0) (:B 1) (:C 3) |} 
 #{| (:A 0) (:B 1) |})

你能举例说明你想如何在代码中使用哈希表吗?@Gwang JinKim举个例子,假设我有一个名为base的非空哈希表,我想在循环中对其进行非破坏性修改。对于每次迭代,我都会在基本哈希表中添加一个新条目,并将修改后的哈希表保存在一个变量中,使基本哈希表保持不变。能否给出一些示例,说明如何在代码中使用哈希表?@Gwang JinKim例如,假设我有一个名为base的非空哈希表,我希望在循环中对其进行非破坏性修改。对于每次迭代,我都会在基本哈希表中添加一个新条目,并将修改后的哈希表保存在一个变量中,使基本哈希表保持不变。fset真是太棒了!非常感谢。这是公共地图上的clojure地图