Getting value at specified index from a hash map in clojure - clojure

I am doing my second homework working with Clojure and am having difficulty getting a value at a specified index from a hash map. Currently I have a function to create a vector of hash maps that represent a graph. I then index into that vector to get a specific hash map representing a node, specifically the nodes connections to other nodes; as such
homework2.core> (reg-ring-lattice 5 2)
[#{1 4} #{0 2} #{1 3} #{4 2} #{0 3}]
homework2.core> ((reg-ring-lattice 5 2) 2)
#{1 3}
My issue is iterating over the values in the hash map itself. I would specifically like to retrieve the nth index from the hash map. Here is what I have tried
homework2.core> ((reg-ring-lattice 5 2) 3)
#{4 2}
homework2.core> (((reg-ring-lattice 5 2) 3) 0)
nil
homework2.core> (get ((reg-ring-lattice 5 2) 3) 0)
nil
I have searched but currently my knowledge of Clojure and its keywords/jargon is limited.
The first question is, am I calling these collections by their proper names. Second, how can I retrieve a value at a specified index in what I am calling a hashmap?

those are no hash MAPS but hash SETS and they are not sorted. You would get a "random" element on sequential/indexed access. E.g.
user=> (first #{1 2 3 4 5})
1
user=> (second #{1 2 3 4 5})
4

A hash-map is a dictionary, key/value pairs. They show up in the output surrounded in curly braces -> {}. A set is something like a list but must contain unique items. Sets show up in the output surrounded by an octothorpe with curly braces -> #{}. Vectors are structures that contain items in an indexed sequence. They show up in the output surrounded in square braces -> [].
See hash-maps at the clojure.org docs, sets at the clojure.org docs, and vectors at the clojure.org docs.
Your reg-ring-lattice function is returning a vector of sets. It doesn't make much sense to index into a set made up of numbers like you have so let me go over what you are doing in your example code blocks.
;This output is a vector of sets
(reg-ring-lattice 5 2)
[#{1 4} #{0 2} #{1 3} #{4 2} #{0 3}]
;This output exploits the fact that vectors implement IFn (see the doc links)
; Surrounding the call in another set of parens makes another call the argument
; of which is '2'. You seem to have a good grasp on all this so far.
((reg-ring-lattice 5 2) 2) ; returns the item at index 2 in the vector
#{1 3}
;This is exactly the same as the above except for getting the item at index 3.
((reg-ring-lattice 5 2) 3)
#{4 2}
;This is what doesn't make sense. Sets are functions of their members. They
;aren't indexed like vectors are
(((reg-ring-lattice 5 2) 3) 0)
; What you are saying here is 'give me item 0, not index 0, out of the set
; returned from looking into the vector at index 3 or nil if not found'.
; It would work if your set had a 0 in it or if you did this:
(((reg-ring-lattice 5 2) 3) 4) ; -> returns 4
; but it's not too helpful because you already know what you're looking up.
;Instead, this is where you start treating the returned set like a list
(first ((reg-ring-lattice 5 2) 3)) ; -> returns 4
(second ((reg-ring-lattice 5 2) 3)) ; -> returns 2
One thing to be wary of with sets, which you can ignore if you are generating sorted sets, they may not be sorted. What is first last time may not be first next time. Another thing to watch for is duplicates. Each item in a set must be unique. You can't have a set like this -> #{4 4}.

As others have pointed out, you are using a vector of sets, not of maps.
Calling a set as a function will return the argument, if it's in the set, otherwise nil:
(#{2 3 4} 1) ;nil
(#{2 3 4} 2); 2
You would like to retrieve the nth element from a set:
(nth (seq #{2 3 4}) 1)
;3
The order may not be what you expect:
(seq #{4 2 3})
; (2 3 4)
But why do you want the nth element anyway? You can enumerate the elements of the set by calling seq on it (implicitly, if you're using functions such as map and reduce). If you show us what you are trying to do, it's likely there's a more idiomatic way to do it. And nth is slow - it tells the sequence element by element.
By the way, by using a vector of sets you are tying yourself to graphs with nodes labelled 0 to n. A map of sets is often a better choice:
{4 #{0 3}, 3 #{2 4}, 2 #{1 3}, 1 #{0 2}, 0 #{1 4}}
... instead of ...
[#{1 4} #{0 2} #{1 3} #{4 2} #{0 3}]

Related

clojure.set/difference fails when result would is empty and right set has higher cardinality

I try to eleminate elements from a set, that are present on a different sequence in Clojure. But the clojure.set/difference doesn't seem to work as I expect it. Where is my error?
Some examples:
Removing empty sequence
(difference #{3 2} '())
Result: #{3 2} (as expected)
Removing some of the elements
(difference #{3 2} '(3))
Result: #{2} (as expected)
Removing elements not present in the set
(difference #{3 2} '(1))
Result: #{3 2} (as expected)
Removing all elements
(difference #{3 2} '(2 3))
Result: #{} (as expected)
Removing more elements that present
(difference #{3 2} '(1 2 3))
Instead of a result I get IllegalArgumentException contains? not supported on type: clojure.lang.PersistentList clojure.lang.RT.contains (RT.java:814)
Expected result: #{}
So it seems, that the clojure.set/difference function fails, when the result is the empty set and the set of elements to be removed has a higher cardinality than the original set. But the question is: why doesn't this work? I'd expect that this is a legal application of clojure.set/difference.
This doesn't work because the difference function compares the sizes of the two inputs and iterates over the smaller collection and removes elements from the first one. If the second argument is larger, it uses contains? to see if an item in the first collection exists in the second. contains? is not supported on lists so you get the exception.
The functions in clojure.set should only be called with set arguments.

Convert a sequence of maps to a map of maps using the value of key in each map

I have a sequence that looks like this: ({:a 1 :b "lorem"} {:a 2 :b "ipsum"}) and I want to convert that to a map of maps using the value of :a as the value of the key in the new map. The result I am expecting is {:1 {:a 1 :b "lorem"} :2 {:a 2 :b "ipsum"}}.
And maybe this is not idiomatic clojure, I'm still learning. I basically receive a large sequence, and I will be looking up values in this sequence by a certain value in each map, and I want to use a map to make it O(1).
In C#, on an IEnumerable, you can call .ToDictionary(x => x.SomeProperty) which would return a Dictionary of key value pairs, using the value of SomeProperty as the key. And this has lookup performance of O(1) instead of the typical O(N) for a list/sequence.
This should do the transform you are after:
(def latin '({:a 1 :b "lorem"} {:a 2 :b "ipsum"}))
(defn transform [kw in]
(zipmap (map kw in) in))
=> (transform :a latin)
{1 {:a 1, :b "lorem"}, 2 {:a 2, :b "ipsum"}}
I hesitated in changing the number to a keyword, as not sure as to the reason you would want to do that...
... edit - because you can always do an indexed (O(1)) retrieve like so:
(get (transform :a latin) 1)
1 doesn't work at the front because it can't be used as a function (unlike :1), however get is a function whose second argument is the search key when the first argument is a map.

Test whether a list doesn't contain a value

I have a vector with maps (each with an id) and I want to select only the maps which id is not in a list of ids. What's the most idiomatic way to do that?
(def input [{:id 1 :asd 8} {:id 2 :asd 4} {:id 3 :asd 7} {:id 4 :asd 4}])
(def connected-ids '(1 3))
;; this is what I want to get:
(def not-connected [{:id 2 :asd 8} {:id 4 :asd 4})
The returned collection doesn't have to be a vector.
My suggestion would be to
Keep the connected IDs in a set: (def connected-ids #{1 3})
This step gives us both deduplication (it doesn't make sense to have an ID in the blacklist twice) and a handy membership test function - simply call the set as a function and it will return the (one) argument iff it is a member, else nil. It's also cheaper in terms of asymptotic time than scanning a list to check whether a value is in it.
Use remove to remove items that we don't want:
(def not-connected (remove (comp connected-ids :id) input))

Clojure assoc vector behaviour

Clojure assoc applied to vector seems have inconsistent behaviour
When index is present in vector, assoc replace the value
(assoc [1 2 3 4 5] 3 42) => [1 2 3 42 5]
When index is next to last one, the vector grows (conj equivalent)
(assoc [1 2 3 4 5] 5 42) => [1 2 3 4 5 42])
Otherwise IndexOutOfBoundsExcpetion is thrown
though it useful in some cases like reduce assoc, this may lead to subtle bugs in a program
Is it expected behaviour or probably bug in assoc for vector?
It is expected. See the docstring for assoc, especially the last note regarding the index argument.
This is described at the top of p. 101 of Clojure Programming.

Why are there so many map construction functions in clojure?

Novice question, but I don't really understand why there are so many operations for constructing maps in clojure.
You have conj, assoc and merge, but they seem to more or less do the same thing?
(assoc {:a 1 :b 2} :c 3)
(conj {:a 1 :b 2} {:c 3})
(merge {:a 1 :b 2} {:c 3})
What's really the difference and why are all these methods required when they do more or less the same thing?
assoc and conj behave very differently for other data structures:
user=> (assoc [1 2 3 4] 1 5)
[1 5 3 4]
user=> (conj [1 2 3 4] 1 5)
[1 2 3 4 1 5]
If you are writing a function that can handle multiple kinds of collections, then your choice will make a big difference.
Treat merge as a maps-only function (its similar to conj for other collections).
My opinion:
assoc - use when you are 'changing' existing key/value pairs
conj - use when you are 'adding' new key/value pairs
merge - use when you are combining two or more maps
Actually these functions behave quite differently when used with maps.
conj:
Firstly, the (conj {:a 1 :b 2} :c 3) example from the question text does not work at all (neither with 1.1 nor with 1.2; IllegalArgumentException is thrown). There are just a handful of types which can be conjed onto maps, namely two-element vectors, clojure.lang.MapEntrys (which are basically equivalent to two-element vectors) and maps.
Note that seq of a map comprises a bunch of MapEntrys. Thus you can do e.g.
(into a-map (filter a-predicate another-map))
(note that into uses conj -- or conj!, when possible -- internally). Neither merge nor assoc allows you to do that.
merge:
This is almost exactly equivalent to conj, but it replaces its nil arguments with {} -- empty hash maps -- and thus will return a map when the first "map" in the chain happens to be nil.
(apply conj [nil {:a 1} {:b 2}])
; => ({:b 2} {:a 1}) ; clojure.lang.PersistentList
(apply merge [nil {:a 1} {:b 2}])
; => {:a 1 :b 2} ; clojure.lang.PersistentArrayMap
Note there's nothing (except the docstring...) to stop the programmer from using merge with other collection types. If one does that, weirdness ensues; not recommended.
assoc:
Again, the example from the question text -- (assoc {:a 1 :b 2} {:c 3}) -- won't work; instead, it'll throw an IllegalArgumentException. assoc takes a map argument followed by an even number of arguments -- those in odd positions (let's say the map is at position 0) are keys, those at even positions are values. I find that I assoc things onto maps more often than I conj, though when I conj, assoc would feel cumbersome. ;-)
merge-with:
For the sake of completeness, this is the final basic function dealing with maps. I find it extremely useful. It works as the docstring indicates; here's an example:
(merge-with + {:a 1} {:a 3} {:a 5})
; => {:a 9}
Note that if a map contains a "new" key, which hasn't occured in any of the maps to the left of it, the merging function will not be called. This is occasionally frustrating, but in 1.2 a clever reify can provide a map with non-nil "default values".
Since maps are such a ubiquitous data structure in Clojure, it makes sense to have multiple tools for manipulating them. The various different functions are all syntactically convenient in slightly different circumstances.
My personal take on the specific functions you mention :
I use assoc to add a single value to a map given a key and value
I use merge to combine two maps or add multiple new entries at once
I don't generally use conj with maps at all as I associate it mentally with lists