Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 2 years ago.
Improve this question
I'm new to Clojure and programming and I am wondering how to go about solving this problem.
I have derived the below list of maps
({:name "Bob",
:occupation "Senior salesman",
:work-department "Sales"}
{:name "Sharon",
:occupation "Executive",
:work-department "Sales"}
{:name "Donald",
:occupation "Customer Support agent",
:work-department "Client services"}
{:name "Catherine",
:occupation "Technical Lead",
:work-department "Engineering"})
How would I go about turning this from a list of maps to a map of the following style?
{:sales
{:name "Bob"
:occupation "Senior salesman"}
:sales
{:name "Sharon"
:occupation "Executive"}
:client-services
{:name "Donald"
:occupation "Customer Support agent"}
:engineering
{:name "Catherine"
:occupation "Technical Lead"}}
Thanking you for the help
You might find the group-by function useful in this situation. Say we define your list of maps as "before"
> before
({:name "Bob",
:occupation "Senior salesman",
:work-department "Sales"}
{:name "Sharon", :occupation "Executive", :work-department "Sales"}
{:name "Donald",
:occupation "Customer Support agent",
:work-department "Client services"}
{:name "Catherine",
:occupation "Technical Lead",
:work-department "Engineering"})
Then group-by will produce a map from key to a list of items with that key:
> (group-by :work-department before)
{"Sales"
[{:name "Bob",
:occupation "Senior salesman",
:work-department "Sales"}
{:name "Sharon",
:occupation "Executive",
:work-department "Sales"}],
"Client services"
[{:name "Donald",
:occupation "Customer Support agent",
:work-department "Client services"}],
"Engineering"
[{:name "Catherine",
:occupation "Technical Lead",
:work-department "Engineering"}]}
If you'd rather have a flat structure output, you could just transform each entry like this:
> (map (fn [e] [(:work-department e) (dissoc e :work-department)]) before)
(["Sales" {:name "Bob", :occupation "Senior salesman"}]
["Sales" {:name "Sharon", :occupation "Executive"}]
["Client services"
{:name "Donald", :occupation "Customer Support agent"}]
["Engineering" {:name "Catherine", :occupation "Technical Lead"}])
Related
This question already has answers here:
Clojure merge multiple map into a single map
(4 answers)
How to merge list of maps in clojure
(2 answers)
Closed 5 days ago.
I want to get the concatenated vector in loop,
I have tried below merge but it didn't help.
(def result [])
(doseq [x [{:name "harry" :occupation "teacher"}
{:name "mark" :occupation "engineer"}
{:name "smith" :occupation "doctor"}]]
(println (merge result x)))
o/p getting
[{:name harry, :occupation teacher}]
[{:name mark, :occupation engineer}]
[{:name smith, :occupation doctor}]
o/p expected
[{:name harry, :occupation teacher}]
[{:name harry, :occupation teacher} {:name mark, :occupation engineer}]
[{:name harry, :occupation teacher} {:name mark, :occupation engineer} {:name smith, :occupation doctor}]
There is a map like this
{:buyers [{:name "James" :city "Berlin"} {:name "Jane" :city "Milan"}]
:sellers [{:name "Dustin" :city "Turin" :age "21"} {:name "Mark" :city "Milan"}]}
and I need to check only for :sellers that all the keys :name, :city and :age are present and if one is missing drop
that map all together and have a new structure as below:
{:buyers [{:name "James" :city "Berlin"} {:name "Jane" :city "Milan"}]
:sellers [{:name "Dustin" :city "Turin" :age "21"}]}
I came across validateur and I am trying to use it like:
(:require [validateur.validation :as v])
(def my-map {:buyers [{:name "James" :city "Berlin"} {:name "Jane" :city "Milan"}]
:sellers [{:name "Dustin" :city "Turin" :age "21"} {:name "Dustin" :city "Milan" :age "" } {:city "Rome" :age "22"}]})
(defn valid-seller? [mp]
(let [v-set (v/validation-set
(v/presence-of #{:name :city :age}))]
(fmap vec (v-set mp))))
(map valid-seller? (:sellers my-map))
=> ({} {:age ["can't be blank"]} {:name ["can't be blank"]})
But I do not know how to update my map so missing keys or nil values be dropped
To make the code more readable, I created a new predicate, valid-seller?, and put validation there. You can use any of these versions:
Pure Clojure:
(defn valid-seller? [m]
(every? #(contains? m %) [:name :city :age]))
Spec:
[org.clojure/spec.alpha "0.3.218"], require [clojure.spec.alpha :as spec]
(defn valid-seller? [m]
(spec/valid? (spec/keys :req-un [::name ::city ::age]) m))
Malli (if you also want to test type of values):
[metosin/malli "0.8.9"], require [malli.core :as malli]
(defn valid-seller? [m]
(malli/validate [:map
[:name :string]
[:city :string]
[:age :string]] m))
Then I used this predicate:
(update {:buyers [{:name "James" :city "Berlin"} {:name "Jane" :city "Milan"}]
:sellers [{:name "Dustin" :city "Turin" :age "21"} {:name "Mark" :city "Milan"}]}
:sellers
#(filter valid-seller? %))
=>
{:buyers [{:name "James", :city "Berlin"} {:name "Jane", :city "Milan"}],
:sellers ({:name "Dustin", :city "Turin", :age "21"})}
After your answer, I think you should use Malli, as it also checks the type of values. You can use some? for any non-nil value:
(defn valid-seller? [m]
(malli/validate [:map
[:name some?]
[:city some?]
[:age some?]] m))
(let [data {:buyers [{:name "James" :city "Berlin"} {:name "Jane" :city "Milan"}]
:sellers [{:name "Dustin" :city "Turin" :age "21"} {:name "Mark" :city "Milan"}]}]
(update data :sellers #(filter (every-pred :name :city :age) %)))
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 6 years ago.
Improve this question
This is what I currently have:
{:total-pages 1, :total-results 1, :items [{:item-atributes {:title Tolkien Calendar 2017, :product-group Book, :manufacturer Harper Voyager, :author J. R.
R. Tolkien}, :SalesRank 12016, :item-links [{:description Technical Details, :url https://www.amazon.com/Tolkien-Calendar-2017-J-R/dp/tech-data/0062566938
%3FSubscriptionId%3DAKIAI6FVBZ4SCQ3VMGCQ%26tag%3D215401-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0062566938} {:description Ad
d To Baby Registry, :url https://www.amazon.com/gp/registry/baby/add-item.html%3Fasin.0%3D0062566938%26SubscriptionId%3DAKIAI6FVBZ4SCQ3VMGCQ%26tag%3D215401
-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0062566938} {:description Add To Wedding Registry, :url https://www.amazon.com/gp/r
egistry/wedding/add-item.html%3Fasin.0%3D0062566938%26SubscriptionId%3DAKIAI6FVBZ4SCQ3VMGCQ%26tag%3D215401-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D3
86001%26creativeASIN%3D0062566938} {:description Add To Wishlist, :url https://www.amazon.com/gp/registry/wishlist/add-item.html%3Fasin.0%3D0062566938%26Su
bscriptionId%3DAKIAI6FVBZ4SCQ3VMGCQ%26tag%3D215401-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0062566938} {:description Tell A
Friend, :url https://www.amazon.com/gp/pdp/taf/0062566938%3FSubscriptionId%3DAKIAI6FVBZ4SCQ3VMGCQ%26tag%3D215401-20%26linkCode%3Dxm2%26camp%3D2025%26creati
ve%3D386001%26creativeASIN%3D0062566938} {:description All Customer Reviews, :url https://www.amazon.com/review/product/0062566938%3FSubscriptionId%3DAKIAI
6FVBZ4SCQ3VMGCQ%26tag%3D215401-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26creativeASIN%3D0062566938} {:description All Offers, :url https://ww
w.amazon.com/gp/offer-listing/0062566938%3FSubscriptionId%3DAKIAI6FVBZ4SCQ3VMGCQ%26tag%3D215401-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D386001%26cre
ativeASIN%3D0062566938}], :detail-page-url https://www.amazon.com/Tolkien-Calendar-2017-J-R/dp/0062566938%3FSubscriptionId%3DAKIAI6FVBZ4SCQ3VMGCQ%26tag%3D2
15401-20%26linkCode%3Dxm2%26camp%3D2025%26creative%3D165953%26creativeASIN%3D0062566938, :asin 0062566938}]}
I got this from a result and wanted to traverse through it.
No, that's not XML. You can use get-in to traverse your nested structure.
As #jmargolistvt said, you have a Clojure data structure (nested maps & arrays), not xml.
Also, next time please print using the prn instead of println or similar, as it is important to keep the double-quote chars around strings like "Harper Voyager".
I simplified the data a bit, and have this:
(ns clj.core
(:require
[tupelo.core :as t]
[clojure.pprint :as pp]
)
(:gen-class))
(t/refer-tupelo)
(def data
{:total-pages 1, :total-results 1, :items
[ {:item-atributes
{:title "Tolkien Calendar 2017", :product-group "Book",
:manufacturer "Harper Voyager"
:author "J. R. R. Tolkien" }, :SalesRank 12016, :item-links [{:description "Technical Details" } ]
} ] } )
(pp/pprint data)
(defn -main [& args]
)
which yields:
~/clj > lein run
{:total-pages 1,
:total-results 1,
:items
[{:item-atributes
{:title "Tolkien Calendar 2017",
:product-group "Book",
:manufacturer "Harper Voyager",
:author "J. R. R. Tolkien"},
:SalesRank 12016,
:item-links [{:description "Technical Details"}]}]}
At this point, you can add
(newline)
(doseq [item (data :items) ]
(newline)
(pp/pprint item)
(newline)
(spyx (item :SalesRank))
(spyx (get-in item [:item-atributes :title])))
to get:
{:item-atributes
{:title "Tolkien Calendar 2017",
:product-group "Book",
:manufacturer "Harper Voyager",
:author "J. R. R. Tolkien"},
:SalesRank 12016,
:item-links [{:description "Technical Details"}]}
(item :SalesRank) => 12016
(get-in item [:item-atributes :title]) => "Tolkien Calendar 2017"
Your project.clj must include this to make the (spyx ...) part work:
:dependencies [
[tupelo "0.9.9"] ...
I've got the following tree:
{:start_date "2014-12-07"
:data {
:people [
{:id 1
:projects [{:id 1} {:id 2}]}
{:id 2
:projects [{:id 1} {:id 3}]}
]
}
}
I want to update the people and projects subtrees by adding a :name key-value pair.
Assuming I have these maps to perform the lookup:
(def people {1 "Susan" 2 "John")
(def projects {1 "Foo" 2 "Bar" 3 "Qux")
How could I update the original tree so that I end up with the following?
{:start_date "2014-12-07"
:data {
:people [
{:id 1
:name "Susan"
:projects [{:id 1 :name "Foo"} {:id 2 :name "Bar"}]}
{:id 2
:name "John"
:projects [{:id 1 :name "Foo"} {:id 3 :name "Qux"}]}
]
}
}
I've tried multiple combinations of assoc-in, update-in, get-in and map calls, but haven't been able to figure this out.
I have used letfn to break down the update into easier to understand units.
user> (def tree {:start_date "2014-12-07"
:data {:people [{:id 1
:projects [{:id 1} {:id 2}]}
{:id 2
:projects [{:id 1} {:id 3}]}]}})
#'user/tree
user> (def people {1 "Susan" 2 "John"})
#'user/people
user> (def projects {1 "Foo" 2 "Bar" 3 "Qux"})
#'user/projects
user>
(defn integrate-tree
[tree people projects]
;; letfn is like let, but it creates fn, and allows forward references
(letfn [(update-person [person]
;; -> is the "thread first" macro, the result of each expression
;; becomes the first arg to the next
(-> person
(assoc :name (people (:id person)))
(update-in [:projects] update-projects)))
(update-projects [all-projects]
(mapv
#(assoc % :name (projects (:id %)))
all-projects))]
(update-in tree [:data :people] #(mapv update-person %))))
#'user/integrate-tree
user> (pprint (integrate-tree tree people projects))
{:start_date "2014-12-07",
:data
{:people
[{:projects [{:name "Foo", :id 1} {:name "Bar", :id 2}],
:name "Susan",
:id 1}
{:projects [{:name "Foo", :id 1} {:name "Qux", :id 3}],
:name "John",
:id 2}]}}
nil
Not sure if entirely the best approach:
(defn update-names
[tree people projects]
(reduce
(fn [t [id name]]
(let [person-idx (ffirst (filter #(= (:id (second %)) id)
(map-indexed vector (:people (:data t)))))
temp (assoc-in t [:data :people person-idx :name] name)]
(reduce
(fn [t [id name]]
(let [project-idx (ffirst (filter #(= (:id (second %)) id)
(map-indexed vector (get-in t [:data :people person-idx :projects]))))]
(if project-idx
(assoc-in t [:data :people person-idx :projects project-idx :name] name)
t)))
temp
projects)))
tree
people))
Just call it with your parameters:
(clojure.pprint/pprint (update-names tree people projects))
{:start_date "2014-12-07",
:data
{:people
[{:projects [{:name "Foo", :id 1} {:name "Bar", :id 2}],
:name "Susan",
:id 1}
{:projects [{:name "Foo", :id 1} {:name "Qux", :id 3}],
:name "John",
:id 2}]}}
With nested reduces
Reduce over the people to update corresponding names
For each people, reduce over projects to update corresponding names
The noisesmith solution looks better since doesn't need to find person index or project index for each step.
Naturally you tried to assoc-in or update-in but the problem lies in your tree structure, since the key path to update John name is [:data :people 1 :name], so your assoc-in code would look like:
(assoc-in tree [:data :people 1 :name] "John")
But you need to find John's index in the people vector before you can update it, same things happens with projects inside.
Suppose I have a nested structure, something like this:
{:data1
{:categories [
{:name "abc" :id 234 :desc "whatever"}
{:name "def" :id 456 :desc "nothing"}]
}
:data2 {...}
:data3 {...}
}
And I need to transform the key names in the maps. I can transform the top level keys like this:
(rename-keys mymap {:data1 :d1})
But I'm not sure how to rename keys nested more deeply in the data structure (say I want to rename the :desc field to :description).
I'm pretty sure that zippers are the answer but can't quite figure out how to do it, or if there's a more straightforward way.
Same as Brian Carper's solution, except the walk namespace already has a specific function for this purpose. All keys at all levels are changed, be they nested inside any sort of collection or seq.
(:use 'clojure.walk)
(def x
{:data1
{:categories
[{:desc "whatever", :name "abc", :id 234}
{:desc "nothing", :name "def", :id 456}]},
:data2
{:categories
[{:desc "whatever", :name "abc", :id 234}
{:desc "nothing", :name "def", :id 456}]}})
(postwalk-replace {:desc :something} x)
{:data1
{:categories
[{:something "whatever", :name "abc", :id 234}
{:something "nothing", :name "def", :id 456}]},
:data2
{:categories
[{:something "whatever", :name "abc", :id 234}
{:something "nothing", :name "def", :id 456}]}}
postwalk is a pretty heavy sledgehammer in general, although it looks from your original question like you might need it. In many cases, you can perform updates in a nested structure with update-in:
user> (let [m {:foo {:deep {:bar 1 :baz 2}}}]
(update-in m [:foo :deep] clojure.set/rename-keys {:baz :periwinkle}))
{:foo {:deep {:periwinkle 2, :bar 1}}}
If you want to rename all :desc keys regardless of at which level of nesting they're located, this might work. If you only want to rename :desc keys at a certain level of nesting, you'll need something slightly more sophisticated.
This only works because clojure.set/rename-keys currently does nothing (returns its first argument untouched) if its first argument isn't a map.
user> (require '[clojure [set :as set] [walk :as walk]])
nil
user> (def x {:data1
{:categories
[{:desc "whatever", :name "abc", :id 234}
{:desc "nothing", :name "def", :id 456}]},
:data2
{:categories
[{:desc "whatever", :name "abc", :id 234}
{:desc "nothing", :name "def", :id 456}]}})
#'user/x
user> (walk/postwalk #(set/rename-keys % {:desc :description :id :ID}) x)
{:data1
{:categories
[{:name "abc", :ID 234, :description "whatever"}
{:name "def", :ID 456, :description "nothing"}]},
:data2
{:categories
[{:name "abc", :ID 234, :description "whatever"}
{:name "def", :ID 456, :description "nothing"}]}}
nil