Moving partition-by's splits "back by one"

77 views Asked by At

I'm parsing some Hiccup in CLJS, with the goal of taking :h2 and :h3 elements and converting them to a tree of nested :ul and :li.

My starting point is a flat vector like:

[[:h2 {} "Foo"] [:h2 {} "Bar"] [:h3 {} "Child1"] [:h2 {} "Baz"]]

If I just map over these and replace (first el) with [:li], I have a flat list. But I'd like to get something like:

[[:li "Foo"] [:li "Bar"] [:ul [:li "Child1"]] [:li "Baz"]]

If I call (partition-by #(= :h2 (first %)) my-vec), I get something almost useful:

(([:h2 {} "Foo"] [:h2 {} "Bar"]) ([:h3 {} "Child1"]) ([:h2 {} "Baz"]))

The partition happens when the predicate #(= :h2 (first %)) changes, (which is what the documentation says it does).

How can I get the behavior I'm looking for?

2

There are 2 answers

0
bright-star On BEST ANSWER

Here's an answer that does the job, but is horribly inelegant, since it essentially mutates the last element in the reduce call when necessary:

(defn listify-element [element]
  "Replaces element type with :li."
  (vec (concat [:li (last element))]))

(defn listify-headings [headings-list]
  "Takes subitems (in :h2 :h3) and creates sub :uls out of the :h3 lists."
  (vec
   (concat
    [:ul]
    (map-indexed
     (fn [ind headings]
       (if (= 0 (mod ind 2))
         (map listify-element headings)
         (vec (concat [:ul] (map listify-element headings)))))
     (partition-by #(= :h2 (first %)) headings-list)))))

(defn nest-listified-headings [vector-list]
  "Nests sub-:uls inside their preceding :lis."
  (vec (concat [:ul]
          (reduce
           (fn [acc el] (if (= (first el) :ul)
                          (conj (pop (vec acc)) (conj (last acc) el))
                          (concat acc el)))
           vector-list))))

Produces:

(nest-listified-headings 
  (listify-headings [[:h2 "Foo"] [:h2 "Bar"] [:h3 "Baz"] [:h3 "Bat"]])

[:ul [:li "Foo"] 
     [:li "Bar" 
     [:ul 
       [:li "Baz"] 
       [:li "Bat"]]]]
5
Alan Thompson On

Here is one way to do it:

(def data [
    [:h2 {} "Foo"]
    [:h2 {} "Bar"]
    [:h3 {} "Child1"]
    [:h2 {} "Baz"] ] )

(defn formatter [elem]
  (condp = (first elem)
    :h2           [:li (last elem)]
    :h3      [:ul [:li (last elem)]]
    ))

(newline) (println :data data)
(newline) (println :result (mapv formatter data))

with result

:data [[:h2 {} Foo] [:h2 {} Bar] [:h3 {} Child1] [:h2 {} Baz]]

:result [[:li Foo] [:li Bar] [:ul [:li Child1]] [:li Baz]]

Update:

Rewrite like so to get all the :h3 items in one :ul

(def data [
    [:h2 {} "Foo"]
    [:h3 {} "Child1"]
    [:h2 {} "Bar"]
    [:h3 {} "Child2"]
    [:h3 {} "Child3"]
    [:h2 {} "Baz"] ] )

(defn h2? [elem]
  (= :h2 (first elem)))

(defn ->li [elem]
  [:li (last elem)])

(defn fmt [data]
  (let [h2 (filter h2? data)
        h3 (filter #(not (h2? %)) data)
        result  (conj (mapv ->li h2)
                  (apply vector :ul (mapv ->li h3))) ]
        result ))

(newline) (println :data data)
(newline) (println :result (fmt data))

with result

:data [[:h2 {} Foo] [:h3 {} Child1] [:h2 {} Bar] [:h3 {} Child2] [:h3 {} Child3] [:h2 {} Baz]]

:result [[:li Foo] [:li Bar] [:li Baz] [:ul [:li Child1] [:li Child2] [:li Child3]]]