I'm rewriting a very odd e-commerce website I first wrote in 1996, for a 
friend.

The website features categories, which are supposed to be arranged into an 
acyclic directed graph (actually there are currently cycles in the graph, 
but there shouldn't be, and that is not the problem I'm trying to solve 
here; assume there are not). Customers can register interest in a category 
or in several categories. When an item is added to the site, we want to 
email every customer who may be interested in that item; which is to say 

customers who are interested in the category to which the item has been 
added, and
customers who are interested in the parent of that category, and
so on recursively to the root of the graph.

Now, I can recurse up the graph asking each category in turn for the 
customers interested in it; and by concatenating the lists, arrive at one 
list of all the interested customers. The problem is that if one customer 
has registered interest in both a category and its parent, he will appear 
on the composite list twice. I need to construct from this a list of 
records in which each customer appears only once, otherwise they'll receive 
duplicate emails, which no-one likes.

My solution, which I give below, is to recurse across the composite list 
constructing a map whose keys are email addresses and whose values are the 
customer records as maps; and then to take the values of that map. It 
works, but it doesn't feel like idiomatic clojure:

(defn map-by-key
  "From this `list-of-maps`, produce a map keyed on the values for this 
`key`"
  [list-of-maps key]
  (if (empty? list-of-maps) {}
    (let [record (first list-of-maps)]
      (merge {(record key) record} (map-by-key (rest list-of-maps) key)))))
                        

(defn interested-customers
  "Return a list of the customers interested in the category with this
   `category-id`. Recurses up the tree of categories; `path` is passed to
   protect against cycles in the tree (which shouldn't be there by are
   not impossible)"
  ([category-id]
   (vals (map-by-key (interested-customers category-id #{}) :email)))
  ([category-id path]
   (if (contains? path category-id) ()   
    (let
      [id (if (integer? category-id) category-id
            (Integer/valueOf (str category-id)))
       category (categories/fetch id)
       parent (:parent category)
       upstream (if (not (nil? parent)) (interested-customers parent (conj 
path id)) ())
       interested (select schema/customer
                          (with schema/category
                            (where {:id id})))]
      (concat interested upstream)))))

Can anyone re-express that in a more idiomatic form?

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- 
You received this message because you are subscribed to the Google Groups 
"Clojure" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to clojure+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to