Fork me on GitHub
#specter
<
2016-08-19
>
joshkh08:08:19

I have another newbie question regarding Specter. Is this something that I can easily do? I have nested vectors of strings representing a tree. If a vector directly follows a string then it is a subtree. I'd like to update the tree in a similar fashion to update-in when dealing with maps. In this non working example I'd want to append "SUB-A-2-2-2" to the "SUB-A-2-2" "node.":

(let [t ["A"
             ["SUB-A-1"
              "SUB-A-2"
              ["SUB-A-2-1"
               "SUB-A-2-2"
               ["SUB-A-2-2-1"]]]]]

      (s/transform ["A" "SUB-A-2" "SUB-A-2-2"]
                   (fn [node] (conj node "SUB-A-2-2-2"))
                   t)
      #_["A"
         ["SUB-A-1"
          "SUB-A-2"
          ["SUB-A-2-1"
           "SUB-A-2-2"
           ["SUB-A-2-2-1"
            ; New:
            "SUB-A-2-2-2"]]]])

joshkh08:08:18

I can't quite figure out how to work something like #(.indexOf nested-vec %) into the selection path, presumably recursively. Which makes me think I need a walker?

nathanmarz12:08:26

@rodeorockstar: cleanest way is to define a new navigator that determines which index to navigate to dynamically

nathanmarz12:08:41

(defnav dynamic-nth [afn]
  (select* [this structure next-fn]
    (next-fn (nth structure (afn structure))))
  (transform* [this structure next-fn]
    (let [i (afn structure)]
      (assoc structure i (next-fn (nth structure i))))))

(defnavconstructor subtree
  [p dynamic-nth]
  [v]
  (p (fn [avec] (inc (.indexOf avec v)))))

nathanmarz12:08:45

example:

(transform
  [(subtree "A")
   (subtree "SUB-A-2")
   (subtree "SUB-A-2-2")
   ALL]
   #(str % "!")
   data)

;; => ["A" ["SUB-A-1" "SUB-A-2" ["SUB-A-2-1" "SUB-A-2-2" ["SUB-A-2-2-1!"]]]]

nathanmarz12:08:05

@rodeorockstar: that said, I would recommend storing your tree in a different format, as right now you're basically required to parse it as you traverse it

nathanmarz12:08:21

your particular example can be done like this:

(setval [(subtree "A") (subtree "SUB-A-2") (subtree "SUB-A-2-2") END]
  ["SUB-A-2-2-2"]
  data)

joshkh12:08:17

That is so cool... Thanks for the example, @nathanmarz . I'll comb over the devnav devnavconstructor macros until they settle in my head. Agreed, the tree structure isn't ideal, and I changed it since asking the question. But I was still curious if something like that could be done with specter so it remained a useful exercise to me. I hadn't thought to use defnav.

joshkh12:08:34

Thanks again for the continued help!

nathanmarz12:08:44

yea, the real power of specter is in being able to define your own navigators and then benefit from the combinatorial ways in which everything can be combined

nathanmarz12:08:51

no problem, happy to help

lellis19:08:23

Hi guys, reading codewalker documentation i found “When afn returns a truthy value, codewalker stops searching that branch of the tree and continues its search of the rest of the data structure” So how can i dont stop when afn return true? I just wanna collect when its true and searching that branch of the tree for more patterns to collect. Sorry if the questions isn't clear.

nathanmarz19:08:08

@lellis you probably want something like this:

(declarepath MyCodewalker)
(providepath MyCodewalker
 [(codewalker map?)
  (continue-then-stay
    MAP-VALS
    MyCodewalker)])

mattsfrey20:08:50

wondering if anyone knows offhand the best way to transform a map where I want to select keys of a certain set and map their children into a final map of {:keyname child}

nathanmarz20:08:30

@mattsfrey what's an example of input/output?

mattsfrey21:08:51

I actually had to re-think it but let me get an example

mattsfrey21:08:38

so this is what I'm trying to achieve:

mattsfrey21:08:43

Basically will have a map of "info types" that are an array of sub maps, I want to pull out the child sub maps and put them into a flat structure as such

mattsfrey21:08:54

(and disregard top level map values that aren't inside a certain set, i.e :bleh)

nathanmarz21:08:52

here's how I would do it:

(def data {:phone-numbers [{:type "work" :value "720-1234"}
                           {:type "home" :value "720-1235"}]
           :organizations [{:name "Google" :title "Software Engineer"}]
           :bleh "blah..."})

(->> data
     (traverse [ALL
                (not-selected? FIRST #{:bleh})
                (collect-one FIRST)
                LAST
                ALL])
     (reduce (fn [v [type val]] (conj v {:type type :value val}))
             []))

mattsfrey21:08:24

that seems to be explicitly ignoring bleh whereas I'd rather explicitly include elements of a set and ignore everything else

mattsfrey21:08:17

i.e. if in [:organizations :phone-numbers :addresses]

nathanmarz21:08:09

just change that line to (selected? FIRST #{:organizations :phone-numbers :addresses})