Paul
Paul

Reputation: 75

Clojure - Using Filters

I am trying to iterate through a map to count specific occurances of individual events although I am having some trouble using a filter as below. What I want to do is filter and count the specific instances of each "ORDER_CATEGORY" which occur in a number of levels - 1, 2, and so on. I am able to iterate though the months and print but teh filter and count I have for the ORDER_CATEGORY's just doesnt seem to work for me.

 GROUPED_BY_MONTH(group-by :MONTH CON)

 ALL (for [flop GROUPED_BY_MONTH] {
    :MONTH  (:MONTH (first (second flop)))
    :ORDER1 (count (filter #(= "1" (:ORDER_CATEGORY %)) flop))
    :ORDER2 (count (filter #(= "2" (:ORDER_CATEGORY %)) flop))
})

I am working with XML files which I have successfully managed into one concatenated list and grouped as below:

["01" [{:MONTH "01", :ORDER_CATEGORY "1"} {:MONTH "01", :ORDER_CATEGORY "1"} {:MONTH "01", :ORDER_CATEGORY "2"}]] ["02" [{:MONTH "02", :ORDER_CATEGORY "1"} {:MONTH "02", :ORDER_CATEGORY "2"} {:MONTH "02", :ORDER_CATEGORY "2"}]]

Therefore, I would expect to have:

MONTH ORDER1 ORDER2

01--------2--------------1

02--------1--------------2

This may be a relatively simple fix that I'm overlooking so thanks for the help in advance!

Upvotes: 0

Views: 129

Answers (2)

Leon Grapenthin
Leon Grapenthin

Reputation: 9276

Your for loop does not work, because you have bound a map entry like this to flop

["01" [{:MONTH "01", :ORDER_CATEGORY "1"} ...]]

And your filter expressions take that as an input:

(filter #(= "1" (:ORDER_CATEGORY %)) flop)

This will not work, because :ORDER_CATEGORY will neither return sth. for "01", nor for the sequence [{:MONTH ...} ...].

Given that what you have bound to flop is a MapEntry, you can use val to fix this:

(filter #(= "1" (:ORDER_CATEGORY %)) (val flop)).

E. g.:

(for [flop GROUPED_BY_MONTH]
  {:MONTH  (:MONTH (first (second flop)))
   :ORDER1 (count (filter #(= "1" (:ORDER_CATEGORY %)) (val flop)))
   :ORDER2 (count (filter #(= "2" (:ORDER_CATEGORY %)) (val flop)))})
=> ({:MONTH "01", :ORDER1 2, :ORDER2 1} {:MONTH "02", :ORDER1 1, :ORDER2 0})

You could also use destructoring, to make your code more clear:

(for [[month maps] GROUPED_BY_MONTH]
  {:MONTH month
   :ORDER1 (count (filter #(= "1" (:ORDER_CATEGORY %)) maps))
   :ORDER2 (count (filter #(= "2" (:ORDER_CATEGORY %)) maps))})
=> ({:MONTH "01", :ORDER1 2, :ORDER2 1} {:MONTH "02", :ORDER1 1, :ORDER2 0})

Notice that in general, this solution is more favorable:

(reduce (fn [acc {:keys [MONTH ORDER_CATEGORY]}]
          (update-in acc [MONTH ORDER_CATEGORY] 
                     (fnil inc 0)))
        {} CON)
=> {"02" {"1" 1}, "01" {"2" 1, "1" 2}}

It returns a map of month->order-category->count that works independently of the kinds and types of order categories and months present in the input set.

E. g.

(reduce (fn [acc {:keys [MONTH ORDER_CATEGORY]}]
          (update-in acc [MONTH ORDER_CATEGORY]
                     (fnil inc 0)))
        {} (conj CON
                 {:MONTH "01" :ORDER_CATEGORY "foo"}
                 {:MONTH "02" :ORDER_CATEGORY "foo"}
                 {:MONTH "02" :ORDER_CATEGORY "bar"}
                 {:MONTH "02" :ORDER_CATEGORY "1"}))
=> {"02" {"bar" 1, "foo" 1, "1" 2}, "01" {"foo" 1, "2" 1, "1" 2}}

However you should be aware that in case of a count of 0 for an ORDER_CATEGORY, no value is associated in the result.

Upvotes: 1

edbond
edbond

Reputation: 3951

I would first convert the data to:

user=> (pprint x)
[{:MONTH "01", :ORDER_CATEGORY "1"}
 {:MONTH "01", :ORDER_CATEGORY "1"}
 {:MONTH "01", :ORDER_CATEGORY "2"}
 {:MONTH "02", :ORDER_CATEGORY "1"}]

and use functions:

user=> (defn inc1 [x] (if x (inc x) 1))
user=> (reduce (fn [acc {:keys [MONTH ORDER_CATEGORY]}]
                     (update-in acc [[MONTH ORDER_CATEGORY]] inc1)) {} x)
{["02" "1"] 1, ["01" "2"] 1, ["01" "1"] 2}

Key is [Month Category], value is the number of orders.

Upvotes: 2

Related Questions