0

Basically, I am trying to implement this algorithm, though maybe there's a better way to go about it.

  • starting at the root
  • check each child of current node for children with leafs (child of child)
  • if any child-of-child nodes of the current node have leafs, record path to current node (not to child) and do not continue down that path any farther.
  • else continue DFS

non-functional pseudo code:

def find_paths(node):
    for child in node.children:
       if child.children.len() == 0
          child_with_leaf = true
    if child_with_leaf
       record path to node
    else
       for child in node.children
           find_paths(child)

For example:

:root
  |- :a
  |   +- :x
  |       |- :y
  |       |   +- :t
  |       |       +- :l2
  |       +- :z
  |          +- :l3
  +- :b
      +- :c
          |- :d
          |   +- :l4
          +- :e
              +- :l5

The result would be:

[[:root :a]
 [:root :b :c]]

Here is my crack at it in clojure:

(defn atleast-one?
  [pred coll]
  (not (nil? (some pred coll))))

; updated with erdos's answer
(defn children-have-leaves?
  [loc]
  (some->> loc
           (iterate z/children)
           (take-while z/branch?)
           (atleast-one? (comp not empty? z/children))))

(defn find-paths
  [tree]
  (loop [loc (z/vector-zip tree)
         ans nil]
    (if (z/end? loc)
      ans
      (recur (z/next loc)
             (cond->> ans
                      (children-have-leaves? loc)
                      (cons (->> loc z/down z/path (map z/node)))))))
  )

(def test-data2
  [:root [:a [:x [:y [:t [:l2]]] [:z [:l3]]]] [:b [:c [:d [:l4]] [:e [:l5]]]]]
  )

Update: fixed the crash with erdos' answer below, but I think there's still a problem with my code since this prints every path and not the desired ones.

adapt-dev
  • 1,608
  • 1
  • 19
  • 30

3 Answers3

2

The exception comes from your children-have-leaves? function.

The (not (empty? z/children)) expression fails, because z/children is a function, however, empty? must be invoked on a collection.

What you need is a predicate that returns true if a node has children, like: (fn [x] (not (empty? (z/children x)))) or shorter: (comp not empty? z/children)

The correct implementation:

(defn children-have-leaves?
  [loc]
  (some->> loc
           (iterate z/children)
           (take-while z/branch?)
           (atleast-one? (comp not empty? z/children))))
erdos
  • 3,135
  • 2
  • 16
  • 27
  • Thanks for your answer. that solves my crash. But I guess there's a problem with my code since it now prints every path and not just desired ones. – adapt-dev May 13 '19 at 20:56
2

I assume you have referenced my previous answer related to zipper. But please note that my previous answer uses vector-zip as is and hence you have to navigate it like a vector-zip - which you may have to wrap your head around how the two cursors work. To simplify the navigation, I suggest you create your own zipper for your tree structure. I.e.

(defn my-zipper [root]
  (z/zipper ;; branch?
            (fn [x]
              (when (vector? x)
                (let [[n & xs] x] (and n (-> xs count zero? not)))))
            ;; children
            (fn [[n & xs]] xs)
            ;; make-node
            (fn [[n & _] xs] [n xs])
            root))

then the solution will be similar to my other answer:

(def test-data2
  [:root 
   [:a 
    [:x 
     [:y 
      [:t [:l2]]] 
     [:z [:l3]]]] 
   [:b 
    [:c 
     [:d [:l4]] 
     [:e [:l5]]]]])

(->> test-data2
     my-zipper
     (iterate z/next)
     (take-while (complement z/end?))
     (filter (comp children-with-leaves? z/node))
     (map #(->> % z/path (map z/node)))
     set)
;; => #{(:root :a :x) (:root :a :x :y) (:root :b :c)}

where the main logic is simplified to:

(defn children-with-leaves? [[_ & children]]
  (some (fn [[c & xs]] (nil? xs)) children))
rmcv
  • 1,956
  • 1
  • 9
  • 8
  • yes, your other answer inspired this. I tried your solution here and I get `#{}` I am having trouble seeing where it is failing. – adapt-dev May 14 '19 at 17:25
  • The root should be a branch like this: `(def test-data2 [:root [:a [:x [:y [:t [:l2]]] [:z [:l3]]]] [:b [:c [:d [:l4]] [:e [:l5]]]]])` – rmcv May 14 '19 at 23:45
  • As each node is of this structure: `[node-name & children]` – rmcv May 14 '19 at 23:55
  • One thing though - how do you get this to stop descending the tree once a match has been found? The non-functional code in my question will stop at `(:root :a :x)` and not return `(:root :a :x :y)`. It looks like your code searches the entire tree rather than stopping down a certain branch. – adapt-dev May 16 '19 at 17:12
  • DFS will get you to `(:root :a :x :y, :t)` as the first path meeting the condition. `(:root :a :x, :z)` is next so you won't be able to retract previous answer. Looks like you are asking for BFS. – rmcv May 17 '19 at 14:33
0

If you want to process tree-like data structures, I would highly recommend the tupelo.forest library.

I don't understand your goal, though. Nodes :a and :c in your example are not equally distant from the closest leaf.

Actually, I just noticed that the tree in your example is different than the tree in your code attempt. Could you please update the question to make them consistent?


Here is an example of how you could do it:

(dotest ; find the grandparent of each leaf
  (hid-count-reset)
  (with-forest (new-forest)
    (let [data              [:root
                             [:a
                              [:x
                               [:y [:t [:l2]]]
                               [:z [:l3]]]]
                             [:b [:c
                                  [:d [:l4]]
                                  [:e [:l5]]]]]
          root-hid          (add-tree-hiccup data)
          leaf-paths        (find-paths-with root-hid [:** :*] leaf-path?)
          grandparent-paths (mapv #(drop-last 2 %) leaf-paths)
          grandparent-tags  (set
                              (forv [path grandparent-paths]
                                (let [path-tags (it-> path
                                                  (mapv #(hid->node %) it)
                                                  (mapv #(grab :tag %) it))]
                                  path-tags)))]
      (is= (format-paths leaf-paths)
        [[{:tag :root} [{:tag :a} [{:tag :x} [{:tag :y} [{:tag :t} [{:tag :l2}]]]]]]
         [{:tag :root} [{:tag :a} [{:tag :x} [{:tag :z} [{:tag :l3}]]]]]
         [{:tag :root} [{:tag :b} [{:tag :c} [{:tag :d} [{:tag :l4}]]]]]
         [{:tag :root} [{:tag :b} [{:tag :c} [{:tag :e} [{:tag :l5}]]]]]])
      (is= grandparent-tags
          #{[:root :a :x] 
            [:root :a :x :y] 
            [:root :b :c]} ))))
Alan Thompson
  • 29,276
  • 6
  • 41
  • 48