group-by replacement when an item has many groups?

94 views
Skip to first unread message

Colin Yates

unread,
Jul 8, 2013, 3:25:54 PM7/8/13
to clo...@googlegroups.com
Hi,

I have a sequence of items and want to group them into categories, the value of which is a function of the item.  This sounds exactly what group-by is after.

The kicker is that the function could return multiple values.  Imagine each item was a date range and I wanted to group them by the number of days in that date range.  

For example, (group-by #(range 1 (inc %)) [1 2 3] => {(1) [1] (1 2) [2] (1 2 3) [3]}.  I want {1 [1] 2 [1 2] 3 [1 2 3]}.

Any ideas?

Thanks!

Col

Jim - FooBar();

unread,
Jul 8, 2013, 3:35:06 PM7/8/13
to clo...@googlegroups.com
you can use group-by as you showed and then reduce-kv over them map
replacing each key with '(count key)'...I don't see a way of doing this
in one-pass using group-by alone...

Jim
> --
> --
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clo...@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+u...@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+u...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>

Ben Wolfson

unread,
Jul 8, 2013, 3:46:33 PM7/8/13
to clo...@googlegroups.com
You could do something like this, which is just a generalization of group-by to the multiple value case (except that group-by actually uses transients):

user> (defn groups-by [f coll]
        (reduce (fn [acc x]
                    (let [ks (f x)]
                      (reduce (fn [acc' k] (update-in acc' [k] #(conj (or % []) x))) acc ks)))
                {} coll))
#'user/groups-by
user> (groups-by #(range 1 (inc %)) [1 2 3])
{3 [3], 2 [2 3], 1 [1 2 3]}

(This isn't the return value you say you want, but it seems to be the right return value, unless I've misunderstood the problem: 1, 2, and 3 all include 1, but only 3 includes 3.)

You can define regular group-by in terms of this function as (defn group-by [f coll] (groups-by (comp vector f) coll)).



--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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+u...@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+u...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 



--
Ben Wolfson
"Human kind has used its intelligence to vary the flavour of drinks, which may be sweet, aromatic, fermented or spirit-based. ... Family and social life also offer numerous other occasions to consume drinks for pleasure." [Larousse, "Drink" entry]

Colin Yates

unread,
Jul 8, 2013, 3:47:59 PM7/8/13
to clo...@googlegroups.com
Hi Jim,

I don't think that would give the result I need.  Let me give a more realistic example:

(defn every-day [[start :start end: end]] ...) ; returns [date1 date2 date3]
(def m [{:id 1 :start 1/1/2010 :end 3/1/2010} {:id 2 :start 3/1/2010 :end 3/1/2010}]

(group-by every-day m) would give something like:
{[1/1/2010 2/1/2010 3/1/2010] [{:id1 ...}] 
 [3/1/2010] [{:id 2...}]}

I now want to transform that result to the following:

{1/1/2010 [{:id 1...}]
 3/1/2010 [{:id 1 ...} {:id 3}]}

I am thinking I probably need to process each key/value in the original map (m) and then use assoc-in to build up the map but that feels pretty imperative and given Clojure's magic and transforming shapes I figure there must be a better way...




On 8 July 2013 20:35, Jim - FooBar(); <jimpi...@gmail.com> wrote:
you can use group-by as you showed and then reduce-kv over them map replacing each key with '(count key)'...I don't see a way of doing this in one-pass using group-by alone...

Jim



On 08/07/13 20:25, Colin Yates wrote:
Hi,

I have a sequence of items and want to group them into categories, the value of which is a function of the item.  This sounds exactly what group-by is after.

The kicker is that the function could return multiple values.  Imagine each item was a date range and I wanted to group them by the number of days in that date range.

For example, (group-by #(range 1 (inc %)) [1 2 3] => {(1) [1] (1 2) [2] (1 2 3) [3]}.  I want {1 [1] 2 [1 2] 3 [1 2 3]}.

Any ideas?

Thanks!

Col
--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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

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+unsubscribe@googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.


--
--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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

For more options, visit this group at
http://groups.google.com/group/clojure?hl=en
--- You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/AkKuHVNPIq4/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+unsubscribe@googlegroups.com.

Colin Yates

unread,
Jul 8, 2013, 3:52:08 PM7/8/13
to clo...@googlegroups.com
Perfect, and bonus points for spotting my stupid "not slept in days" muppetry.  The results I want are as you infer: {1 [1 2 3] 2 [1 2] 3 [3]}.

Now all I have to do is understand your code - time for more coffee I think (not implying your code is difficult, rather the difficultly is a function of my ignorance).



You received this message because you are subscribed to a topic in the Google Groups "Clojure" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/clojure/AkKuHVNPIq4/unsubscribe.
To unsubscribe from this group and all its topics, send an email to clojure+u...@googlegroups.com.

Michał Marczyk

unread,
Jul 8, 2013, 4:08:25 PM7/8/13
to clo...@googlegroups.com
Another implementation:

(defn groups-by [f coll]
(apply merge-with into
(map (fn [k] (zipmap (f k) (repeat [k])))
coll)))

Or with ->>:

(defn groups-by [f coll]
(->> coll
(map (fn [k] (zipmap (f k) (repeat [k]))))
(apply merge-with into)))

Could use #(zipmap (f %) (repeat [%])) in map, of course.

In any case,

(groups-by #(range 1 (inc %)) [1 2 3])

returns

{3 [3], 2 [2 3], 1 [1 2 3]}

Cheers,
Michał

Colin Yates

unread,
Jul 8, 2013, 4:13:55 PM7/8/13
to clo...@googlegroups.com
Wow - I need to get some sleep:  {1 [1 2 3] 2 [2 3] 3 [3]}

Yoshinori Kohyama

unread,
Jul 8, 2013, 10:00:14 PM7/8/13
to clo...@googlegroups.com
Hi Colin,

One more solution, with example data in the process commented

(let [f #(range 1 (inc %))
      coll '(1 2 3)]
  (->>
    (for [x coll    ; x = 1, 2, 3
          y (f x)]  ; y = 1       (where x = 1),
                    ;     1, 2    (where x = 2),
                    ;     1, 2, 3 (where x = 3)
      [y x])        ; [y, x] = [1, 1], [1, 2], [2, 2], [1, 3], [2, 3], [3, 3]
    (reduce         ; for example where
      (fn [a [y x]] ;   a = {1 [1 2 3], 2 [2]}, [y x] = [2 3]
                    ; => (a y []) = [2]
                    ;    (conj ... x) = [2 3]
        (assoc a y (conj (a y []) x)))
      {})))         ;    (assoc a y ...) = {1 [1 2 3], 2 [2 3]}

Hope this helps.

Y.Kohyama

Colin Yates

unread,
Jul 9, 2013, 3:49:18 AM7/9/13
to clo...@googlegroups.com
Thanks Y.Kohyama - it does.


--
Reply all
Reply to author
Forward
0 new messages