5
votes

Given a map with the key :content, where content is a list of strings or other maps, how can I flatten out the values to receive only the strings?

(flattener {:content '("b" {:content ("c" {:content ("d")})} "e")})

> '("b" "c" "d" "e")

I'm stumbling through very hacky loop recur attempts and now my brain is burnt out. Is there a nice idiomatic way to do this in Clojure?

Thanks.

What I've got is below, and although it works, it's quite ugly

(defn flatten-content
  [coll]
  (loop [acc '(), l coll]
    (let [fst (first l), rst (rest l)]
      (cond
       (empty? l) (reverse acc)
       (seq? fst) (recur acc (concat fst rst))
       (associative? fst) (recur acc (concat (:content fst) rst))
       :else (recur (conj acc fst) rst)))))
2

2 Answers

9
votes

The tree-seq function helps walk, and since your map

(def m {:content '("b" {:content ("c" {:content ("d")})} "e")})

always has a list of "children" keyed by :content, this works

(filter string? (tree-seq associative? :content m))
;=> ("b" "c" "d" "e")
5
votes

The following recursive function works (and is about 25% faster than a filtered tree-seq approach):

(defn flatten-content [node]
  (lazy-seq
    (if (string? node)
      (list node)
      (mapcat flatten-content (:content node)))))