如何使用高阶函数生成Clojure中所有子树的列表?

如何使用高阶函数生成Clojure中所有子树的列表?,clojure,tree,Clojure,Tree,给定一棵树,如何使用高阶函数生成Clojure中所有(适当)子树的列表 背景 我正在研究代码2019的出现。问题从一个问题开始。我使用Clojure列表将邻接列表表示为一个n元树,其结构如下 不是叶的节点是一个包含两部分的列表:第一部分是表示树的该部分的根的元素;第二部分是表示根的分支的n个元素。叶子是以关键字作为唯一元素的列表。因此,我代表一棵树的形式 B -- C / A \ D 以下是: (:A (:B (:C)) (:D)) 使用递归的解决方案 我想列出给定树的每个适当子

给定一棵树,如何使用高阶函数生成Clojure中所有(适当)子树的列表

背景

我正在研究代码2019的出现。问题从一个问题开始。我使用Clojure列表将邻接列表表示为一个n元树,其结构如下

不是叶的节点是一个包含两部分的列表:第一部分是表示树的该部分的根的元素;第二部分是表示根的分支的n个元素。叶子是以关键字作为唯一元素的列表。因此,我代表一棵树的形式

  B -- C
 /
A
 \
  D
以下是:

(:A (:B (:C)) (:D))
使用递归的解决方案

我想列出给定树的每个适当子树。我知道如何使用递归进行此操作,如下所示:

(defn subtrees
  [tree]
  (loop [trees tree
         results '()]
    (if (empty? trees)
      results
      (let [subtree #(if (keyword? (first %)) (rest %) nil)
            leaf? #(and (list %) (keyword? (first %)) (= (count %) 1))
            sub (subtree (first trees))]
        (if (every? leaf? sub)
          (recur (rest trees) (into results sub))
          (recur (into (rest trees) sub) (into results sub)))))))
因此,我使用
结果
:我从
中的树开始,然后在每个步骤将不是一个或多个叶子的子树添加到
结果
(或者:如果我有一个或多个叶子,则直接添加到
结果
)。这给了我一个
的所有正确子树的列表,这是函数的要点。有非常详细的注释和一堆测试用例

我的问题

我想知道如何使用高阶函数实现同样的功能。我真正想做的是使用
map
并递归调用函数:在每个阶段,只需对列表中的每个元素调用
subtree
。我遇到的问题是,当我这样做的时候,我最终会遇到一大堆括号,并且无法始终深入到这些括号中去找到子树。大概是这样的:

(defn subt
  [trees]
  (let [subtree #(if (keyword? (first %)) (rest %) nil)
        leaf? #(and (list %) (keyword? (first %)) (= (count %) 1))
        sub (subtree trees)]
    (if (every? leaf? sub)
      nil
      (cons (map subt sub) trees))))
(defn solve [data]
  (let [items (clojure.string/split data #"\)|\s+")
        pairs (partition 2 items)
        lookup (reduce (fn [acc [par ch]] (assoc acc ch par)) {} pairs)
        count-parents #(->> %
                            (iterate lookup)
                            (take-while identity)
                            count
                            dec)]
    (apply + (map count-parents (distinct items)))))

(def data "COM)B
           B)C
           C)D
           D)E
           E)F
           B)G
           G)H
           D)I
           E)J
           J)K
           K)L")

#'user/data

user> (solve data)
;;=> 42

user> (solve (slurp "./orb.txt"))
;;=> 402879 ;; for my task input data

你可以看到
(map subt sub)
就是我在这里要说的,但是我在使用
map
时遇到了很多困难,尽管我觉得这就是我想要的高阶函数。我考虑使用
reduce
作为上面
子树中
循环的替代;但是由于
通过添加子树进行更改,我认为
reduce
不合适,至少在我构建的循环中是这样我也应该说,我对图书馆做这项工作不感兴趣;我想知道如何使用核心函数解决此问题。提前感谢。

您可以使用函数
与家长一起散步,从。代码如下:

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

(def orbits
  [:com
   [:b
    [:g
     [:h]]
    [:c
     [:d
      [:i]
      [:e
       [:f]
       [:j
        [:k
         [:l]]]]]]]])

(def sum (atom 0))

(defn parent-levels
  [parents]
  (t/it-> parents
    (count it)
    (/ it 2)))

(defn count-orbits
  [data]
  (t/walk-with-parents-readonly data
    {:enter (fn [parents item]
              (when (vector? item)
                (let [levels (parent-levels parents)]
                  (t/spyx [(first item) levels])
                  (swap! sum + levels))))}))

(dotest
  (count-orbits orbits)
  (t/spyx @sum))
结果

--------------------------------------
   Clojure 1.10.2-alpha1    Java 14
--------------------------------------

Testing tst.demo.core
[(first item) levels] => [:com 0]
[(first item) levels] => [:b 1]
[(first item) levels] => [:g 2]
[(first item) levels] => [:h 3]
[(first item) levels] => [:c 2]
[(first item) levels] => [:d 3]
[(first item) levels] => [:i 4]
[(first item) levels] => [:e 4]
[(first item) levels] => [:f 5]
[(first item) levels] => [:j 5]
[(first item) levels] => [:k 6]
[(first item) levels] => [:l 7]
(clojure.core/deref sum) => 42

你可以。源代码(可以针对特定的用例进行简化)。

您可以使用函数
与家长一起漫游只读
完成。代码如下:

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

(def orbits
  [:com
   [:b
    [:g
     [:h]]
    [:c
     [:d
      [:i]
      [:e
       [:f]
       [:j
        [:k
         [:l]]]]]]]])

(def sum (atom 0))

(defn parent-levels
  [parents]
  (t/it-> parents
    (count it)
    (/ it 2)))

(defn count-orbits
  [data]
  (t/walk-with-parents-readonly data
    {:enter (fn [parents item]
              (when (vector? item)
                (let [levels (parent-levels parents)]
                  (t/spyx [(first item) levels])
                  (swap! sum + levels))))}))

(dotest
  (count-orbits orbits)
  (t/spyx @sum))
结果

--------------------------------------
   Clojure 1.10.2-alpha1    Java 14
--------------------------------------

Testing tst.demo.core
[(first item) levels] => [:com 0]
[(first item) levels] => [:b 1]
[(first item) levels] => [:g 2]
[(first item) levels] => [:h 3]
[(first item) levels] => [:c 2]
[(first item) levels] => [:d 3]
[(first item) levels] => [:i 4]
[(first item) levels] => [:e 4]
[(first item) levels] => [:f 5]
[(first item) levels] => [:j 5]
[(first item) levels] => [:k 6]
[(first item) levels] => [:l 7]
(clojure.core/deref sum) => 42

你可以。源代码(可以针对特定的用例进行简化)。

我可能弄错了,但核心库中的
tree seq
函数似乎可以为您实现以下功能:

(tree-seq seq rest '(:A (:B (:C)) (:D)))

;;=> ((:A (:B (:C)) (:D)) (:B (:C)) (:C) (:D))
您只需排除第一项,即树本身

我知道,这不是“如何手动编写此代码”的答案, 但是分析
树seq
源代码应该澄清如何在clojure中惯用地完成它

事实上,它使用了类似以下内容(简化):

这一个是惰性的,因此它不会导致堆栈溢出,尽管使用了递归。我真的不认为是否应该再优化,但为了教育

至于任务本身呢,我会以某种方式简化它,因为您实际上不需要子树,而只需要每个项的父项计数。因此,您甚至不需要构建树,只需要子->父查找表。我能想到这样的事情:

(defn subt
  [trees]
  (let [subtree #(if (keyword? (first %)) (rest %) nil)
        leaf? #(and (list %) (keyword? (first %)) (= (count %) 1))
        sub (subtree trees)]
    (if (every? leaf? sub)
      nil
      (cons (map subt sub) trees))))
(defn solve [data]
  (let [items (clojure.string/split data #"\)|\s+")
        pairs (partition 2 items)
        lookup (reduce (fn [acc [par ch]] (assoc acc ch par)) {} pairs)
        count-parents #(->> %
                            (iterate lookup)
                            (take-while identity)
                            count
                            dec)]
    (apply + (map count-parents (distinct items)))))

(def data "COM)B
           B)C
           C)D
           D)E
           E)F
           B)G
           G)H
           D)I
           E)J
           J)K
           K)L")

#'user/data

user> (solve data)
;;=> 42

user> (solve (slurp "./orb.txt"))
;;=> 402879 ;; for my task input data

这个可以通过动态编程进一步优化,但对于提供的输入来说已经足够好了。

我可能弄错了,但似乎核心库中的
tree-seq
函数应该可以为您实现以下功能:

(tree-seq seq rest '(:A (:B (:C)) (:D)))

;;=> ((:A (:B (:C)) (:D)) (:B (:C)) (:C) (:D))
您只需排除第一项,即树本身

我知道,这不是“如何手动编写此代码”的答案, 但是分析
树seq
源代码应该澄清如何在clojure中惯用地完成它

事实上,它使用了类似以下内容(简化):

这一个是惰性的,因此它不会导致堆栈溢出,尽管使用了递归。我真的不认为是否应该再优化,但为了教育

至于任务本身呢,我会以某种方式简化它,因为您实际上不需要子树,而只需要每个项的父项计数。因此,您甚至不需要构建树,只需要子->父查找表。我能想到这样的事情:

(defn subt
  [trees]
  (let [subtree #(if (keyword? (first %)) (rest %) nil)
        leaf? #(and (list %) (keyword? (first %)) (= (count %) 1))
        sub (subtree trees)]
    (if (every? leaf? sub)
      nil
      (cons (map subt sub) trees))))
(defn solve [data]
  (let [items (clojure.string/split data #"\)|\s+")
        pairs (partition 2 items)
        lookup (reduce (fn [acc [par ch]] (assoc acc ch par)) {} pairs)
        count-parents #(->> %
                            (iterate lookup)
                            (take-while identity)
                            count
                            dec)]
    (apply + (map count-parents (distinct items)))))

(def data "COM)B
           B)C
           C)D
           D)E
           E)F
           B)G
           G)H
           D)I
           E)J
           J)K
           K)L")

#'user/data

user> (solve data)
;;=> 42

user> (solve (slurp "./orb.txt"))
;;=> 402879 ;; for my task input data

这个可以通过动态编程进一步优化,但对于提供的输入来说已经足够了。

这里尝试使用标准库中的各种函数计算所有子树

(定义展开子树[树集]
(进入#{}(comp(map rest)cat)树集)
(定义所有子树[树]
(减为#{}
(取while seq(迭代展开子树#{tree})))
我们可以这样称呼它:

(defn subt
  [trees]
  (let [subtree #(if (keyword? (first %)) (rest %) nil)
        leaf? #(and (list %) (keyword? (first %)) (= (count %) 1))
        sub (subtree trees)]
    (if (every? leaf? sub)
      nil
      (cons (map subt sub) trees))))
(defn solve [data]
  (let [items (clojure.string/split data #"\)|\s+")
        pairs (partition 2 items)
        lookup (reduce (fn [acc [par ch]] (assoc acc ch par)) {} pairs)
        count-parents #(->> %
                            (iterate lookup)
                            (take-while identity)
                            count
                            dec)]
    (apply + (map count-parents (distinct items)))))

(def data "COM)B
           B)C
           C)D
           D)E
           E)F
           B)G
           G)H
           D)I
           E)J
           J)K
           K)L")

#'user/data

user> (solve data)
;;=> 42

user> (solve (slurp "./orb.txt"))
;;=> 402879 ;; for my task input data
(所有子树’(:A(:B(:C))(:D)))
;; => #{(:D)(:B(:C))(:C)(:A(:B(:C))(:D))}

辅助函数
展开子树
获取一组树,并生成输入集的一组新的一级子树。然后,从初始树开始,我们使用with
展开子树
,生成一系列展开子树。我们从这个序列中提取元素,直到不再有子树。然后我们将所有子树合并成一个集合,这就是结果。当然,如果需要,可以从该集合中创建初始树。

这里尝试使用标准库中的各种函数计算所有子树

(定义展开子树[树集]
(进入{}(comp(map rest)cat)树-s