【问题标题】:Clojure: How to generate a 'trie'?Clojure:如何生成“trie”?
【发布时间】:2010-11-29 23:24:36
【问题描述】:

鉴于以下...

(def inTree
 '((1 2)
   (1 2 3)
   (1 2 4 5 9)
   (1 2 4 10 15)
   (1 2 4 20 25)))

你会如何把它变成这个 trie?

(def outTrie
 '(1
    (2 ()
       (3 ())
       (4 (5
            (9 ()))
          (10
            (15 ()))
          (20
            (25 ()))))))

【问题讨论】:

    标签: clojure tree functional-programming trie


    【解决方案1】:

    作为一般方法,我会这样做:

    • 编写几个函数来创建一个 trie 并将新元素插入到 trie 中。
    • 创建一个新的 trie。
    • 遍历输入列表并将每个元素插入到 trie 中。

    这个问题非常适合递归实现。如果可能,我会以此为目标。

    【讨论】:

      【解决方案2】:

      我确信有一个更漂亮的方法(有!看布赖恩的回答更好):

      (defn find-in-trie
        "Finds a sub trie that matches an item, eg:
        user=> (find-in-trie '(1 (2) (3 (2))) 3)
        (3 (2))"
        [tr item]
        (first (for [ll (rest tr) :when (= (first ll) item)] ll)))
      
      
      (defn add-to-trie
        "Returns a new trie, the result of adding se to tr, eg:
        user=> (add-to-trie nil '(1 2))
        (1 (2))"
        [tr se]
        (cond
          (empty? se) tr
          (empty? tr) (add-to-trie (list (first se)) (rest se))
          :else (if-let [st (find-in-trie tr (first se))]
                  (cons (first tr)
                        (cons (add-to-trie st (rest se))
                              (filter (partial not= st) (rest tr))))
                  (cons (first tr)
                        (cons (add-to-trie (list (first se)) (rest se))
                              (rest tr))))))
      
      (def in '((1 2)
                (1 2 3)
                (1 2 4 5 9)
                (1 2 4 10 15)
                (1 2 4 20 25)))
      
      (reduce add-to-trie '(nil) in)
      

      -> (nil (1 (2 (4 (20 (25)) (10 (15)) (5 (9))) (3))))

      请注意,我选择使用 nil 作为根节点,并且没有费心保留空列表来表示没有子节点。实际上这样做是不正确的,因为它不保留子字符串标识。

      【讨论】:

      • 谢谢。查看常见问题的代码有助于发现语言的习语。
      • 不用担心,看看布赖恩的回答,它更加地道和正确。
      【解决方案3】:

      列表在这里非常笨拙,更不用说效率低下。在 Clojure 中,在适当的时候使用向量和哈希映射和集合更为惯用。使用哈希映射:

      (def in-tree
       '((1 2)
         (1 2 3)
         (1 2 4 5 9)
         (1 2 4 10 15)
         (1 2 4 20 25)))
      
      (defn add-to-trie [trie x]
        (assoc-in trie `(~@x :terminal) true))
      
      (defn in-trie? [trie x]
        (get-in trie `(~@x :terminal)))
      

      如果您希望它打印 sorted,您可以改用 sorted-maps,但您必须编写自己的 assoc-in 版本,该版本一直使用 sorted maps。无论如何:

      user> (def trie (reduce add-to-trie {} in-tree))
      #'user/trie
      user> trie
      {1 {2 {4 {20 {25 {:terminal true}}, 10 {15 {:terminal true}}, 5 {9 {:terminal true}}}, 3 {:terminal true}, :terminal true}}}
      user> (in-trie? trie '(1 2))
      true
      user> (in-trie? trie '(1 2 4))
      nil
      user> (in-trie? trie '(1 2 4 20 25))
      true
      

      【讨论】:

      • 很好的答案并强调我实际上错误地忽略了子字符串问题。我会建议一个稍微不同的 in-tri?: (defn in-trie? [trie x] (:terminal (get-in trie x) false)) user=> (in-trie?trie '(1 2 4)) false 使其成为真正的谓词,避免拼接语法。
      • 也许是::terminal,以防我们尝试包含:terminal 的序列?
      • 我已经修复了发现的错误@GregFooter。随意撤消编辑。与@TimothyPratley 不同,我发现您对取消引用拼接的使用很有帮助,因为它将它显示为对数据的操作——而不是被困在宏体中。
      【解决方案4】:

      这是一个干净的解决方案。这修复了 Brian 的 add-to-trie 方法的错误,因为它目前依赖于您以长度递增的顺序插入 seq。它还允许通过前缀查询 trie,这是一个常见的用例。

      注意这里的内存使用率更高,因为它将值存储在 trie 的叶节点中,因此您可以执行搜索。

      (defn add-to-trie [trie x]
        (assoc-in trie x (merge (get-in trie x) {:val x :terminal true})))
      
      (defn in-trie? [trie x]
        "Returns true if the value x exists in the specified trie."
        (:terminal (get-in trie x) false))
      
      (defn prefix-matches [trie prefix]
        "Returns a list of matches with the prefix specified in the trie specified."
        (keep :val (tree-seq map? vals (get-in trie prefix))))
      
      (defn build-trie [coll]
        "Builds a trie over the values in the specified seq coll."
        (reduce add-to-trie {} coll))
      

      【讨论】:

      • 所以我猜如果你总是使用相同数量的键,Brian 的版本会很好?
      • 你对prefix-matches的定义使用了函数map-filter,但是标准库中没有这个函数。我试图对它的功能进行逆向工程,但这并不明显。你能发布它的定义吗?
      • map-filter 类似于keep,在核心库中。
      • 我已经对你提到的@Brian's answer 的错误进行了最低限度的修复。
      • 当只有终端节点有:val 条目时,您选择同时包含:val 键和:terminal 键是否有原因?
      猜你喜欢
      • 2020-02-01
      • 1970-01-01
      • 2011-12-12
      • 1970-01-01
      • 2014-03-05
      • 2013-02-07
      • 1970-01-01
      • 2011-08-07
      相关资源
      最近更新 更多