Emacs 循环的初始子句,需要在声明变量之前引用它

Emacs 循环的初始子句,需要在声明变量之前引用它,emacs,lisp,elisp,Emacs,Lisp,Elisp,对不起,标题太复杂了,我尽了最大努力让它有意识。好吧,如果你有更好的主意,改变它 为了不让您感到困惑,这是Emacs Lisploop,而不是Common Lisp: (defun hxswfml-build-trie (alist) "Builds a trie (a list, containing number of hash-maps, each hash-map uses single character for a key, except for `t' symbol, whic

对不起,标题太复杂了,我尽了最大努力让它有意识。好吧,如果你有更好的主意,改变它

为了不让您感到困惑,这是Emacs Lisp
loop
,而不是Common Lisp:

(defun hxswfml-build-trie (alist)
  "Builds a trie (a list, containing number of hash-maps, each hash-map
uses single character for a key, except for `t' symbol, which, if present
as a key is the key for the value one has to substitute with."
  (loop for (key . value) in alist
        with trie = (make-hash-table)
        do (loop for c across key
                 with branch =
                 (or (gethash c trie)
                     (puthash c (make-hash-table) trie))
                 with first-time = t
                 do (if first-time (setq first-time nil)
                      (setq branch
                            (or (gethash c branch)
                                (puthash c (make-hash-table) branch))))
                 finally (puthash t value branch))
        finally (return trie)))
这将一个列表转换为一个由哈希表组成的树,其中每个表都包含键,这些键是我稍后搜索和替换的字符串的字符。这是优化搜索多个关键字所必需的,这些关键字在大量文本中可能具有相似的前缀,然后用相应的关键字替换它们

问题是,在内部循环中,我希望初始化
分支
trie
,然后在以后的所有迭代中将其设置为新哈希表(为尚未成为已知前缀一部分的字符创建)或已从前缀为该字符创建的哈希表

理想情况下,它看起来像:

for branch = (or (and branch (gethash c branch)) (puthash c (make-hash-table) trie))
;;                    ^-----------------^------- cannot reference it here

这就是为什么我有愚蠢的
第一次
标志,我本来可以避免的。我是否可以先使用
表单,或者以其他方式重新构造函数,以避免使用此标志和额外的
if
?这个函数是否快速并不重要(搜索应该快速,但树的构建不需要),但它看起来很难看:)

我不确定我是否理解它,但在Common Lisp中我会这样做:

(loop for i = (foo) then (1+ i) ...)

由于您明确提到重构是一个潜在的选项,因此我建议将函数组合的两个操作分开:创建trie和将元素插入trie

如果你将尝试定义为一个更模块化的数据结构,你可以从以下两个函数开始:

(定义trie create()
(使哈希表:测试“相等”)
(defun trie put(键值trie)
(如果(相等键“”)
(putt值trie)
(let*((c(子串键01))
(子trie(gethash c trie)))
(儿童除外)
(setq子trie(trie创建))
(puthash c child trie trie))
(trie put(子字符串键1)值子trie)))
(正如您所看到的,我建议在这里使用递归而不是嵌套的
循环
s-这可能是一个品味问题,但在我看来,这使代码更简单、更清晰。)

接下来,您可能需要添加函数,如
trie get
trie remove

使用此代码,将列表转换为trie将成为创建新trie,然后使用上述函数将所有元素插入其中的组合:

(let((trie(trie-create)))
(mapc’(lambda(x)(trie-put(car x)(cdr x)trie))列表)
未经测试:

(defun hxswfml-build-trie (alist)
  "Builds a trie (a list, containing number of hash-maps, each hash-map
uses single character for a key, except for `t' symbol, which, if present
as a key is the key for the value one has to substitute with."
  (loop for (key . value) in alist
        with trie = (make-hash-table)
        for leaf = (reduce (lambda (branch c)
                             (or (gethash c branch)
                                 (puthash c (make-hash-table) branch)))
                           key :initial-value trie)
        do (puthash t value leaf)
        finally (return trie)))

请注意,已经有一个
trie.el
包在Elisp中实现了常规尝试(免责声明:我是包的作者)。它已经存在了好几年了,在最近几年的Emacsen中可以从GNU ELPA获得。也可以从以下网站下载

默认情况下,它使用AVL树作为尝试的基础数据结构,而不是哈希表。但在创建trie时,可以指定不同的底层数据结构。所有标准的trie搜索(加上一些额外的)都已实现,并且与底层数据结构无关


这不会直接回答您的问题,但可能会节省您的工作。

谢谢您的详细解释。你的观点经过深思熟虑。当然,从字符串到作为键的字符的更改非常简单,就像将
equal
更改为
eql
一样。但您是对的,键的长度受Emacs递归深度的限制。如果Emacs支持尾部递归优化,这就不会是问题。从更大的角度来看,也许我的答案剩下的最有趣的一点可能是将
trie
数据结构模块化为更多分离的、可重用的函数。这似乎是一个有趣的问题,但我仍然不明白您在读了两遍之后想要做什么。你能举一些例子说明这是如何工作的吗。特别是你的最终解决方案是什么?怀远的代码似乎不正确。