0

I have a nested map like so:

{:A {:B {:A {:B {:A {:B 0}}}}}}

I want to count the occurrences of the pair [:A :B] so that in the case above, the result is 3.

My initial idea was to use clojure.walk/postwalk to traverse the map and increment a counter.

Is there a more optimal way of achieving this?

Edmond
  • 615
  • 1
  • 5
  • 15
  • what if the input is `{:A {:B {:C {:A {:B {:A {:B 0}}}}}}}` ? the result should still be 3 ? if so, the accepted answer is wrong (it gives you `1`, obviously) – leetwinski Jan 22 '18 at 20:13

3 Answers3

4

tree-seq works out nice in this case:

(defn count-ab [data]
  (->> data
       (tree-seq coll? seq)
       (keep #(get-in % [:A :B]))
       count))

user> (def data {:A {:B {:A {:B {:A {:B 0}}}}}})
#'user/data

user> (count-ab data)
3

user> (def data-1 {:A {:B {:C {:A {:B {:A {:B 0}}}}}}})
#'user/data-1

user> (count-ab data-1)
3

user> (def data-2 {:A {:X {:A {:B 100}}
                       :B {:C {:A {:B {:A {:B 0}}}}}}})
#'user/data-2

user> (count-ab data-2)
4
leetwinski
  • 17,408
  • 2
  • 18
  • 42
1

Because it's nested map, my pragmatic idea is to traverse recursively and count:

(defn do-count [m]
   (loop [m m
          counter 0]
       (if-let [m* (get-in m [:A :B])]
           (recur m* (inc counter))
           counter)))


(do-count {:A {:B {:A {:B {:A {:B 0}}}}}}) ;==> 3
Minh Tuan Nguyen
  • 1,026
  • 8
  • 13
0

I would suggest writing a function to turn the deeply nested map into a flat map keyed by paths

 (defn flatten-map
  "unwind deeply nested map into map of paths and vals"
  ([m] (into {} (flatten-map [] m)))
  ([path m]
   (if (map? m)
     (mapcat (fn [[k v]]
               (flatten-map (conj path k) v))
             m)
     [[path m]])))

you can use this to count the adjacent [:a :b] keys like this

(->> {:A {:B {:A {:B {:A {:B 0}}}}}}
     flatten-map
     keys 
     (mapcat #(partition 2 1 %))
     (filter #(= [:A :B] %))
     count)
Ben Hammond
  • 705
  • 1
  • 7
  • 20