Fork me on GitHub
#beginners
<
2018-11-11
>
sean02:11:22

How would I write a function to find the sum of "hours" where "capex" is true?

jaihindhreddy-duplicate09:11:46

You can use reduce as demonstrated by didibus, but in my head what you want is to take the values of d, filter them by the key "capex" take the key "hours" from each and sum them up. This, in Clojure, encodes to

(->> d
		vals
		(filter #(true? (get % "capex")))
		(map #(get % "hours"))
		(apply +))
If your keys were keywords like so:
(def d {
    "project A" {:hours 7, :capex true},
    "project B" {:hours 3, :capex false},
    "project C" {:hours 6, :capex true}
   })
you could have used the keywords as functions themselves
(->> d
		vals
		(filter :capex)
		(map :hours)
		(apply +))

sean04:11:20

🔥

😂 4
Eric Ervin01:11:18

Looks like a question I was asked during a whiteboard exercise. Big thing I took away from it is that the threading macro makes whiteboard programming easier.

sean02:11:15

been trying to get this for a minute now 😩

didibus02:11:54

Anytime you need to loop over a collection and get a reduced value out of it. Like a sum, average, etc. You're looking for reduce

sean02:11:28

good lord you are a saint!

sean02:11:46

Thanks!!! I need to up my clojure fu for sure

sean02:11:28

Could you recommend any learning materials?

didibus02:11:34

Going through the examples at: http://www.4clojure.com/problems is a good way to get better at processing data with Clojure. If you struggle too much on one of the problem, you can look at the solutions of others.

didibus02:11:13

As a general quick intro guide to learn the language, I always recommend: https://aphyr.com/posts/301-clojure-from-the-ground-up-welcome

didibus02:11:04

And you'll be surprised how much you learn by reading over every section in the official reference: https://clojure.org/reference/reader

didibus02:11:12

Which doesn't take that long to go over.

sean02:11:34

awesome!!! Filling up my tabs now! Thanks!!

bmcferren05:11:54

can you guys please help me understand why (into nil [56]) results in (56) and (into (into nil [56]) [89]) results in (89 56) but (into (89) [56]) results in ClassCastException java.lang.Long cannot be cast to clojure.lang.IFn user/eval1168 (form-init1780124488703515866.clj:236) thank you in advance for help you can share

didibus06:11:40

@mcferren Yes, its because (89) is not how you define a literal list. You must type it like so: '(89).

Vincent Cantin08:11:45

@mcferren There is also (list 89) which is possible.

Vincent Cantin08:11:36

(89) is evaluated as a function call where the function is 89 - numbers are not coercible to a function (i.e. something which implements the java IFn interface), so you get an error.

bmcferren17:11:48

@U8MJBRSR5 and (into nil [56]) returns a literal list? Thanks Vincent

bmcferren18:11:16

I thought it has to do with "casting" a vector into a list but i was actually trying to "cast" a vector into a function call

didibus18:11:17

Yes, (into nil [56]) returns a list. But not a literal list.

didibus18:11:30

Read my replies in the main chat room after your question if you want to understand the details.

didibus18:11:53

You need to understand the textual representation vs the in memory one.

didibus18:11:13

And the compile process phases.

didibus18:11:32

The short answer is (into nil [56]) returns a list data structure. When typing out Clojure code lists literals must be quoted '(89) or (quote 89). But in EDN they don't have to be.

didibus18:11:54

That's because Clojure code is read and evaluated (unless quoted). And EDN is only read, it is not evaluated.

didibus18:11:24

Where literal means textual representation. And data structure means internal binary representation.

bmcferren19:11:22

thanks @U0K064KQV My main intent was to understand how default value was handled when calling a reduce function that mutates upon this key during each iteration

didibus19:11:48

Haha, ya. You got down a bit of a rabbit whole here. Since your issue had nothing to do with that really.

didibus06:11:51

Notice the quote '

didibus06:11:54

If you type it without the quote, the reader reads it as an S-expression, and Clojure will try to call the symbol 89 as if it were a function. But 89 is not a function (IFn), it is a Long. Thus you see the exception.

didibus06:11:26

Its a bit confusing, because when you print a list, it prints it without a quote

didibus06:11:05

I'll try to explain. There are two phases in evaluating code.

didibus06:11:43

First, the text that you typed (your code), goes through a "read" phase. Basically, the text is parsed into a data-structure. In that phase, (89) becomes a List, and [89] would become a vector. But, the second phase will take the read data-structure and evaluate it as Clojure code.

didibus06:11:13

And in this evaluation process, all List are treated like syntax for function execution.

didibus06:11:32

So "(89)" is read into (89) which is then evaluated such that 89 is called as if it were a function with no arguments. That's when you see the exception.

didibus06:11:48

Now, you can tell Clojure not to proceed to the second phase, the evaluation phase.

didibus06:11:15

That's done using the quote special form. Which ' is a syntax sugar for. Thus "'(89)" becomes (quote (89)) which is evaluated into (89). The quote tells Clojure, don't evaluate what comes next, just return it as it was read.

didibus06:11:55

And this is the underlying principle for homoiconicity. Clojure code is actually a data-structure. When you evaluate Clojure code, you just perform a walk on the data-structure, and based on the type of the elements and their position, you perform various actions.

didibus06:11:03

And this data-structure has a literal text representation. The reader is used to parse your text into that data-structure.

didibus06:11:46

Now there's actually a third phase. The macro-expansion phase. That phase happens after the text was read into a data-structure, but before the data-structure gets evaluated. In that phase, you are given a chance to modify the data-structure that was read, before it gets evaluated. That is what the macros do.

didibus06:11:03

But, this is probably a rabbit whole of info given the question you asked. So don't worry if it doesn't all make sense to you quite yet. It will one day. 😛

Karol Wójcik14:11:13

First time I got a problem with Java interop 😛

(ns some-ns.core
  (:import
   [java.util UUID Locale]
   [com.devskiller.jfairy Fairy]))

(def ^Fairy fairy (Fairy/create (Locale/getDefault)))

(.person fairy)
I’m trying to use this one https://github.com/Devskiller/jfairy The error is IllegalArgumentException No matching field found: person for class com.devskiller.jfairy.Fairy clojure.lang.Reflector.getInstanceField (Reflector.java:271) Which is quite funny because I can see in source code that the property exists in Fairy class. Can someone please help me?

Toby Clemson14:11:44

@kwcharllie379 It looks like the person method takes a varargs argument. I think this means you'll need to invoke it with an empty array to achieve the same as the docs from clojure. This stackoverflow post is for a similar problem: https://stackoverflow.com/questions/48138406/calling-java-function-with-optional-parameters-from-clojure

Karol Wójcik14:11:57

@toby924 But how can I get the inner class of PersonProperties?

Toby Clemson14:11:25

PersonProperties.PersonProperty would become PersonProperties$PersonProperty. More examples here: https://stackoverflow.com/questions/7140843/how-to-access-static-inner-java-class-via-clojure-interop

Toby Clemson14:11:57

This is to do with how Java creates inner classes at compile time rather than a Clojure specific thing

Karol Wójcik14:11:33

(println (PersonProperties$PersonProperty))

Karol Wójcik14:11:45

Throws an error CompilerException java.lang.RuntimeException: Unable to resolve symbol: PersonProperties$PersonProperty in this context, compiling:(/Users/karol/Workspace/clojure/zbd-faker/src/zbd_faker/faker.clj:13:10)

Karol Wójcik14:11:16

Thank you very much Toby 🙂

Toby Clemson14:11:44

Missing import?

Toby Clemson14:11:46

No problem 🙂

athomasoriginal18:11:17

I have a data structure like this:

(def data
    [[:id [[1 374] [2 375]]]
     [:name [["str-1" "str-2"] ["str-3" "str-4"]]]])
I would like to turn it into this:
[   [1 374 "str-1" "str-2"]  [2 375 "str-3" "str-4"]   ]
data can have any number of key + coll tuples in it. Most of my solutions end up being overly verbose so I wanted to get others takes on this.

noisesmith18:11:16

@tkjone apply map concat - never mind, that's not quite right

noisesmith18:11:34

user=> (apply map concat (map second data))
((1 374 "str-1" "str-2") (2 375 "str-3" "str-4"))

mfikes18:11:27

A small tweak on the above if you need vectors:

(apply mapv into (map second data))

noisesmith18:11:13

and if you can guarantee the input are always vectors

mfikes18:11:40

Yeah, otherwise be prepared for some backwards stuff

Ahmed Hassan19:11:52

Why do we have to use apply for first map?

mfikes19:11:21

Because you get a collection of collections that you want map to work on...

mfikes19:11:40

Compare with this, which is what happens in the end

(map concat
  [[1 374] [2 375]]
  [["str-1" "str-2"] ["str-3" "str-4"]])

❤️ 4
Ahmed Hassan19:11:18

So, concat would be mapped onto both collections. Right.

athomasoriginal19:11:41

Much cleaner than what I had. Thanks for the input!

Lennart Buit20:11:34

Hmm, I expected (vec ...) to be implemented in terms as (into [] ...) or the other way around, but thats not the case. Are there situations where I would want to prefer one over the other?

mfikes20:11:33

Prefer into if the source is not effectively immutable. For example, trouble:

cljs.user=> (def x #js [1 2 3])
#'cljs.user/x
cljs.user=> (def y (vec x))
#'cljs.user/y
cljs.user=> (aset x 1 :no)
:no
cljs.user=> y
[1 :no 3]

Lennart Buit20:11:07

That is only a concern on the non JVM based implementation then?

mfikes20:11:21

It can cause a problem on the JVM as well:

user=> (def x (into-array [1 2 3]))
#'user/x
user=> (def y (vec x))
#'user/y
user=> (aset x 1 17)
17
user=> y
[1 17 3]

mfikes20:11:40

But, in general, prefer vec if you want fast coercion.

Lennart Buit20:11:17

right, so vec is better if I can assume that the to-be-transformed collection is immutable

mfikes20:11:27

This will soon be true on both Clojure and ClojureScript, for example

(let [v [1 2 3]] (identical? v (vec v)))

mfikes20:11:07

Yeah, I would like to say vec is always better, unless you are giving it data that can be changed underneath it

Lennart Buit20:11:35

Cool! I saw that kibit was suggesting it, and I discussed with a coworker

mfikes20:11:49

Ahh, right. Kibit is smart, evidently!

mfikes20:11:02

That's cool!

Lennart Buit20:11:02

unless you give it macro’s 😉

Lennart Buit20:11:40

It suggests reducing (reduce #(and %1 %2) true coll) to (reduce and true coll), but thats wrong

Lennart Buit20:11:53

But it gives solid tips otherwise, its great when you are starting out (like I am)

mfikes20:11:44

Hah! Kibit’s suggestion looks well-intentioned there.

mfikes20:11:32

It must have a simple pattern that triggers that :)

Lennart Buit21:11:07

Yeah, it is, it just fails to realise that and and or are macros instead of functions

andy.fingerhut22:11:32

You could file an issue with some code that demonstrates the problem on the Kibit project on Github, in case someone figures out a way to improve on that behavior. Looks like Kibit is still being updated.

andy.fingerhut22:11:37

Tools like Kibit and Eastwood are I expect fundamentally going to give wrong messages in some cases, but certainly nice if those can be reduced.

Lennart Buit22:11:40

I think there already is a tracking issue: https://github.com/jonase/kibit/issues/142

Lennart Buit22:11:17

Otherwise I obviously would, its the easiest way to contribute right 🙂.

bmcferren23:11:15

in general, how much more expensive if fooAA than fooBB in my example? Thank you in advance for your insight.

didibus23:11:14

Gotta benchmark it to know for sure.

didibus23:11:43

I wouldn't worry about it, unless you're trying to do some kind of massive data transform.

👍 4
bmcferren23:11:35

I have come across this often so I am trying to get to the bottom of what is the correct general practice. For me it seems fooBB is quicker because it keeps the same object in tact but fooAA has been more legible is certain situations. I just don't know how expensive it may be to add a new container obj with a references to the contents of the original obj (which I think is what is happening to whats returned from fooAA)

bmcferren23:11:00

even if it is a massive data transform, isn't this a simple matter of creating small "pointer references" ?

didibus23:11:39

I think, but I'm not sure. The difference is that update uses the same persistent data structure, and modifies it. Where as in fooAA, you create a new one. Don't know how that really reflects on performance

didibus23:11:46

It seems using reduce-kv might be best in terms of performance and using something built in.

didibus23:11:35

Also, if you look at the code for update, it is: (assoc m k (f (get m k)))

didibus23:11:08

@mcferren If this was too complicated. Here's what I would recommend you do. Add a function in your code like this:

(defn map-vals
  "Map f over every value of m.
   Returns a map with the same keys as m, where each of its values is now the result of applying f to them one by one.
   f is a function of one arg, which will be called which each value of m, and should return the new value."
  [f m]
  (reduce-kv (fn [m k v]
               (assoc m k (f v)))
             {} m))

didibus23:11:48

@mcferren Then you can use it as such.

didibus23:11:25

@mcferren And if you're looking to update specific keys only, I would use assoc or update. But if you are looking to update all keys, I would use map-vals.

didibus23:11:36

@mcferren And if you want to update specific keys only, and also get a map with only those keys that you updated, you can combine map-vals with select-keys like so:

user>(map-vals inc (select-keys dab [:bbb :ccc]))
=> {:bbb 1, :ccc 1}

didibus23:11:40

@mcferren And finally 😝 , if you're going to be updating a lot of nested data-structures, I would use the Specter library: https://github.com/nathanmarz/specter