Clojure - использование спектра для преобразования вложенной структуры данных, замена одного узла несколькими - PullRequest
6 голосов
/ 15 марта 2019

Я использую Spectre для преобразования вложенных структур данных в Clojure, но я еще не освоил его.В частности, я пытаюсь создать преобразование, которое найдет элемент на любой глубине, который соответствует предикату, и заменит его несколькими элементами.

[:top
 [:arbitrary 1 2
  [:nesting
   2
   3
   [:needle] ; <-- the thing to find
   ]]]

-->

[:top
 [:arbitrary 1 2
  [:nesting
   2
   3
   [:n1] [:n2] [:n3]  ; <-- 3 items inserted in the place of 1
   ]]]

Что я не могу понять, так этокак разделить заменяющие элементы на родительский вектор, т.е. как заменить один элемент тремя элементами, а не одним элементом, содержащим три дочерних элемента.

Ответы [ 3 ]

3 голосов
/ 15 марта 2019
(defn replace-needle [input replacement]
    (let [needle-parent?     #(= % [:needle])
          NEEDLE-PARENT      (recursive-path
                                 [] p (cond-path
                                          #(and (vector? %) (some needle-parent? %)) [(continue-then-stay [ALL p])]
                                          vector? [ALL p]))
          inject-replacement (fn inject [x] (vec (mapcat #(if (needle-parent? %) replacement [%]) x)))]
        (transform [NEEDLE-PARENT] inject-replacement input)))


(let [input       [:top
                   [:arbitrary 1 2
                    [:nesting 2 3 [:needle]]]]
      replacement [[:n1] [:n2] [:n3]]]
    (replace-needle input replacement))
3 голосов
/ 15 марта 2019

Я не знаю, как сделать это с помощью Spectre, но вот функция, которая делает это с clojure.zip:

(defn splice-replace [zipper smap]
  (loop [loc zipper]
    (if (z/end? loc)
      (z/root loc)
      (recur
       (z/next
        (if-let [sub (smap (z/node loc))]
          (reduce (comp z/right z/insert-right)
                  (z/replace loc (first sub))
                  (rest sub))
          loc))))))

Вы можете вызвать его с помощью молнии вашей структуры данных и картыиз значений, которые вы хотите заменить, в последовательность их замещающих значений, которые будут вставлены в их положение:

(def zipper
  (z/vector-zip [:top
                 [:arbitrary 1 2
                  [:nesting 2 3 [:needle]]]]))

(splice-replace zipper {[:needle] [[:n1] [:n2] [:n3]]})
 => [:top [:arbitrary 1 2 [:nesting 2 3 [:n1] [:n2] [:n3]]]]

(splice-replace zipper {[:nesting 2 3 [:needle]] (range 3 10)})
=> [:top [:arbitrary 1 2 3 4 5 6 7 8 9]]
1 голос
/ 15 марта 2019

Я думал, что можно найти вектор, который содержит [:needle], а затем индекс [:needle], а затем использовать srange, чтобы склеить новые элементы в родительский элемент в этот индекс, но я не нашел способ сделать это с помощью Spectre.

Вот та же идея, выраженная с помощью clojure.walk:

(require '[clojure.walk :refer [postwalk]])

(postwalk (fn [node]
            (if (and (vector? node)
                     (some (partial = [:needle]) node))
              (let [idx (.indexOf node [:needle])]
                (vec (concat (take idx node)
                             [[:n1] [:n2] [:n3]]
                             (drop (inc idx) node))))
              node))
          data)

;; => [:top [:arbitrary 1 2 [:nesting 2 3 [:n1] [:n2] [:n3]]]]
...