Fork me on GitHub
#specter
<
2018-02-14
>
nathanmarz00:02:39

@johanatan of course, navigators are infinitely composable

nathanmarz00:02:11

will be easier to show you the most elegant approach if you give an example of the input and desired output

cperrone12:02:09

Hi @nathanmarz and all, I’m beginning to use Specter everywhere 🙂. I’m still struggling with some admittedly basic recursion/tree transformation though. For example, here’s my input:

(def tree [0
                 [1]
                 [2 [3]]])
The tree is arbitrarily nested, each node is a vector of type
[root & children]
(e.g. [0] is the tree root, [1] and [2] its immediate children, [1] and [2] are siblings). My goal is to transform the tree into the following structure. In other words, each node under the root becomes an :li item. Also if a node has children, I’d like to “wrap” them with a :ul container. It should be simple, but I can’t get my head around it yet. But maybe you can help me on this one?
[:ul
       [:li {:id "1"}]
       [:li {:id "2"}
        [:ul
         [:li {:id "3"}]]]]

nathanmarz14:02:49

@cperrone here's one way to do it:

(def tree [0
            [1]
            [2 [3]]])

(def NODE-CHILDREN
  (path INDEXED-VALS #(not= 0 (first %)) LAST))

(def NODES (recursive-path [] p
             (continue-then-stay
              NODE-CHILDREN
              p)))

(defn wrap [children id _]
  (if (empty? children)
    [:li {:id id}]
    [:li {:id id} (setval BEFORE-ELEM :ul children)]
    )
  )

(nth
  (transform
    [NODES
     (collect NODE-CHILDREN)
     (collect-one FIRST)
     ]
    wrap
    tree
    )
  2)

cperrone14:02:45

ah brilliant, thank you @nathanmarz. I much appreciate it. I suspected I needed to collect values along the way, but I wasn’t sure how (I need to play more with that). Great answer. Would this be more typical of a zipper use, by the way? At some stage I wondered if walking with

(defn my-zipper [v]
  (z/zipper next rest my-make-node v))
(obviously then wrapped by the specter zipper :D) would have been easier for this case.

nathanmarz15:02:07

@cperrone zippers aren't needed in this case

nathanmarz15:02:37

it's best to avoid using zippers since they add a lot of overhead

cperrone15:02:29

I’m still studying your approach to the problem, in particular, I was surprised by the setval within the transform fn

nathanmarz15:02:26

your use case does two things: wrap in :li and wrap in :ul

nathanmarz15:02:56

the inner setval prepends to a vector without changing the type (as cons would do)

cperrone15:02:16

yes. I guess I also need to check the result of the overall transform “before” you take the nth index.

nathanmarz15:02:09

yea, the handling of the root is special with how you laid out the problem

cperrone15:02:56

yes, maybe some slight tweaking on the input could help (as it’s often the case)

nathanmarz15:02:53

if you have the control, then making the structure of the data as consistent as possible helps a lot

cperrone15:02:34

eh eh I do. I though I was consistent though. [root children children] all the way no?

nathanmarz15:02:05

yea, in this case it's the output that's slightly inconsistent

cperrone15:02:23

true, the wrapping is slightly tricky

cperrone15:02:03

i read somewhere that zippers are pretty lightweight by the way… I don’t particularly like the imperative navigation that it often leads to though.

nathanmarz15:02:34

"lightweight" is relative

nathanmarz15:02:00

compared to a straight reduce it's not

nathanmarz15:02:22

it's not just performance, they also add conceptual overhead

cperrone15:02:23

ah ah I guess so, especially in clojurescript, everything counts, so it’s a matter of tradeoffs