On Mon, Jan 17, 2011 at 1:47 AM, Stefan Rohlfing <stefan.rohlf...@gmail.com> wrote: > Hi all, > > I am trying to implement the function 'group-by' from Clojure.Core > using my current knowledge of Clojure but cannot get pass a > java.lang.Exception. > > This is the code so far: > > (defn key? [key coll] > (some #{key} (keys coll))) > > (defn my-group-by [f coll] > (let [test (fn [m x] > (let [res (f x)] > (if (key? res m) > (conj (m res) x) > (assoc m res (vec x)))))] > (reduce test {} coll)))
First of all, your key? is only ever (supposed to be) called on maps, so you can dispense with it: (defn my-group-by [f coll] (let [test (fn [m x] (let [res (f x)] (if (contains? res m) (conj (m res) x) (assoc m res (vec x)))))] (reduce test {} coll))) Second, your already-in-the-map case returns a vector. It doesn't modify the vector in place; it makes a new one with one more item and returns that. Not the map it should return. The next iteration of your reduce, the vector gets passed to key? with some value. Your key? function calls keys on it, which results in an exception. I get: user=> (keys ['x 'y 'z]) java.lang.ClassCastException: clojure.lang.Symbol cannot be cast to java.util.Map$Entry You got: > ;; java.lang.Exception: Unable to convert: class java.lang.Integer to > Object[] (repl-1:2) I'm not sure why the difference -- different Clojure versions? -- but no matter, the fix is the same: (defn my-group-by [f coll] (let [test (fn [m x] (let [res (f x)] (if (contains? res m) (assoc m res (conj (m res) x)) (assoc m res (vec x)))))] (reduce test {} coll))) This actually returns an updated map and not just an updated vector. But it can be made nicer in a couple of ways. One uses the optional not-found option to get to dispense with the contains?: (defn my-group-by [f coll] (let [test (fn [m x] (let [res (f x)] (assoc m res (conj (m res []) x))))] (reduce test {} coll))) And the other uses update-in. Update-in means the key only needs to be specified once, which lets you dispense with the inner let: (defn my-group-by [f coll] (let [test (fn [m x] (update-in m [(f x)] #(if % (conj % x) [x])))] (reduce test {} coll))) This can be formatted a bit more nicely: (defn my-group-by [f coll] (reduce (fn [m x] (update-in m [(f x)] #(if % (conj % x) [x]))) {} coll)) This gets rid of the outer let as well. The latter two versions can't distinguish the map having a key with an associated value of nil and the map lacking that key; however, the map never associates a key with anything other than a vector, so this ends up not mattering. For reference (and I only peeked at this *after* writing all of the above), the implementation of group-by in clojure.core is: (defn group-by "Returns a map of the elements of coll keyed by the result of f on each element. The value at each key will be a vector of the corresponding elements, in the order they appeared in coll." {:added "1.2" :static true} [f coll] (persistent! (reduce (fn [ret x] (let [k (f x)] (assoc! ret k (conj (get ret k []) x)))) (transient {}) coll))) This is basically the same as the version I got adding the optional not-found argument to get instead of using update-in, except that it uses transients for the performance benefits (and has docstrings and metadata) and gets rid of the outer let in the same manner as the final update-in using version I wrote. -- 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