Building a map from a vector - clojure

So I have a vector which sort of looks like this
["John" 23 "5551234" "Sally" 34 "5556667"]
the vector contains a lot more entries like this, what I am trying to do is make a vector of maps like this:
[{:name "John" :age 23 :ph "5551234"} {:name "Sally" :age 34 :ph "5556667"}]
Is there any way to accomplish this?

(def sample ["John" 23 "5551234" "Sally" 34 "5556667" "Harry" 42 "5554242"])
Partition the input vector into records using e.g. (partition 3 sample) (each record has 3 elements) and then
Map a zipmap:
(mapv #(zipmap [:name :age :ph] %) (partition 3 sample))
; => [{:ph "5551234", :age 23, :name "John"}
; {:ph "5556667", :age 34, :name "Sally"}
; {:ph "5554242", :age 42, :name "Harry"}]
Or use for comprehension (returns a lazy sequence rather than a vector):
(for [[name age ph] (partition 3 sample)] {:name name :age age :ph ph})
; => ({:name "John", :age 23, :ph "5551234"}
{:name "Sally", :age 34, :ph "5556667"}
{:name "Harry", :age 42, :ph "5554242"})
Note key order is not defined for maps. The for comphrehension is using an array-map since the number of key-value pairs is small, and thus the keys appear in order, but this is an implementation detail. You can explicitly use array-maps if order is important but will have a performance penalty for look-ups on larger maps.

Another couple options:
(->> ["John" 23 "5551234" "Sally" 34 "5556667"]
(partition 3)
(map (fn [[name age ph]]
{:name name :age age :ph ph})))
(->> ["John" 23 "5551234" "Sally" 34 "5556667"]
(partition 3)
(map (partial interleave [:name :age :ph]))
(map (partial apply hash-map)))

Related

Map from list of maps

My problem is next, i have list of maps, for example:
({:id 1 :request-count 10 ..<another key-value pair>..}
{:id 2 :request-count 15 ..<another key-value pair>..}
...)
Need create map with records in which, key is value of 'id' and value is value of 'request-count', for each map from prev example, like:
{1 10
2 15
...}
I know how to do this. My question is - standard library have function for achieve this? Or maybe i can achiev this with combination few function, without 'reduce'?
Use the juxt function to generate a sequence of pairs, and then toss them into a map:
(into {} (map (juxt :id :request-count) data))
Example:
user=> (def data [{:id 1 :request-count 10 :abc 1}
#_=> {:id 2 :request-count 15 :def 2}
#_=> {:id 3 :request-count 20 :ghi 3}])
#'user/data
user=> (into {} (map (juxt :id :request-count) data))
{1 10, 2 15, 3 20}
Be aware that if there is more than one map in data with the same :id, then the last one encountered by map will be the one that survives in the output map.
I would do it like so:
(def data
[{:id 1 :request-count 10}
{:id 2 :request-count 15}] )
(defn make-id-req-map [map-seq]
(vec (for [curr-map map-seq]
(let [{:keys [id request-count]} curr-map]
{id request-count}))))
With result:
(make-id-req-map data) => [{1 10} {2 15}]
Note: while you could combine the map destructuring into the for statement, I usually like to label the intermediate values as described in Martin Fowler's refactoring "Introduce Explaining Variable".

Apply a function to multiple values of a hashmap and return the result in an array

I have a definition:
(def class1 {:people ({:name "John" :age "25"} {:name "Harry" :age "23"} {:name "Peter" :age "24"})})
The result I want is a vector that looks like
[["John" "25"]
["Harry" "23"]
["Peter" "24"]]
If I call (map (-> class1 :people) [:name :age])
then I get the result ("Peter" "24"). Why do I only get the values from the last hashmap and not the others?
If I then call (into [] (map (-> class1 :people) [:name :age]))
then I get the result ["Peter" "24"]
What I need to do is run the into function again on the other two hashmaps and then put the 3 results into a vector but I don't know how to do this.
My problem is that when I run (map (-> class1 :people) [:name :age]), I get the last of the hashmaps. I believe that you have to do something like run a function on each of the hashmaps, one at a time and then each time put that vector into another vector to get the form that I am looking for.
Any help would be much appreciated
(def class1 {:people ({:name "John" :age "25"} {:name "Harry" :age "23"} {:name "Peter" :age "24"})})
defines class1 as the map:
{:people {:name "Peter" :age "24"}}
because a list of the form (a b c) evaluates a b and c in turn before evaluating to c. If you want your map to contain all the maps you need to quote it:
(def class1 {:people '({:name "John" :age "25"} {:name "Harry" :age "23"} {:name "Peter" :age "24"})})
To extract the values you want from this map you can use:
(mapv (juxt :name :age) (:people class1))

How to group-by a collection that is already grouped by in Clojure?

I have a collection of maps
(def a '({:id 9345 :value 3 :type "orange"}
{:id 2945 :value 2 :type "orange"}
{:id 145 :value 3 :type "orange"}
{:id 2745 :value 6 :type "apple"}
{:id 2345 :value 6 :type "apple"}))
I want to group this first by value, followed by type.
My output should look like:
{
:orange [{
:value 3,
:id [9345, 145]
}, {
:value 2,
:id [2935]
}],
:apple [{
:value 6,
:id [2745, 2345]
}]
}
How would I do this in Clojure? Appreciate your answers.
Thanks!
Edit:
Here is what I had so far:
(defn by-type-key [data]
(group-by #(get % "type") data))
(reduce-kv
(fn [m k v] (assoc m k (reduce-kv
(fn [sm sk sv] (assoc sm sk (into [] (map #(:id %) sv))))
{}
(group-by :value (map #(dissoc % :type) v)))))
{}
(by-type-key a))
Output:
=> {"orange" {3 [9345 145], 2 [2945]}, "apple" {6 [2745 2345], 3 [125]}}
I just couldnt figure out how to proceed next...
Your requirements are a bit inconsistent (or rather irregular) - you use :type values as keywords in the result, but the rest of the keywords are carried through. Maybe that's what you must do to satisfy some external formats - otherwise you need to either use the same approach as with :type through, or add a new keyword to the result, like :group or :rows and keep the original keywords intact. I will assume the former approach for the moment (but see below, I will get to the shape as you want it,) so the final shape of data is like
{:orange
{:3 [9345 145],
:2 [2945]},
:apple
{:6 [2745 2345]}
}
There is more than one way of getting there, here's the gist of one:
(group-by (juxt :type :value) a)
The result:
{["orange" 3] [{:id 9345, :value 3, :type "orange"} {:id 145, :value 3, :type "orange"}],
["orange" 2] [{:id 2945, :value 2, :type "orange"}],
["apple" 6] [{:id 2745, :value 6, :type "apple"} {:id 2345, :value 6, :type "apple"}]}
Now all rows in your collection are grouped by the keys you need. From this, you can go and get the shape you want, say to get to the shape above you can do
(reduce
(fn [m [k v]]
(let [ks (map (comp keyword str) k)]
(assoc-in m ks
(map :id v))))
{}
(group-by (juxt :type :value) a))
The basic idea is to get the rows grouped by the key sequence (and that's what group-by and juxt do,) and then combine reduce and assoc-in or update-in to beat the result into place.
To get exactly the shape you described:
(reduce
(fn [m [k v]]
(let [type (keyword (first k))
value (second k)
ids (map :id v)]
(update-in m [type]
#(conj % {:value value :id ids}))))
{}
(group-by (juxt :type :value) a))
It's a bit noisy, and it might be harder to see the forest for the trees - that's why I simplified the shape, to highlight the main idea. The more regular your shapes are, the shorter and more regular your functions become - so if you have control over it, try to make it simpler for you.
I would do the transform in two stages (using reduce):
the first to collect the values
the second for formating
The following code solves your problem:
(def a '({:id 9345 :value 3 :type "orange"}
{:id 2945 :value 2 :type "orange"}
{:id 145 :value 3 :type "orange"}
{:id 2745 :value 6 :type "apple"}
{:id 2345 :value 6 :type "apple"}))
(defn standardise [m]
(->> m
;; first stage
(reduce (fn [out {:keys [type value id]}]
(update-in out [type value] (fnil #(conj % id) [])))
{})
;; second stage
(reduce-kv (fn [out k v]
(assoc out (keyword k)
(reduce-kv (fn [out value id]
(conj out {:value value
:id id}))
[]
v)))
{})))
(standardise a)
;; => {:orange [{:value 3, :id [9345 145]}
;; {:value 2, :id [2945]}],
;; :apple [{:value 6, :id [2745 2345]}]}
the output of the first stage is:
(reduce (fn [out {:keys [type value id]}]
(update-in out [type value] (fnil #(conj % id) [])))
{}
a)
;;=> {"orange" {3 [9345 145], 2 [2945]}, "apple" {6 [2745 2345]}}
You may wish to use the built-in function group-by. See http://clojuredocs.org/clojure.core/group-by

How to read a file with test data in with Clojure?

I am writing a piece of code that needs to read in a text file that has data. The text file is in the format:
name 1 4
name 2 4 5
name 3 1 9
I am trying to create a vector of a map in the form [:name Sarah :weight 1 cost :4].
When I try reading the file in with the line-seq reader, it reads each line as an item so the partition is not correct. See repl below:
(let [file-text (line-seq (reader "C://Drugs/myproject/src/myproject/data.txt"))
new-test-items (vec (map #(apply struct item %) (partition 3 file-text)))]
(println file-text)
(println new-test-items))
(sarah 1 1 jason 4 5 nila 3 2 jonas 5 6 judy 8 15 denny 9 14 lis 2 2 )
[{:name sarah 1 1, :weight jason 4 5, :value nila 3 2 } {:name jonas 5 6, :weight judy 8 15, :value denny 9 14}]
I then tried to just take 1 partition, but still the structure is not right.
=> (let [file-text (line-seq (reader "C://Drugs/myproject/src/myproject/data.txt"))
new-test-items (vec (map #(apply struct item %) (partition 1 file-text)))]
(println file-text)
(println new-test-items))
(sarah 1 1 jason 4 5 nila 3 2 jonas 5 6 judy 8 15 denny 9 14 lis 2 2 )
[{:name sarah 1 1, :weight nil, :value nil} {:name jason 4 5, :weight nil, :value nil} {:name nila 3 2 , :weight nil, :value nil} {:name jonas 5 6, :weight nil, :value nil} {:name judy 8 15, :weight nil, :value nil} {:name denny 9 14, :weight nil, :value nil} {:name lis 2 2, :weight nil, :value nil} {:name , :weight nil, :value nil}]
nil
Next I tried to slurp the file, but that is worse:
=> (let [slurp-input (slurp "C://Drugs/myproject/src/myproject/data.txt")
part-items (partition 3 slurp-input)
mapping (vec (map #(apply struct item %) part-items))]
(println slurp-input)
(println part-items)
(println mapping))
sarah 1 1
jason 4 5
nila 3 2
jonas 5 6
judy 8 15
denny 9 14
lis 2 2
((s a r) (a h ) (1 1) (
Please help! This seems like such an easy thing to do in Java, but is killing me in Clojure.
split it into a sequence of lines:
(line-seq (reader "/tmp/data"))
split each of them into a sequence of words
(map #(split % #" ") data)
make a function that takes a vector of one data and turns it into a map with the correct keys
(fn [[name weight cost]]
(hash-map :name name
:weight (Integer/parseInt weight)
:cost (Integer/parseInt cost)))
then nest them back together
(map (fn [[name weight cost]]
(hash-map :name name
:weight (Integer/parseInt weight)
:cost (Integer/parseInt cost)))
(map #(split % #" ") (line-seq (reader "/tmp/data"))))
({:weight 1, :name "name", :cost 4}
{:weight 2, :name "name", :cost 4}
{:weight 3, :name "name", :cost 1})
you can also make this more compact by using zip-map
You are trying to do everything in one place without testing intermediate results. Instead Clojure recommends to decompose task into a number of subtasks - this makes code much more flexible and testable. Here's the code for your task (I assume records in file describe people):
(defn read-lines [filename]
(with-open [rdr (clojure.java.io/reader filename)]
(doall (line-seq rdr))))
(defn make-person [s]
(reduce conj (map hash-map [:name :weight :value] (.split s " "))))
(map make-person (read-lines "/path/to/file"))

Filter for Lists in Clojure

I am having a bit of difficulty with Lists in Clojure
I have a quick question concerning the filter function
Let's say I have a List composed of Maps
My code is:
(def Person {:name Bob } )
(def Person2 {:name Eric } )
(def Person3 {:name Tim } )
(def mylist (list Person Person2 Person3))
How would i go about filtering my list so that , for example: I want the list minus Person2 (meaning minus any map that has :name Eric)
Thank you very much to everybody helping me out. This is my last question I promise
For this purpose, it's better to use the 'remove' function. It takes a sequence, and removes elements on which it's predicate returns 'true'. It's basically the opposite of filter. Here is an example of it, and filter's usage for the same purposes, that I worked up via the REPL.
user> (def m1 {:name "eric" :age 32})
#'user/m1
user> (def m2 {:name "Rayne" :age 15})
#'user/m2
user> (def m3 {:name "connie" :age 44})
#'user/m3
user> (def mylist (list m1 m2 m3))
#'user/mylist
user> (filter #(not= (:name %) "eric") mylist)
({:name "eric", :age 32})
user> (remove #(= (:name %) "eric") mylist)
({:name "Rayne", :age 15} {:name "connie", :age 44})
As you can see, remove is a little bit cleaner, because you don't have to use not=. Also, when working with maps, you don't have to use the 'get' function unless you want it to return something special if a key isn't in the map. If you know the key you're looking for will be in the map, there is no reason to use 'get'. Good luck!
Suppose you have something like this:
(def Person {:name "Bob" } )
(def Person2 {:name "Eric" } )
(def Person3 {:name "Tim" } )
(def mylist (list Person Person2 Person3))
This would work:
(filter #(not= "Eric" (get % :name)) mylist)
user=> (filter (fn [person] (not= (person :name) "Eric")) mylist)
({:name "Bob"} {:name "Tim"})
or using a more compact syntax:
user=> (filter #(not= (% :name) "Eric") mylist)
({:name "Bob"} {:name "Tim"})