renanreismartins
renanreismartins

Reputation: 309

How to do a filter with varargs?

I have a map of users and their favorite bands:

(def data
  {
    :David {"Tribalistas" 3.0
       "Daft Punk" 5.0
       "Lorde" 4.0
       "Fall Out Boy" 1.0}

    :Matt {"Imagine Dragons" 3.0
      "Daft Punk" 4.0
      "Lorde" 4.0
      "Fall Out Boy" 1.0}

    :Ben {"Kacey Musgraves" 4.0
     "Imagine Dragons" 3.0
     "Lorde" 3.0
     "Fall Out Boy" 1.0}

} )

and I need to filter the results that have two keys in common, in this case, band1 and band2

(defn common-ratings [band1 band2 ratings]
  (filter #(and ((second %) band1) ((second %) band2)) ratings))


(common-ratings "Daft Punk" "Lorde" data) ; should return David and Matt lines

but now, I need to transform the bands in a varargs, I tried to use something like:

apply and...

So I can use the function like this:

(common-ratings "Daft Punk" "Lorde" "Another band" "Another Band2" data)

but It does not work.

Thanks in advance

Upvotes: 0

Views: 148

Answers (3)

Thumbnail
Thumbnail

Reputation: 13473

  • As has been said, and is a macro, so cannot be an argument to a function, apply or any other.
  • The closest standard function to and is every?.

Since you have an unknown number of bands, pass them as a collection:

(defn common-ratings [bands ratings]
  (filter #(every? (val %) bands) ratings))

... where I've replaced second with val to show that we're dealing with map entries.

For example,

(common-ratings ["Daft Punk" "Lorde"] data)
;([:David {"Tribalistas" 3.0, "Daft Punk" 5.0, "Lorde" 4.0, "Fall Out Boy" 1.0}] [:Matt {"Imagine Dragons" 3.0, "Daft Punk" 4.0, "Lorde" 4.0, "Fall Out Boy" 1.0}])

If you want to pass the bands as individual arguments, put them last to capture them as a rest argument:

(defn common-ratings [ratings & bands]
  ... )

... which you call like this:

(common-ratings data "Daft Punk" "Lorde")

... with the same effect as before.

Upvotes: 2

Jarlax
Jarlax

Reputation: 1576

One of the options is to wrap and macro into function:

(defn and-fn [x y] (and x y))

Then you can use reduce instead of apply to achieve the same result:

(reduce and-fn [true true true false]) ; => false

For convenience, last reduce can be wrapped into another function:

(defn and-multi [& args]
  (reduce #(and %1 %2) true args)) ; initial value is needed for invocation with no args

Now it will behave very similar to normal and macro:

(and-multi) ; => true
(and-multi false true) ; => false
(apply and-multi [true true true]) ; => true

It will even have same behaviour with the evaluation of args:

(and false (range)) ; => false (despite of second param being infinite sequence)
(and-multi false (range)) ; => false

Upvotes: 0

renanreismartins
renanreismartins

Reputation: 309

Thank you a lot guys, my final solution is pretty close to @Thumbnail solution:

(defn common-ratings [& bands] (filter #(every? (second %) bands) data))

Upvotes: 0

Related Questions