从Clojure中以向量表示的树中获取边缘

问题描述

我有一棵这样的树:

let problemSet = [problemA,problemB,...]
let shuffledProblemSet = shuffle(problemSet) // fisher-yates shuffle

// to present a "random" non-repeating problem,just traverse the shuffled problems in order
let index = 0

let nextProblem = shuffledProblemSet[index++]
// Now,send this to discord...

const embed = new discord.MessageEmbed()
  .addField(nextProblem.question,invisible) //Here I put a random question
  .addField(nextProblem.answers) // And here I would want to put the appropriate answer choices
  // yada yada

如何获得边缘,即:

[:root
  [:a [:b [:c [:g]]]]
  [:d [:e [:f [:g]]]]]

解决方法

这是我在检查您的答案之前想到的。除非我错过了一些东西,否则似乎更惯用了。

(defn vec->edges [v-tree]
  (->> v-tree
       (tree-seq vector? next)
       (mapcat (fn [[a & children]]
                 (map (fn [[b]] [a b]) children)))))
,

此方法使用一个基本循环(不需要额外的库或递归):

(defn get-edges [tree]
  (loop [subtrees [tree]
         output []]
    (if (empty? subtrees)
      output
      (let [[[root & first-subtrees] & subtrees] subtrees]
        (recur (into subtrees first-subtrees)
               (into output (map #(-> [root (first %)])) first-subtrees))))))

在示例数据上对其进行测试:

(get-edges [:root
            [:a [:b [:c [:g]]]]
            [:d [:e [:f [:g]]]]])
;; => [[:root :a] [:root :d] [:d :e] [:e :f] [:f :g] [:a :b] [:b :c] [:c :g]]

这是另一种基于延迟序列的方法:

(defn get-edges2 [tree]
  (->> [tree]
       (iterate #(into (rest %) (rest (first %))))
       (take-while seq)
       (mapcat (fn [subtrees]
                 (let [[[root & sub] & _] subtrees]
                   (map #(-> [root (first %)]) sub))))))
,

我真的很喜欢您提出问题的方式Scott Klarenbach,这真的是综合性的。

我提出了一种解决方案,解决原始的Clojure问题。棘手的部分是递归调用的位置以及如何处理这些递归调用的结果。

(def data
  [:root
   [:a [:b [:c [:g]]]]
   [:d [:e [:f [:g]]]]])

(defn get-edges [collection]
  (let [root (first collection)
        branches (rest collection)]
    (if (empty? branches)
      []
      (let [edges
            (mapv (fn [branch] [root (first branch)]) branches)
            sub-edges
            (->> branches
                 (mapcat (fn [branch] (get-edges branch)))
                 vec)]
        (if (empty? sub-edges)
          edges
          (vec (concat edges sub-edges)))))))

(get-edges data)
;; => [[:root :a] [:root :d] [:a :b] [:b :c] [:c :g] [:d :e] [:e :f] [:f :g]]
,

没有简单的内置方法可以做到这一点。最简单的方法是滚动自己的递归。

这是具有唯一打h标签(无重复)的解决方案:

(ns tst.demo.core
  (:use tupelo.core tupelo.test)
  (:require
    [schema.core :as s]))

(def result (atom []))

(s/defn walk-with-path-impl
  [path :- [s/Any] ; parent path is a vector
   user-fn
   data :- [s/Any]] ; each data item is a hiccup vector
  (let [tag       (xfirst data)
        path-next (append path tag)]
    (user-fn path data)
    (doseq [item (xrest data)]
      (walk-with-path-impl path-next user-fn item))))

(defn walk-with-path!
  [user-fn data]
  (walk-with-path-impl [] user-fn data))

(s/defn print-edge
  [path :- [s/Any] ; parent path is a vector
   data :- [s/Any]] ; each data item is a hiccup vector
  (when (not-empty? path)
    (let [parent-node (xlast path)
          tag         (xfirst data)
          edge        [parent-node tag]]
      (swap! result append edge))))

单元测试显示输入数据和结果

(dotest
  (let [tree [:root
              [:a
               [:b
                [:c
                 [:z]]]]
              [:d
               [:e
                [:f
                 [:g]]]]]]
    (reset! result [])
    (walk-with-path! print-edge tree)
    (is= @result
      [[:root :a] [:a :b] [:b :c] [:c :z]
       [:root :d] [:d :e] [:e :f] [:f :g]])))

Here are the docs用于便捷功能,例如xfirstappend