Dear Meikel and Ken,

Thank you very much for your corrections and suggestions! I admit it
took a while for me to understand the more sophisticated parts of your
code, but on the way I learned quite a lot about the correct usage of
conj, assoc, update-in, and fnil!

Best regards,

Stefan

On Jan 17, 3:21 pm, Ken Wesson <kwess...@gmail.com> wrote:
> 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

Reply via email to