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))
Related
I have a map with collection of these {:id 2489 ,values :.......} {:id 5647 ,values : .....} and so on till 10000 and I want to filter its value dependent on another collection which has ids of first one like (2489 ,......)
I am new to clojure and I have tried :
(into {} (filter #(some (fn [u] (= u (:id %))) [2489 3456 4567 5689]) record-sets))
But it gives me only the last that is 5689 id as output {:id 5689 ,:values ....}, while I want all of them, can you suggest what I can do.
One problem is that you start out with a sequence of N maps, then you try to stuff them into a single map. This will cause the last one to overwrite the first one.
Instead, you need to have the output be a sequence of M maps (M <= N).
Something like this is what you want:
(def data
[{:id 1 :stuff :fred}
{:id 2 :stuff :barney}
{:id 3 :stuff :wilma}
{:id 4 :stuff :betty}])
(let [ids-wanted #{1 3}
data-wanted (filterv
(fn [item]
(contains? ids-wanted (:id item)))
data)]
(println data-wanted))
with result:
[{:id 1, :stuff :fred}
{:id 3, :stuff :wilma}]
Be sure to use the Clojure CheatSheet: http://jafingerhut.github.io/cheatsheet/clojuredocs/cheatsheet-tiptip-cdocs-summary.html
I like filterv over plain filter since it always gives a non-lazy result (i.e. a Clojure vector).
You are squashing all your maps into one. First thing, for sake of performance, is to change your list of IDs into a set, then simply filter.
(let [ids (into #{} [2489 3456 4567 5689])]
(filter (comp ids :id) record-sets))
This will give you the sequence of correct maps. If you want to covert this sequence of maps into a map keyed by ID, you can do this:
(let [ids (into #{} [2489 3456 4567 5689])]
(->> record-sets
(filter (comp ids :id))
(into {} (map (juxt :id identity)))))
Another way to do this could be with the use of select-keys functions in Clojure
select-keys returns a map of only the keys given to the function
so given that your data is a list of maps we can convert it into a hash-map of ids using group-by and then call select-keys on it
(def data
[{:id 1 :stuff :fred}
{:id 2 :stuff :barney}
{:id 3 :stuff :wilma}
{:id 4 :stuff :betty}])
(select-keys (group-by :id data) [1 4])
; => {1 [{:id 1, :stuff :fred}], 4 [{:id 4, :stuff :betty}]}
However now the values is a map of ids. So in order to get the orignal structure back we need get all the values in the map and then flatten the vectors
; get all the values in the map
(vals (select-keys (group-by :id data) [1 4]))
; => ([{:id 1, :stuff :fred}] [{:id 4, :stuff :betty}])
; flatten the nested vectors
(flatten (vals (select-keys (group-by :id data) [1 4])))
; => ({:id 1, :stuff :fred} {:id 4, :stuff :betty})
Extracting the values and flattening might seem a bit inefficient but i think its less complex then the nested loop that needs to be done in the filter based methods.
You can using the threading macro to compose all the steps together
(-> (group-by :id data)
(select-keys [1 4])
vals
flatten)
Another thing that you can do is to store the data as a map of ids from the beginning this way using select keys wont require group-by and the result wont require flattening.
Update all keys in a map
(update-values (group-by :id data) first)
; => {1 {:id 1, :stuff :fred}, 2 {:id 2, :stuff :barney}, 3 {:id 3, :stuff :wilma}, 4 {:id 4, :stuff :betty}}
This would probably be the most efficient for this problem but this structure might not work for every case.
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.
It gives me a ArrayMap as I code
(class (hash-map))
But it comes out a HashMap when I code:
(class (hash-map "" ""))
Question is "How can I create an empty hash-map"?
Another possibility is to use pre-defined EMPTY field:
user=> (clojure.lang.PersistentHashMap/EMPTY)
{}
In my opinion it is better shows your intent.
You can create empty hash-map like this:
(. clojure.lang.PersistentHashMap create {})
(clojure.lang.PersistentHashMap/create {})
(clojure.lang.PersistentHashMap/EMPTY)
You can check the source code of hash-map:
user=> (source hash-map)
(defn hash-map
"keyval => key val
Returns a new hash map with supplied mappings. If any keys are
equal, they are handled as if by repeated uses of assoc."
{:added "1.0"
:static true}
([] {})
([& keyvals]
(. clojure.lang.PersistentHashMap (create keyvals))))
As you can see in the code, if you don't provide arguments, hash-map function returns {}, which is the instance of PersistentArrayMap.
If you really need the instance of empty PersistentHashMap, you can create it with the following code:
(. clojure.lang.PersistentHashMap create {})
You can check the class of created instance:
user=> (class (. clojure.lang.PersistentHashMap create {}))
clojure.lang.PersistentHashMap
user=> (class (clojure.lang.PersistentHashMap/create {}))
clojure.lang.PersistentHashMap
user=> (class (clojure.lang.PersistentHashMap/EMPTY)) ;; om-nom-nom's : much simpler
clojure.lang.PersistentHashMap
But, I'm not sure that doing this is good or necessary. Perhaps you code shouldn't depend on specific implementation class.
You shouldn't really need to worry about this. The runtime makes a judgement on the best implementation to use. PersistentArrayMap is preferred (ie. it's more efficient in time and space) for a small number of key/value pairs, but promotion to PersistentHashMap happens once the kv limit of 8 is crossed, see the relevant code for details
*clojure-version*
{:major 1, :minor 5, :incremental 1, :qualifier nil}
; map declared with {} with 8 kv pairs is ArrayMap
(type {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8})
=> clojure.lang.PersistentArrayMap
; map declared with {} with 9 kv pairs is HashMap
(type {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9})
=> clojure.lang.PersistentHashMap
; assoc'ing 1 kv pairs into an ArrayMap is an ArrayMap (oddly)
(type (-> {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8}
(assoc :i 9)))
clojure.lang.PersistentArrayMap
; assoc'ing 2 kv pairs into an ArrayMap is an HashMap
(type (-> {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8}
(assoc :i 9)
(assoc :j 10)))
clojure.lang.PersistentHashMap
i have a series of items in a set like this:
(def my-set
#{
{:id "ab" :a 1 :b 2}
{:id "abc" :a 1 :b 2}
{:id "abcd" :a 1 :b 2}
}
)
: and I wish to update one of the items something like this :
(update-in-set my-set :id "abc" {:id "abc" :a 6 :b 20})
. that would return :
#{
{:id "ab" :a 1 :b 2}
{:id "abc" :a 6 :b 20}
{:id "abcd" :a 1 :b 2}
}
: Is there any Clojure built in function or other easy way to do this?
Update
In the end I did this:
(defn update-in-set [my-set key value new-record]
(merge (clojure.set/select #(not= (get % key) value) my-set ) new-record)
)
I wonder if you shouldn't be using a map rather than a set here, with id as the key. Then what you want to do could be easily performed with assoc.
You are having problems as sets don't really have the idea of updating values - each item is unique and either present or not - so what you need to do is remove the old value and add a new one. This could be done a little easier with conj and disj I think:
(conj (disj #{'a 'b 'c} 'a) 'e)
Which would remove 'a and add 'e. This assumes you have some way of getting the complete item from the "key".
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