Reputation: 3247
what would be the best way of filtering the following nested map, keeping the nested map structure. In my example Alice and Bob can be duplicated, e.g. the same employee can be working in several different plants at a time.
(def universe
{:customer1
{:plant1
{ "Alice" {:age 35 :sex "F"}
"Bob" {:age 25 :sex "M"}}
:plant2 {}}
:customer2 {}
})
I would like to for example filter by age >30 and return the same map structure. Ideally this would work for any nested map depth, filtering on the innermost level. Expected result:
(def universe
{:customer1
{:plant1
{ "Alice" {:age 35 :sex "F"}
}
:plant2 {}}
:customer2 {}
})
I've looked at clojure filter nested map to return keys based on inner map values but it doesn't look to be solving my problem. Thank you,
Upvotes: 2
Views: 389
Reputation: 3247
Thanks to @akond for the answer, reading the code made me think of a non specter solution. Still, slightly surprised there's no easy way to apply filter
in this use case.
(defn recursive-filter [pred k m]
(letfn [(pair-filter [pair] (if (pred (k (second pair))) pair nil))]
(into {}
(for [a m]
(if (empty? (second a))
[(first a) {}]
(if (contains? (second a) k)
(pair-filter a)
[(first a) (recursive-filter pred k (second a))]))))))
Upvotes: 0
Reputation: 29984
Your data is a bit unusual in that one would normally expect :customer1
, :customer2
etc to be different entries in a vector. For semi-structured data like this, I would consider postwalk
:
(ns tst.demo.core
(:use tupelo.core demo.core tupelo.test)
(:require
[clojure.walk :as walk] ))
(def universe
{:customer1
{:plant1
{"Alice" {:age 35 :sex "F"}
"Bob" {:age 25 :sex "M"}}
:plant2 {}}
:customer2 {}})
(def age-of-wisdom 30)
(defn wisdom-only
[form]
(let [filter-entry? (when (map-entry? form)
(let [[-name- details] form
age (:age details)] ; => nil if missing
(and age ; ensure not nil
(< age age-of-wisdom))))]
(if filter-entry?
{}
form)))
(walk/postwalk wisdom-only universe) =>
{:customer1
{:plant1
{"Alice" {:age 35, :sex "F"}}
:plant2 {}}
:customer2 {}}
Upvotes: 1
Reputation: 16060
It is very similar to one of previous questions:
(use '[com.rpl.specter])
(let [input {:customer1
{:plant1
{"Alice" {:age 35 :sex "F"}
"Bob" {:age 25 :sex "M"}}
:plant2 {}}
:customer2 {}}
desired-output {:customer1
{:plant1 {"Alice" {:age 35 :sex "F"}}
:plant2 {}}
:customer2 {}}
RECUR-MAP (recursive-path [] p (cond-path map? (continue-then-stay [MAP-VALS p])))]
(clojure.test/is (= (setval [RECUR-MAP (pred :age) #(> 30 (:age %))] NONE input)
desired-output)))
Upvotes: 3