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