Clojure - How i can count vector of entry maps - clojure

How i can count mobile and web access discarding a nil values from a list of maps? the output should be anything like this " Statistic mobile = 1 web = 2", but all is imutable on other languagens a simple i++ resolve but how is in clojure. thanks.
def data [{:name "app1" :type "mobile" }
{:name "site1" :type "web" }
{:name "site1" :type "web" }
{:name "boot" :type nil }]

(frequencies (map :type data))
gives
{"mobile" 1, "web" 2, nil 1}

user=> (for [[k v] (group-by :type data) :when k] [k (count v)])
(["mobile" 1] ["web" 2])

Related

How to filter a collection of maps into group-by map by value?

Lets say I have a collection like:
(def xs
[{:name "Apple" :type "Fruit is a type"}
{:name "Tomato" :type "Vegetable are food"}
{:name "Pear" :type "the type can also be Fruit"}
{:name "Steak" :type "eat less Meat"}])
And I want to filter and group-by the collection into something like this:
{:Fruit [{:name "Apple" :type "Fruit is a type"} {:name "Pear" :type "the type can also be Fruit"}] :Vegetable [{:name "Tomato" :type "Vegetable are food"}]
I currently just filter the results but can't seem to figure out a good way to group-by. Here's what I have so far:
(defn filter-response [x query]
(filter #(s/includes? (:type %) query) x))
(defn group-by-types [queries]
(map #(filter-response xs %) queries))
(group-by-types ["Fruit" "Vegetable"])
How can I accomplish this?
Updated Answer
You can use a list comprehension to check each item in the collection for each pattern.
(defn- all-occurrences [xs patterns]
(for [x xs
pattern patterns
:when (clojure.string/includes? (:type x) pattern)]
[(keyword pattern) x]))
Or using your filter-response function:
(defn- all-occurrences [xs patterns]
(for [pattern patterns
x (filter-response xs pattern)]
[(keyword pattern) x]))
Then use reduce with update to merge the list of occurrences into a single map:
(defn group-by-patterns [xs patterns]
(reduce (fn [m [pattern text]] (update m pattern conj text))
{}
(all-occurrences xs patterns)))
Calling it with the new input:
(def xs
[{:name "Apple" :type "Fruit is a type"}
{:name "Tomato" :type "Vegetable are food"}
{:name "Pear" :type "the type can also be Fruit"}
{:name "Steak" :type "eat less Meat"}])
(group-by-patterns xs ["Fruit" "Vegetable"])
=> {:Fruit ({:name "Pear", :type "the type can also be Fruit"} {:name "Apple", :type "Fruit is a type"}),
:Vegetable ({:name "Tomato", :type "Vegetable are food"})}
Original Answer
First you can use group-by to group by values under specified keys:
(def xs
[{:name "Apple" :type "Fruit"}
{:name "Tomato" :type "Vegetable"}
{:name "Pear" :type "Fruit"}
{:name "Steak" :type "Meat"}])
erdos=> (group-by :type xs)
{"Fruit" [{:name "Apple", :type "Fruit"} {:name "Pear", :type "Fruit"}],
"Vegetable" [{:name "Tomato", :type "Vegetable"}],
"Meat" [{:name "Steak", :type "Meat"}]}
Then use select-keys to filter the keys:
erdos=> (select-keys (group-by :type xs) ["Fruit" "Vegetable"])
{"Fruit" [{:name "Apple", :type "Fruit"} {:name "Pear", :type "Fruit"}],
"Vegetable" [{:name "Tomato", :type "Vegetable"}]}
If you need keyword keys, you need an extra mapping step:
erdos=> (into {}
(for [[k v] (select-keys (group-by :type xs) ["Fruit" "Vegetable"])]
[(keyword k) v]))
{:Fruit [{:name "Apple", :type "Fruit"} {:name "Pear", :type "Fruit"}],
:Vegetable [{:name "Tomato", :type "Vegetable"}]}

How do I filter a list of vectors in Clojure?

I am new to Clojure and learning the properties of various data structures in Clojure. Here, I have a list of vectors as follows:
(["1" "Christiano Ronaldo" "Portugal" "35"]
["2" "Lionel Messi" "Argentina" "32"]
["3" "Zinedine Zidane" "France" "47"])
where the first element of each vector is the id. How do I filter out single vectors from the list based on the id? For eg., id = 1 should return
["1" "Christiano Ronaldo" "Portugal" "35"]
I tried doing the same on a nested-map:
(def footballers
[
{:id 1 :name "Christiano Ronaldo" :country "Portugal" :age 35}
{:id 2 :name "Lionel Messi" :country "Argentina" :age 32}
{:id 3 :name "Zinedine Zidane" :country "France" :age 47}
]
)
and was successful using the filter function
(filter #(= (:id %) 1) footballers)
Result:
({:id 1, :name "Christiano Ronaldo", :country "Portugal", :age 35})
How do I do the same in a list of vectors using filter function?
(filterv #(= "1" (first %)) footballers) ; or `filter`
;=> [["1" "Christiano Ronaldo" "Portugal" "35"]] ; vector containing 1 vector
Please see this list of documentation.

Accessing elements of a Clojure map in a vector of maps

I have:
(def moo (my-func))
which returns:
[{:id 1 :name "Bob"}
{:id 2 :name "Jane"}
{:id 3 :name "Greg"}]
How do I now access moo to get the :name where :id=3? Thanks.
I would rather prefer using some (since it is more logical than using filter i guess, because it is designed to find exactly one value):
(def data
[{:id 1 :name "Bob"}
{:id 2 :name "Jane"}
{:id 3 :name "Greg"}])
(defn name-by-id [id data]
(some #(when (= (:id %) id) (:name %)) data))
user>
(name-by-id 3 data)
"Greg"
user>
(name-by-id 100 data)
nil
One way would be to use a filter
(def moos
[{:id 1 :name "Bob"}
{:id 2 :name "Jane"}
{:id 3 :name "Greg"}])
(defn name-for-id
[id]
(:name (first (filter #(= (:id %) id) moos))))
(name-for-id 3) ; => "Greg"
(def names
[{:id 1 :name "Bob"}
{:id 2 :name "Jane"}
{:id 3 :name "Greg"}])
;;get the :name where :id=3
(defn answer []
(:name (first (filter (fn [e] (= 3 (:id e))) names))))
In the above rather than names you could have moo.

Basic Clojure: how do I flatten a nested list?

Please look at the following code:
(def data {:color ["R", "B", "G"] :name "Hello" :up "down"})
(defn collapse-vector-kvp [k v]
(map #(hash-map k %) v))
(defn collapse-map [m]
(map #(let
[x %]
(if (vector? (val x))
(collapse-vector-kvp (key x) (val x))
(hash-map (key x) (val x))
)) m))
(collapse-map data)
=> ({:name "Hello"} ({:color "R"} {:color "B"} {:color "G"}) {:up "down"})
What I would like to do is create a single list, rather than have the 'color' entries be in a list within the list. Is this easily achievable?
user=> (def data2 '({:name "Hello"} ({:color "R"} {:color "B"} {:color "G"}) {:up "down"}))
#'user/data2
user=> (flatten data2)
({:name "Hello"} {:color "R"} {:color "B"} {:color "G"} {:up "down"})
Another version of collapse-map:
(defn collapse-map [m]
(let [sep-m (group-by (comp vector? val) m)]
(concat (map (fn [[k v]] {k v})
(sep-m false))
(apply concat (map (fn [[k v]]
(collapse-vector-kvp k v))
(sep-m true))))))
(def test-data {:color ["R" "B" "G"]
:name "Hello"
:k ["v1" "v2" "v3"]
:up "down"})
(collapse-map test-data)
=> ({:name "Hello"}
{:up "down"}
{:color "R"}
{:color "B"}
{:color "G"}
{:k "v1"}
{:k "v2"}
{:k "v3"})

Merging Arrays in Clojure

I need to merge a collection of arrays based on id.
Example data:
EDIT: (changed to match Clojure data structures)
[{:id 1, :region :NA, :name :Test1, :OS :W}
{:id 1, :region :EU, :name :Test2, :OS :W}
{:id 2, :region :AS, :name :test3, :OS :L}
{:id 2, :region :AS, :name :test4, :OS :M}]
Becomes:
EDIT: (changed to match Clojure data structures)
[{:id 1, :region [:NA :EU], :name [:Test1 :Test2] ,:OS [:W]}
{:id 2, :region [:AS] :name [:test3 :Test4], :OS [:L :M]}]
| is the delimiter (changeable)
If possible, also would like alphabetical order as well.
(def data
[{:id 1, :region :NA, :name :Test1, :OS :W}
{:id 1, :region :EU, :name :Test2, :OS :W}
{:id 2, :region :AS, :name :test3, :OS :L}
{:id 2, :region :AS, :name :test4, :OS :M}])
(defn key-join
"join of map by key , value is distinct."
[map-list]
(let [keys (keys (first map-list))]
(into {} (for [k keys] [k (vec (set (map #(% k) map-list)))]))))
(defn group-reduce [key map-list]
(let [gdata (group-by key map-list)]
(into [] (for [[k m] gdata] (let [m (key-join m)](assoc m key ((key m) 0)))))))
user=> (group-reduce :id data)
[{:name [:Test2 :Test1], :OS [:W], :region [:EU :NA], :id 1} {:name [:test3 :test4], :OS [:L :M], :region [:AS], :id 2}]
You can use some combination of functions from clojure.set (if you change the outermost vector to set). Specifically clojure.set/index looks promising.
You can use the merge-with function as shown below in the example.
Firstly, we define some helper functions
(defn collect [& xs]
(apply vector (-> xs distinct sort)))
The collect function makes sure that the items in xs are unique and sorted and finally returns them in a vector.
(defn merge-keys [k xs]
(map #(apply merge-with collect %) (vals (group-by k xs))))
merge-keys first groups the hash-maps in xs by a primary key (in your case :id), takes each list of grouped items and merges the values of the keys using the collect function from above.
(def xs [{:id 1, :region :NA, :name :Test1, :OS :W}
{:id 1, :region :EU, :name :Test2, :OS :W}
{:id 2, :region :AS, :name :test3, :OS :L}
{:id 2, :region :AS, :name :test4, :OS :M}])
(merge-keys :id xs)
=> ({:id [1],
:region [:EU :NA],
:name [:Test1 :Test2],
:OS [:W]}
{:id [2],
:region [:AS],
:name [:test3 :test4],
:OS [:L :M]})
Note however that even the :id key now has vector associated with it. You can easily un-vector it by either introducing an if statement in collect which associates a single value with the key instead of a vector...
(defn collect [& xs]
(let [cs (apply vector (-> xs distinct sort))]
(if (= 1 (count cs)) (first cs) cs)))
...or take the result from merge-keys and do
(map #(update-in % [:id] first) result)
which will only un-vector the :id map entry