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

时间:2012-11-10 23:10:33

标签: emacs lisp elisp

对于令人费解的头衔,我尽力让自己有意识。好吧,如果你有更好的主意,请改变它!

不要混淆你,这是 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)))

这会将alist转换为由哈希表组成的树,其中每个表都包含键,这些键是我稍后搜索和替换的字符串的字符。这需要优化搜索多个键,可能在大文本中使用相似的前缀,然后用相应的键替换它们。

问题在于,在内部循环中,我想要将branch初始化为trie,然后在所有后续迭代中将其设置为新的散列表(为字符创建而不是已知前缀的一部分),或者已经为前缀中的字符创建的哈希表。

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

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

这就是为什么我有一个愚蠢的first-time旗帜,我可以避免。我可以以某种方式使用initially形式,或者以某种其他方式重构函数以避免此标志和额外的if吗?这个函数快速并不是很重要(搜索应该很快,但树的构建不需要),但它看起来很丑陋:))

4 个答案:

答案 0 :(得分:3)

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

如果您将try的定义视为更模块化的数据结构,您可以从以下两个函数开始:

(defun trie-create ()
  (make-hash-table :test 'equal))

(defun trie-put (key value trie)
  (if (equal key "")
      (puthash t value trie)      
    (let* ((c (substring key 0 1))
           (child-trie (gethash c trie)))
      (unless child-trie
        (setq child-trie (trie-create))
        (puthash c child-trie trie))
      (trie-put (substring key 1) value child-trie))))

(正如你所看到的,我建议在这里递归而不是嵌套的loop - 这可能是一个品味问题,但在我看来,这使得代码更简单,更清晰。)< / p>

接下来,您可能希望添加trie-gettrie-remove等功能。

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

(let ((trie (trie-create)))
  (mapc '(lambda (x) (trie-put (car x) (cdr x) trie)) alist))

答案 1 :(得分:2)

未测试:

(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)))

答案 2 :(得分:2)

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

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

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

答案 3 :(得分:1)

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

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