【问题标题】:Clojure: Remove value in all leaves of a treeClojure:删除树的所有叶子中的值
【发布时间】:2020-12-21 14:21:06
【问题描述】:

我在 clojure 中实现了一个 trie,但我正在努力使用 remove-values 函数。我使用的结构是这样的:

(def trie {\a {:value #{"val1" "val2"}}
           \b {\c     {:value #{"val1"}}
               :value #{"val2"}}})

我想调用这样的函数(remove-value trie "val1") 并获得一个结构,其中“val1”的所有实例都从离开节点的集合中删除。生成的 trie 如下所示:

{\a {:value #{"val2"}}
 \b {\c     {:value #{}}
     :value #{"val2"}}}

或者更好:

{\a {:value #{"val2"}}
 \b {:value #{"val2"}}}

从我在这里看到的 SO 这可能可以在五行 clojure 中完成,但我不知道如何。此外,我并没有与数据结构结婚,如果您需要更改它以使惯用版本工作,请随意,只要它仍然是一个尝试。

【问题讨论】:

    标签: clojure tree trie


    【解决方案1】:

    一种方法是使用postwalk。例如

    (def trie {\a {:value #{"val1" "val2"}}
               \b {\c     {:value #{"val1"}}
                   :value #{"val2"}}})
    ; #'user/trie
    (require '[clojure.walk :refer [postwalk]])
    ; nil
    (doc postwalk)
    ; -------------------------
    ; clojure.walk/postwalk
    ; ([f form])
    ;   Performs a depth-first, post-order traversal of form.  Calls f on
    ;   each sub-form, uses f's return value in place of the original.
    ;   Recognizes all Clojure data structures. Consumes seqs as with doall.
    ; nil
    (postwalk #(if (set? %) (disj % "val1") %) trie)
    ; {\a {:value #{"val2"}}, \b {\c {:value #{}}, :value #{"val2"}}}
    
    

    您还可以从那里对地图进行过滤(dissoc 字符键,如果它们不再包含任何值)

    【讨论】:

      【解决方案2】:

      我建议使用非常规则的 trie 结构: 一个映射,其第一个条目始终为 :value,其值为 #{}。 所有其他键都是子项,它们包含自己尝试的值。 (意味着他们在开头有一个:value 键!)

      然后为哈希映射定义car/firstcdr/restconsmap (-map)。

      (defn first-map [m]
        (let [k (first (keys m))]
           {k (k m)}))
      
      (defn rest-map [m]
        (into {} (map (fn [k] {k (k m)}) (rest (keys m)))))
      
      (defn cons-map [m1 m2]
        (into {} (concat m1 m2)))
      
      (defn map-map [f m & args]
        (into {} (map (fn [k] {k (apply f (k m) args)}) 
                      (keys m))))
      

      试试看:

      (def m {:a 1 :b 2 :c 3})
      
      (first-map m)                 ;; => {:a 1}
      (rest-map m)                  ;; => {:b 2 :c 3}
      (cons-map {:a 1} {:b 2 :c 3}) ;; => {:a 1 :b 2 :c 3}
      (map-map #(+ % 1) m)          ;; => {:a 2, :b 3, :c 4}
      (map-map #(+ %1 %2) m 1)      ;; => {:a 2, :b 3, :c 4}
      

      然后,使用它们定义remove-value

      (defn remove-value [trie val]
        (cons-map {:value (disj (:value (first-map trie)) val)}
                  (map-map remove-value (rest-map trie) val)))
      

      原来,我写道:

      (defn remove-value [trie val]
        (cond (empty? (rest-map trie))
              {:value (disj (:value (first-map trie)) val)}
              :else (cons-map {:value (disj (:value (first-map trie)) val)}
                              (map-map remove-value (rest-map trie) val))))
      

      但后来意识到 :else 分支会自动执行此操作,因为 map-map 在空的 (rest-map trie) 上只会导致 {:value (disj (:value (first-map trie)) val)} - 也可以表示为 (let [[k v] (first-map trie)] {k (disj v val)}).

      您的trie 将是:

      (def trie {:value #{}
                 :a {:value #{"val1" "val2"}}
                 :b {:value #{"val2"}
                     :c {:value #{"val1"}}}})
      

      注意:顶层的树总是以 :value #{}(树的根)。 :value 的值将始终是一个集合,要么是空的 #{},要么包含一个或多个元素。 所以,每个孩子本身就是一个完整的trie

      user=> (remove-value trie "val1")
      {:value #{}, :a {:value #{"val2"}}, :b {:value #{"val2"}, :c {:value #{}}}}
      user=> (remove-value trie "val2")
      {:value #{}, :a {:value #{"val1"}}, :b {:value #{}, :c {:value #{"val1"}}}}
      

      【讨论】:

      • 感谢您非常详尽的解释。我喜欢每个分支的统一性。
      【解决方案3】:
      (require '[com.rpl.specter :as s])
      
      (let [trie {\a {:value #{"val1" "val2"}}
                      \b {\c     {:value #{"val1"}}
                          :value #{"val2"}}}]
              (->> trie
                   (s/setval (s/compact (s/walker set?) (s/set-elem "val1")) s/NONE)
                   (s/setval (s/compact (s/walker #(and (map? %) (empty? %)))) s/NONE)))
      

      【讨论】:

        【解决方案4】:

        这个比我想象的要复杂!当我试图快速回答时,我至少犯了两个错误。这是来自a template project 的工作版本:

        (ns tst.demo.core
          (:use demo.core  tupelo.test)
          (:require
            [clojure.walk :as walk]
            [tupelo.core :as t]
            ))
        
        (defn remove-value
          [data remove-val]
          (walk/postwalk
            (fn [item]
              (if (instance? clojure.lang.MapEntry item)
                (let [[k v] item] ; destructure MapEntry into key/val pair
                  ; must return a MapEntry (or a 2-vector equivalent)
                  [k (if (= k :value)
                       (disj v remove-val)
                       v)]) ; must return item if unchanged
                item)) ; must return item if unchanged
            data))
        
        (dotest
          (let [my-data {\a {:value #{"val1" "val2"}}
                         \b {\c     {:value #{"val1"}}
                             :value #{"val2"}}}
                ]
            (is= (remove-value my-data "val1")
              {\a {:value #{"val2"}}
               \b {\c     {:value #{}}
                   :value #{"val2"}}})))
        

        如果使用cond-it->,可以稍微简化一下

        (defn remove-value
          [data remove-val]
          (walk/postwalk
            (fn [item]
              (t/cond-it-> item
                (instance? clojure.lang.MapEntry it)
                (let [[k v] it] ; destructure MapEntry into key/val pair
                  (t/map-entry ; must return a MapEntry (or a 2-vector equivalent)
                    k (t/cond-it-> v
                        (= k :value) (disj it remove-val))))))
            data))
        

        您可能也对better-cond感兴趣。


        对于更复杂的树结构操作,您可能对Tupelo Forest感兴趣。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-09-27
          • 1970-01-01
          • 2019-09-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多