I have a reagent atom which consists a vector of key/value maps.
How can I remove a key/value pair with certain key value which happens to be UUID? I have tried:
(swap! state/items (dissoc #state/items id))
but I get:
core.cljs:270 Uncaught Error: No protocol method IMap.-dissoc defined for type cljs.core/LazySeq: ({:id #uuid "e1f9341f-bc02-4c17-a594-b9b5ede72214", :description "foo bar"})
I think you need to use remove. So this should help:
(def data [{:id "e1f9341f-bc02-4c17-a594-b9b5ede72214" :description "Foo"} {:id "e1f9341f-bc02-4c17-a594-b9b5ede72214" :description "Bar"}] )
(remove #(= (:id %) "e1f9341f-bc02-4c17-a594-b9b5ede72214") data)
;; => ()
The swap! function takes the old value and returns the updated value. So data from above will be the old value. Your function you pass to swap! will thus look like this:
(fn [old]
(remove #(= (:id %) "e1f9341f-bc02-4c17-a594-b9b5ede72214") old))
Related
Given I have this action to perform
(def structure (atom [{:id "an-id"} {:id "another-id"}]))
(def job {:type "some-job"})
(reset! structure (map #(if (= "an-id" (:id %)) (update-in % [:performed-jobs] (fnil conj []) job) %) #structure))
next structure:
[{:id "an-id" :performed-jobs [{:type "some-job"}]} {:id "another-id"}]
How can I use swap! to change a single occurrence in my structure instead of resetting it all?
Replace reset! by swap! by giving it a function that takes the old value of the atom and returns a new value to store in the atom.
Replace dereferencing of the atom with the function's argument, the old value.
(swap! structure
(fn [old]
(map #(if (= "an-id" (:id %))
(update-in % [:performed-jobs]
(fnil conj []) job) %)
old)))
I have an atom called app-state that holds a map. It looks like this:
{:skills [{:id 1 :text "hi"} {:id 2 :text "yeah"}]}
What is the idiomatic way to remove the element inside the vector with :id = 2 ? The result would look like:
{:skills [{:id 1 :text "hi"}]}
...
So far, I have this:
(defn new-list [id]
(remove #(= id (:id %)) (get #app-state :skills)))
swap! app-state assoc :skills (new-list 2)
It works, but I feel like this isn't quite right. I think it could be something like:
swap! app-state update-in [:skills] remove #(= id (:id %))
But this doesn't seem to work.
Any help is much appreciated!
Try this:
(defn new-list [app-state-map id]
(assoc app-state-map :skills (into [] (remove #(= id (:id %)) (:skills app-state-map)))))
(swap! app-state new-list 2)
swap! will pass the current value of the atom to the function you supply it. There's no need to dereference it yourself in the function.
See the docs on swap! for more details.
(swap! state update :skills (partial remove (comp #{2} :id)))
(def skills {:skills [{:id 1 :text "hi"} {:id 2 :text "yeah"}]})
(defn remove-skill [id]
(update skills :skills (fn [sks] (vec (remove #(= id (:id %)) sks)))))
You would then be able to call say (remove-skill 1) and see that only the other one (skill with :id of 2) is left.
I like your way better. And this would need to be adapted for use against an atom.
You can use filter to do this. Here is a function that takes an id and the map and let's you filter out anything that doesn't match your criteria. Of course, you can make the #() reader macro check for equality rather than inequality depending on your needs.
user=> (def foo {:skills [{:id 1 :text "hi"} {:id 2 :text "yeah"}]})
#'user/foo
user=> (defn bar [id sklz] (filter #(not= (:id %) id) (:skills sklz)))
#'user/bar
user=> (bar 1 foo)
({:id 2, :text "yeah"})
user=> (bar 2 foo)
({:id 1, :text "hi"})
Say I have a list of maps that looks like the following:
(def my-map '({:some-key {:another-key "val"}
:id "123"}
{:some-key {:another-key "val"}
:id "456"}
{:some-other-key {:a-different-key "val2"}
:id "789"})
In my attempt to filter this map by :another-key, I tried this:
(filter #(= "val" ((% :some-key) :another-key)) my-map)))
However, this will throw a NullPointerException on the map entry that doesn't contain the key I'm filtering on. What would be the optimal way to filter this map, excluding entries that don't match the filtered schema entirely?
Your first lookup of the key :some-key will return nil if the map key is not in the map. Calling nil will result in the NPE you see.
The solution is easy, just make the keyword lookup itself in the map which work even if given a nil:
(def my-map '({:some-key {:another-key "val"}
:id "123"}
{:some-key {:another-key "val"}
:id "456"}
{:some-other-key {:a-different-key "val2"}
:id "789"}))
(filter #(= "val" (:another-key (% :some-key))) my-map)
You can also use get-in:
(filter #(= "val" (get-in % [:some-key :another-key])) my-map)
And if your list could potentially have nil items:
(filter #(= "val" (:another-key (:some-key %))) my-map)
Explanation:
(:k nil);; => nil
(nil :k);; => NPE
({:k 4} :k);; => 4
(:k {:k 4});; => 4
;; BTW, you can also specify the "not found" case:
(:k nil :not-there);; => :not-there
See also the clojure style guide.
I'm generating json as literally as I can in clojure. My problem is that certain branches of the json are only present if given parameters are given. Here is a sample of such a condition
(defn message-for
[name uuid & [generated-uuids]]
{:message {:id (generate-uuid)
:details {:name name}
:metadata {:batch (merge {:id uuid}
(when generated-uuids (let [batches (map #(array-map :id %) generated-uuids)]
{:generatedBatches batches})))}}})
Unfortunately the when/let part is quite ugly. This same could be achieved using when-let as following but it doesn't work because my map returns [] instead of a nil.
(defn message-for
[name uuid & [generated-uuids]]
{:message {:id (generate-uuid)
:details {:name name}
:metadata {:batch (merge {:id uuid}
(when-let [batches (map #(array-map :id %) generated-uuids)]
{:generatedBatches batches}))}}})
Any ideas if I could somehow make when-let consider an empty list/array/seq as false so I could clean up my code a bit?
not-empty returns its argument if it is not empty.
When using when-let with a collection, always use not-empty
to retain the collection type
make refactoring easier
expressivenes
(when-let [batches (not-empty (map ...))]
...)
In your case I'd however prefer something like this:
...
:metadata {:batch (cond-> {:id uuid}
(seq generated-uuids)
(assoc :generatedBatches (map ...)))}
...
Notice that all three of the advantages listed above where met, without a nested let.
Also notice a new advantage
easier to extend with more conditions lateron
seq returns nil on an empty input sequence so you could do:
(when-let [batches (seq (map #(array-map :id %) generated-uuids))]
{:generatedBatches batches}))}}})
Clojure beginner here..
If I have a set of maps, such as
(def kids #{{:name "Noah" :age 5}
{:name "George":age 3}
{:name "Reagan" :age 1.5}})
I know I can get names like this
(map :name kids)
1) How do I select a specific map? For example
I want to get back the map where name="Reagan".
{:name "Reagan" :age 1.5}
Can this be done using a filter?
2) How about returning the name where age = 3?
Yes, you can do it with filter:
(filter #(= (:name %) "Reagan") kids)
(filter #(= (:age %) 3) kids)
There's clojure.set/select:
(clojure.set/select set-of-maps #(-> % :age (= 3)))
And similarly with name and "Reagan". The return value in this case will be a set.
You could also use filter without any special preparations, since filter calls seq on its collection argument (edit: as already described by ffriend while I was typing this):
(filter #(-> % :age (= 3))) set-of-maps)
Here the return value will be a lazy seq.
If you know there will only be one item satisfying your predicate in the set, some will be more efficient (as it will not process any additional elements after finding the match):
(some #(if (-> % :age (= 3)) %) set-of-maps)
The return value here will be the matching element.