【问题标题】:Non destructive modify hash table非破坏性修改哈希表
【发布时间】:2021-01-31 13:03:16
【问题描述】:

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

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

但是对setf 的调用会修改*hash-table*,从而破坏原始文件。我有一个应用程序,我想利用哈希表查找的效率,但我也想非破坏性地修改它们。我看到的解决方法是在对其进行操作之前复制原始哈希表,但这在我的情况下是不切实际的,因为我正在处理的哈希表包含数千个元素并复制大型哈希表,例如循环首先会否定使用它们的计算效率优势。

【问题讨论】:

  • 你能举一些你想如何在代码中使用哈希表的例子吗?
  • @Gwang-JinKim 例如,假设我有一个名为 base 的非空哈希表,我想在循环中对其进行非破坏性修改。对于每次迭代,我都会在基础哈希表中添加一个新条目,并将修改后的哈希表保存在一个变量中,并保持基础不变。

标签: lisp common-lisp hashtable sbcl setf


【解决方案1】:

根据您的需要,您可以只使用关联列表,使用assoc 和其他函数在现有绑定之上建立新绑定。 assoc 返回第一个匹配元素的事实意味着您可以隐藏绑定:

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

=> ((:b . 3) (:a . 1) (:b . 2))

如果您在结果列表中调用(assoc :b list),则条目将为(:b . 3),但原始列表未修改。

FSet

如果关联列表还不够,FSet 库为 Common Lisp 提供纯粹的函数式数据结构,例如映射,它们是不可变的哈希表。它们被实现为平衡树,这比简单的方法要好。还有其他更高效的数据结构,但您可能需要自己实现它们(Hash array mapped trie编辑:参见https://github.com/danshapero/cl-hamt,感谢@Flux))。话虽这么说,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) |})

【讨论】:

  • fset 真的很不错!谢谢!所以这是普通 lisp 上的 clojure 映射?还有其他用于普通 lisp 的 Clojure 东西吗?
  • 哈希数组映射尝试的实现:cl-hamt.
  • @Flux 谢谢!
【解决方案2】:

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

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

;; 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*扩展的哈希表

(setf (get '*h3* 'extendeds) '(*h2* *h1*))
(setf (get '*h2* 'extendeds) '(*h1*))

(defun collect-extendeds (hts)
  (let ((res (loop for ht in hts
                   nconcing (get ht 'extendeds))))
    (remove-duplicates res)))

;; this function can recursively retrieve all hashtables
(defun get-extendeds* (hts &optional (acc '()))
  (let ((hts (if (listp hts) hts (list hts))))
      (let ((nexts (collect-extendeds hts)))
        (cond ((every #'null nexts) (nreverse (remove-duplicates (append hts acc))))
              (t (get-extendeds* nexts (remove-duplicates (append hts acc))))))))

;; write a macro to retrieve key's value from all downstream hashtables
(defmacro geth (key ht)
  `(mget ,key ,(get-extendeds* ht)))

(geth 'a *h3*)
;; 1 ;
;; NIL  ;; NIL because it was not in *h3* directly but in one of the hashtables
;; which it extends.

;; problem is if 'NIL is a value of an existing key,
;; one would still get 'NIL NIL.

【讨论】:

    猜你喜欢
    • 2012-11-09
    • 1970-01-01
    • 2013-08-28
    • 1970-01-01
    • 2014-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多