2

For the following data:

(def occurrence-data '(["John" "Artesyn" 1 31.0] ["Mike" "FlexPower" 2 31.0] ["John" "Eaton" 1 31.0]))

I would like to have a function:

(defn visit-numbers
  "Produce a map from coordinates to number of customer visits from occurrence records."
  [coordinates occurrences]
  (let [selector ??? ; a function that would be equivalent to (juxt #(nth % c1) #(nth % c2) ..), where c1, c2, ... are elements of coordinates 
        ]
    (group-by selector occurrences)
  )

For example, for coordinates = [1 3]

It should be

(group-by (juxt #(nth % 1) #(nth % 3)) occurrence-data)

I guess that it should be possible? I tried to use some list expression but has not figured out yet.

My experiment of following:

(def selector (list 'juxt '#(nth % 1) '#(nth % 3)))
(group-by selector occurrence-data)

Got error:

java.lang.ClassCastException: clojure.lang.PersistentList cannot be cast to clojure.lang.IFn
           core.clj:6600 clojure.core/group-by[fn]
       protocols.clj:143 clojure.core.protocols/fn
        protocols.clj:19 clojure.core.protocols/fn[fn]
        protocols.clj:31 clojure.core.protocols/seq-reduce
        protocols.clj:48 clojure.core.protocols/fn
        protocols.clj:13 clojure.core.protocols/fn[fn]
           core.clj:6289 clojure.core/reduce
           core.clj:6602 clojure.core/group-by

I have two problems to solve:

  1. How to make selector a function?
  2. How to dynamic construct such function based coordinates?

Thanks for your pointers, and help!

I also guess that using macro might also be possible to do it?

Or am I using too complicated method to achieve my goal?

3 Answers 3

4

Simply call juxt directly to create your function, and define selector to hold that function:

(def selector (juxt #(nth % 1) #(nth % 3)))

To make it dynamically, create a function-creating function:

(defn make-selector [& indexes] (apply juxt (map (fn[i] #(nth % i)) indexes)))

REPL example:

core> (def occurrence-data '(["John" "Artesyn" 1 31.0] ["Mike" "FlexPower" 2 31.0] ["John" "Eaton" 1 31.0]))
#'core/occurrence-data
core> (def selector (juxt #(nth % 1) #(nth % 3)))
#'core/selector
core> (group-by selector occurrence-data)
{["Artesyn" 31.0] [["John" "Artesyn" 1 31.0]], ["FlexPower" 31.0] [["Mike" "FlexPower" 2 31.0]], ["Eaton" 31.0] [["John" "Eaton" 1 31.0]]}
core> (group-by (make-selector 0 1 2) occurrence-data)
{["John" "Artesyn" 1] [["John" "Artesyn" 1 31.0]], ["Mike" "FlexPower" 2] [["Mike" "FlexPower" 2 31.0]], ["John" "Eaton" 1] [["John" "Eaton" 1 31.0]]}
Sign up to request clarification or add additional context in comments.

2 Comments

I think the OP was mixing up functions and macros. You don't need to quote function bodies.
Your answer misses the second part of the OP's question: how to dynamically create such a function depending on a set of coordinates. It's trivial to do this in Clojure using an anonymous function that closes over the coordinates.
2

This is almost index

(clojure.set/index occurrence-data [2 3])
;=>
;    {{3 31.0, 2 2} #{["Mike" "FlexPower" 2 31.0]},
;     {3 31.0, 2 1} #{["John" "Eaton" 1 31.0] ["John" "Artesyn" 1 31.0]}}

Where you can see, for example, that there are two records that share the same values at coordinates 2 and 3, those values being 1 and 31.0.

If you wanted to strip back out the indices and map to a count, then

(reduce-kv 
  (fn [a k v] (conj a {(vals k) (count v)})) 
  {} 
  (clojure.set/index occurrence-data [2 3]))
;=> {(31.0 1) 2, (31.0 2) 1}

1 Comment

Thanks for showing me the existing solution and elegant reduction!
1

Define

(defn group-by-indices [ns coll]
  (group-by #(mapv % ns) coll))

then, for example,

(group-by-indices [1] occurrence-data)
;{["Artesyn"] [["John" "Artesyn" 1 31.0]],
; ["FlexPower"] [["Mike" "FlexPower" 2 31.0]],
; ["Eaton"] [["John" "Eaton" 1 31.0]]}

and

(group-by-indices [2 3] occurrence-data)
;{[1 31.0] [["John" "Artesyn" 1 31.0] ["John" "Eaton" 1 31.0]],
; [2 31.0] [["Mike" "FlexPower" 2 31.0]]}

If you want to keep the selection map, use select-keys instead of mapv. Then we're getting close to A.Webb's use of clojure.set/index, which is, other things being equal, the method of choice.

1 Comment

Thanks! Amazingly elegant using mapv or map and group-by to achieve the goals.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.