Using disj to remove an element from a set in Clojure - clojure

Hi I've hit a brick wall whilst trying to remove an element from a set.
I have a map of cards.
(def cards
{
:card1 {:name "Wisp" :type "Monster" :damage 1 :health 1 :cost 0 :charge "t"}
:card2 {:name "Spider Tank" :type "Monster" :damage 3 :health 4 :cost 3}
:card3 {:name "Boulder Fist Ogre" :type "Monster" :damage 6 :health 7 :cost 6}
}
)
And a deck (set) of these cards.
(def deck1 (set (map cards '(:card1 :card2 :card3))))
When I use disj to try and remove one of these cards nothing happens.
(disj deck1 :card1)
I really have no idea why.

leetwinski was right in the comments. (disj deck1 (:card1 cards)) is correct.

First, this is not a typical clojure idiom:
'(:card1 :card2 :card3)
This is easier and cleaner:
[:card1 :card2 :card3]
Second, you are complicating your collections a bit, in my opinion. As stated in the other comments, you cannot disj a key that is not in there; your map function is returning the values associated with the keys :card1 etc, so trying to disj the key on the results will do nothing.
Now, the fact you are turning this into a set only matters if you expect the values in your original map to possibly be duplicated in that map. Is it possible to have more than one Wisp card with the same damage, etc? If it is possible that :card5 and :card8, for example, could be identical values, then turning the map into a set will remove those duplicates. If it is not possible that cards would be identical, then the map already has unique keys that cannot be duplicated and so I'm not sure what you are gaining by transforming it into a set.

deck1 is
#{{:name "Wisp", :type "Monster", :damage 1, :health 1, :cost 0, :charge "t"}
{:name "Spider Tank", :type "Monster", :damage 3, :health 4, :cost 3}
{:name "Boulder Fist Ogre", :type "Monster", :damage 6, :health 7, :cost 6}}
This set does not contain the value :card1. So (disj deck1 :card1) has no effect.
You want something like
(apply disj deck1 (filter #(= (:name %) "Wisp") deck1))
... which removes all elements with :name "Wisp" - there is only one, giving
#{{:name "Spider Tank", :type "Monster", :damage 3, :health 4, :cost 3}
{:name "Boulder Fist Ogre", :type "Monster", :damage 6, :health 7, :cost 6}}

You can avoid turning this into a set and simply use dissoc instead. Dissoc works on a map rather than a set, so you can avoid the extra type conversion.
user=> (dissoc cards :card1)
{:card2 {:name "Spider Tank", :type "Monster", :damage 3, :health 4, :cost 3},
:card3 {:name "Boulder Fist Ogre", :type "Monster", :damage 6, :health 7, :cost 6}}

Related

Recursive map query using specter

Is there a simple way in specter to collect all the structure satisfying a predicate ?
(./pull '[com.rpl/specter "1.0.0"])
(use 'com.rpl.specter)
(def data {:items [{:name "Washing machine"
:subparts [{:name "Ballast" :weight 1}
{:name "Hull" :weight 2}]}]})
(reduce + (select [(walker :weight) :weight] data))
;=> 3
(select [(walker :name) :name] data)
;=> ["Washing machine"]
How can we get all the value for :name, including ["Ballast" "Hull"] ?
Here's one way, using recursive-path and stay-then-continue to do the real work. (If you omit the final :name from the path argument to select, you'll get the full “item / part maps” rather than just the :name strings.)
(def data
{:items [{:name "Washing machine"
:subparts [{:name "Ballast" :weight 1}
{:name "Hull" :weight 2}]}]})
(specter/select
[(specter/recursive-path [] p
[(specter/walker :name) (specter/stay-then-continue [:subparts p])])
:name]
data)
;= ["Washing machine" "Ballast" "Hull"]
Update: In answer to the comment below, here's a version of the above the descends into arbitrary branches of the tree, as opposed to only descending into the :subparts branch of any given node, excluding :name (which is the key whose values in the tree we want to extract and should not itself be viewed as a branching off point):
(specter/select
[(specter/recursive-path [] p
[(specter/walker :name)
(specter/stay-then-continue
[(specter/filterer #(not= :name (key %)))
(specter/walker :name)
p])])
:name]
;; adding the key `:subparts` with the value [{:name "Foo"}]
;; to the "Washing machine" map to exercise the new descent strategy
(assoc-in data [:items 0 :subparts2] [{:name "Foo"}]))
;= ["Washing machine" "Ballast" "Hull" "Foo"]
The selected? selector can be used to collect structures for which another selector matches something within the structure
From the examples at https://github.com/nathanmarz/specter/wiki/List-of-Navigators#selected
=> (select [ALL (selected? [(must :a) even?])] [{:a 0} {:a 1} {:a 2} {:a 3}])
[{:a 0} {:a 2}]
I think you could iterate on map recursively using clojure.walk package. On each step, you may check the current value for a predicate and push it into an atom to collect the result.

Add items from collection 1 to collection 2, if collection 2 doesn't contain item from collection 1

I've got two maps:
(def people {:1 "John" :2 "Paul" :3 "Ringo" :4 "George"})
(def band
{:data
{:members
{:1 {:id 1 :name "John"}
:2 {:id 2 :name "Paul"}}}})
I want to loop over people and add any members that don't exist in [:data :members] to band, resulting in:
(def band
{:data
{:members
{:1 {:id 1 :name "John"}
:2 {:id 2 :name "Paul"}
:3 {:id 3 :name "Ringo"}
:4 {:id 4 :name "George"}}}})
Here's what I've tried:
(for [[id name] people]
(when-not
(contains? (get-in band [:data :members]) id)
(assoc-in band [:data :members id] {:id id :name name})))
Which yields:
({:data
{:members
{:4 {:id :4, :name "George"},
:1 {:name "John", :id 1},
:2 {:name "Paul", :id 2}}}}
nil
nil
{:data
{:members
{:1 {:name "John", :id 1},
:2 {:name "Paul", :id 2},
:3 {:id :3, :name "Ringo"}}}})
I'm not sure why I'm getting back what looks to be a list of each mutation of band. What am I doing wrong here? How can I add the missing members of people to band [:data :members]?
To be pedantic you aren't getting back any mutation of band. In fact, one of the most important features of Clojure is that the standard types are immutible, and the primary collection operations return a modified copy without changing the original.
Also, for in Clojure is not a loop, it is a list comprehension. This is why it always returns a sequence of each step. So instead of altering an input one step at a time, you made a new variation on the input for each step, each derived from the immutable original.
The standard construct for making a series of updated copies of an input based on a sequence of values is reduce, which passes a new version of the accumulator and each element of the list to your function.
Finally, you are misunderstanding the role of :keyword syntax - prefixing an item with a : is not needed in order to construct map keys - just about any clojure value is a valid key for a map, and keywords are just a convenient idiom.
user=> (def band
{:data
{:members
{1 {:id 1 :name "John"}
2 {:id 2 :name "Paul"}}}})
#'user/band
user=> (def people {1 "John" 2 "Paul" 3 "Ringo" 4 "George"})
#'user/people
user=> (pprint
(reduce (fn [band [id name :as person]]
(if-not (contains? (get-in band [:data :members]) id)
(assoc-in band [:data :members id] {:id id :name name})
band))
band
people))
{:data
{:members
{3 {:id 3, :name "Ringo"},
4 {:id 4, :name "George"},
1 {:name "John", :id 1},
2 {:name "Paul", :id 2}}}}
nil
You may notice the body of the fn passed to reduce is essentially the same as the body of your for comprehension. The difference is that instead of when-not which returns nil on the alternate case, I use if-not, which allows us to propagate the accumulator (here called band, same as the input) regardless of whether any new version of it is made.

clojure prewalk with select-keys

(clojure.walk/prewalk #(if (map? %)
(select-keys % [:c])
%)
{:a 1 :b [{:c 3} {:d 4}] :c 5})
=>{:c 5}
why does this only find {:c 5} and not also {:c 3}?
I'm trying to write something that will pull out all key/value pairs that exist for any form and at any level for the key I specify.
When it your function is called with
{:c 5, :b [{:c 3} {:d 4}], :a 1}
...it returns:
{:c 5}
...thus discarding all other keys, including the :b branch, which is thus not traversed.

How to convert list of hashmaps into one hasmap in clojure?

I have a list which looks like this:
({:course 2, :mark 9} {:course 5, :mark 8} {:course 6, :mark 10})
And i want to convert it to hashmap:
{:2 9 :5 8 :6 10}
List was created from mysql database, i dont know can i get that datas from database in some other format, which will be easier to convert to one hashmap, i used java.jdbc query function.
Can anybody help me?
(fn [data] (into {} (map (juxt :course :mark) data)))
on
(list {:course 2, :mark 9} {:course 5, :mark 8} {:course 6, :mark 10})
produces
{2 9, 5 8, 6 10}
The keyword function does not accept numbers as arguments, so I don't think you can get quite what you were looking for. But digits are as good keys as keywords anyway.
Wrong! As others have demonstrated, Clojure accepts digit strings as keywords. We can adapt the above to use such:
(fn [data] (into {} (map (juxt (comp keyword str :course) :mark) data)))
on
(list {:course 2, :mark 9} {:course 5, :mark 8} {:course 6, :mark 10})
produces
{:2 9, :5 8, :6 10}
But, as I've said, I think this is needlessly risky. Why not just use the numbers as keys?
Further to using digit (strings) as keywords:
The reader doc about symbols says
Symbols begin with a non-numeric character ...
Keywords are like symbols, except: ... (further restricted)
Another variant. (Probably less efficient than Thumbnail's if you've got a lot of data.)
(zipmap (map (comp keyword str :course) data)
(map :mark data))
where data is in the format specified in the question.

Appending into nested associative structures

I have a structure that I created at the REPL,
{1 {10 {:id 101, :name "Paul"},
20 {}},
2 {30 {}, 40 {}},
3 {50 {}, 60 {}}}
and I want to add a new k v to the key 1, such that the resulting structure looks like this,
{1 {10 {:id 101, :name "1x2"}, 20 {}, 11 {:id 102, :name "Ringo"}},
2 {30 {}, 40 {}}, 3 {50 {}, 60 {}}}.
I just discovered get-in update-in and assoc-in for working with nested structures like these, but cannot figure out how to add new elements within elements. In my app this is all wrapped in a ref and updated with dosync/alter, but for now, I just want to be able to do this at the REPL.
Maybe I've just been looking at this too long, but any attempt to use assoc or assoc-in just changes what is already there, and does not append new elements.
Given your input
(def input
{1 {10 {:id 101 :name "Paul"}
20 {}}
2 {30 {} 40 {}}
3 {50 {} 60 {}}})
You can use assoc-in to add an element to the nested map with key 1 like this:
(assoc-in input [1 11] {:id 102 :name "Ringo"})
which yields
{1 {10 {:id 101 :name "Paul"}
11 {:id 102 :name "Ringo"}
20 {}}
2 {30 {} 40 {}}
3 {50 {} 60 {}}}
Assoc-in doesn't need to point all the way to the deepest level of a structure.
If you use two calls to assoc-in you can use the second one to change the name "Paul" to "1x2" as per your example:
(assoc-in
(assoc-in input [1 11] {:id 102 :name "Ringo"})
[1 10 :name] "1x2"))
Which returns
{1 {10 {:id 101 :name "1x2"}
11 {:id 102 :name "Ringo"}
20 {}}
2 {30 {} 40 {}}
3 {50 {} 60 {}}}
For what it's worth you could still do this if you had to point to an existing node:
(update-in input [1] assoc 11
{:id 102 :name "Ringo"})