Fork me on GitHub
#beginners
<
2020-06-18
>
Stuart00:06:12

If i have a list of maps, e.g.

({:b10 1, :b2 "1"}
 {:b10 3, :b2 "11"}
 {:b10 5, :b2 "101"}
 {:b10 7, :b2 "111"}
 {:b10 9, :b2 "1001"}
 {:b10 33, :b2 "100001"}
 {:b10 99, :b2 "1100011"})
Is their a way to use reduce to add up all the values of :b10 without having to do something like (map #(:b10 %) my-list) first ?

noisesmith00:06:04

sure, with a reducing function like (fn [acc m] (+ acc (:b10 m))) and the optional init arg (0 of course)

👍 3
noisesmith00:06:56

of course (apply + (map ...)) with your original version does the same thing

noisesmith00:06:52

also :b10 and #(:b10 %) do the same thing for the one arg case (in fact for all non-macro f, f and #(f %) are the same for the one arg case)

Stuart00:06:29

(time (->> (range 1 1000000)
           (map (fn [x] {:b10 x :b2 (Integer/toString x 2)}))
           (filter (fn [x] (and (is-palindrom (:b2 x)) (is-palindrom (:b10 x)))))
           (reduce (fn [acc m] (+ acc (:b10 m))) 0)))
"Elapsed time: 328.579422 msecs"
=> 872187
And
(defn find-base2-base10-palindromes-below-1-million [^Integer sum, ^Integer cur-base-10]
  (if (= cur-base-10 1000000)
    sum
    (if (and (is-palindrom cur-base-10) (is-palindrom (Integer/toString cur-base-10 2)))
      (recur (+ sum cur-base-10) (inc cur-base-10))
      (recur sum (inc cur-base-10)))))
(time (find-base2-base10-palindromes-below-1-million 0 1))
"Elapsed time: 136.254228 msecs"
=> 872187
(defn is-palindrom [x]
  (= (str x) (str/reverse (str x))))
Should I just expect the second one to be faster? Is their something I can read / learn to be able to write code in the first version, but have it be as quitck as the second version, or is that just not achievable?

seancorfield00:06:46

What about transduce here? Even tho' you'd have (map :b10) it would do it all in one non-lazy operation.

Stuart00:06:40

Cause I like the syntax in the first version better, but when I do that its more than twice as slow. @seancorfield transducers can make the first version faster?

noisesmith00:06:50

the likely performance difference is in the allocation / navigation of the lazy-seqs

noisesmith00:06:59

which transducers do avoid

Stuart00:06:13

Cool, Ihaven't got to transducers yet so I will look into them.

noisesmith00:06:36

your first example should be pretty easy to turn into transduce (transduce (comp (map ...) (filter ...) (map :b10)) + (range 1 100000))

Stuart00:06:18

In the first example then, is it creating intermediate collections between map and filter?

noisesmith00:06:44

and unlike transduce, it actually creates a collection from range (transduce is clever enough to know it doesn't have to)

noisesmith00:06:38

also to be pedantic, x isn't base 10, it's a number, clojure's default print of it is base 10, but numbers don't have bases, strings do

noisesmith00:06:57

(unless one counts the in-memory representation, which is very rarely anything but base 2)

adam02:06:40

Just curious if I am reinventing the wheel here or it could be simplified:

(defn stitch
  "Concatenates a list of strings without leaving extra spaces."
  [& args]
  (let [vals (remove empty? args)]
    (when (not-empty vals)
      (reduce #(str  %1 " " %2) vals))))
These are passing:
(deftest stitch-test
  (testing "Concatenating without extra spaces."
    (is (= (stitch (when false "one")) nil))
    (is (= (stitch "has-icons-left") "has-icons-left"))
    (is (= (stitch "has-icons-left" "has-icons-right") "has-icons-left has-icons-right"))))

seancorfield02:06:34

@somedude314 That feels like (str/join " " (keep seq args)) but I'd have to test that in the REPL to be sure.

seancorfield02:06:53

Ah, not quite. (filter seq args)

seancorfield02:06:18

user=> (clojure.string/join " " (filter seq ["Hello" "" "World" "!" "" "and" "more"]))
"Hello World ! and more"
user=>

adam02:06:49

Thanks but how do I pass use my & args to this? I am not passing a vector

seancorfield02:06:32

@somedude314 args would just replace the vector. It's "just" a sequence.

seancorfield02:06:30

user=> (defn foo [& args] (->> args (filter seq) (clojure.string/join " ") (not-empty)))
#'user/foo
user=> (foo "this" "and" "that")
"this and that"
user=> (foo "what" "" "about" "" "this")
"what about this"
user=> (foo "" "" "")
nil
user=>

seancorfield02:06:41

Is that what you're looking for?

adam03:06:17

Yes, last one passed all tests.

seancorfield02:06:20

Although empty sequences and nil both return an empty string (which seems more intuitive to me):

user=> (clojure.string/join " " (filter seq []))
""
user=> (clojure.string/join " " (filter seq nil))
""
user=>

adam02:06:15

It actually must be nil because otherwise Hiccup displays the property (empty CSS class)

walterl02:06:40

Wrap it in not-empty?

seancorfield02:06:59

(->> args (filter seq) (clojure.string/join " ") (not-empty)) yup, that would work.

adam03:06:26

Last one worked. Thanks. Going to read what seq is 🙂

raspasov03:06:57

Here’s a solution via transducers:

(transduce
  (map identity)
  (fn
    ;init (called at the start)
    ([] nil)
    ;reducing...
    ([accum item] (str accum " " item))
    ;completing (called at the end)
    ([ret]
     (if (string? ret) 
       (clojure.string/trim ret) 
       ret)))
  ["qwe" "asd"])

adam03:06:43

@U050KSS8M it fails with ["qwe" "" nil "asd"]

raspasov03:06:51

ah, you wanted to have nil as the input…

raspasov03:06:21

(transduce
  (comp
    (filter string?)
    (remove empty?))
  (fn
    ;init (called at the start)
    ([] nil)
    ;reducing...
    ([accum item] (str accum " " item))
    ;completing (called at the end)
    ([ret]
     (if (string? ret) 
       (clojure.string/trim ret) 
       ret)))
  ["qwe" "" nil "asd"])

raspasov03:06:28

=> “qwe asd”

raspasov03:06:52

It returns nil if the input is an empty collection or nil

adam03:06:50

Awesome, thanks. I will try to understand it. Nice to have a concrete example of transducers for something I am doing.

👍 3
raspasov03:06:53

It’s a bit more verbose, but in my opinion it gives you a bit more flexibility once you need to do a more involved transformation (+ better performance for large collections)

hiredman04:06:37

Reduce using str is bad, lots of allocation, lots of copying (not my bugaboo, but @ghadi will come for you)

👌 3
Patrick Farwick04:06:52

If I have a vector in a text file, is it possible to read that file without converting the vector into one long string? or an easy way to strip the string away and just have the vector?

hiredman04:06:35

(with-open [i (io/input-stream ...)] (read i))

Patrick Farwick04:06:51

I do get

class java.io.BufferedInputStream cannot be cast to class
   java.io.PushbackReader 
when trying this.

Patrick Farwick05:06:46

Hmm, I got it with (read (.PushbackReader. (io/reader "test-resources/test.txt")))

EmmanuelOga07:06:57

some times I spend way too much time code-golfing some code... anyway, I was wondering if someone can think of a more succinct way of doing this:

(map-indexed (fn [idx itm] (or itm ([:a :b :c] idx))) [1 nil 3]) => [1 :b 3]

EmmanuelOga07:06:07

that is, I want to provide default to values on a vector that may be nil

raspasov07:06:39

(replace {nil :i-am-nil} [1 2 3 nil 4])

raspasov07:06:44

=> [1 2 3 :i-am-nil 4]

EmmanuelOga07:06:14

hmmm problem is that I have a different default per index

EmmanuelOga07:06:28

another example:

EmmanuelOga07:06:32

(keep-indexed #(or %2 ([:a :b :c] %1)) [1 nil 3])

EmmanuelOga07:06:42

(yeah I'm not putting that on my code hahah)

EmmanuelOga07:06:32

actually the keep-indexed is not any better than map-indexed in that example

EmmanuelOga07:06:32

so it makes a bit more sense here's the actual function I'm working with

EmmanuelOga07:06:36

(defn path-to-topic
  "Converts a request path to a topic."
  [path]
  (let [[base name ext] (fu/get-base-name-and-ext path)]
    [(str (or base "/") (or name "index")) (or ext "html")]))

EmmanuelOga07:06:57

so I have 3 defaults: one for the basename, one for the name, and one for the extension

raspasov07:06:07

Yea… if your requirement is to replace based on index, your original example might not be a bad option; you do need map-indexed to keep track of the index

raspasov07:06:16

It encapsulates pretty well what you trying to achieve with the map-indexed and the (or …)

raspasov07:06:57

Do you control the data? Aka the vector with the nils? One solution to the problem is to avoid having the problem in the first place

raspasov07:06:20

As in, if you can manage to put the correct value instead of the nil to start with, you wouldn’t have to do this transformation

EmmanuelOga07:06:26

hmmmm that's a good point

clj 3
Ernesto Garcia10:06:28

(map #(if (some? %1) %1 %2) [1 nil false] [:a :b :c])

Ernesto Garcia10:06:34

As this doesn't work for false values: (map #(or %1 %2) [1 nil false] [:a :b :c])

Endre Bakken Stovner12:06:19

I have a triply nested for-loop:

(for [[xc xp] xs]
    (for [infile infiles]
      (for [outfile outfiles]
        [[xc infile] [xp outfile]]))))
What is the preferred way to remove the nesting from the results? I do not want to use flatten, as then [[xc infile] [xp outfiles]] would also be flattened. But using 3X apply concat feels wrong :/.

Daniel Stephens12:06:03

for can take many collections so you could just have

(for [[xc xp] xs
      infile infiles
      outfile outfiles]
  [[xc infile] [xp outfile]])
which may not need to do any un-nesting?

👍 3
Endre Bakken Stovner12:06:54

Thanks, I knew there had to be a better way 🙂

😊 3
noisesmith14:06:22

btw - pedantic side note, for is not a loop, it's a generator / list comprehension. loop is our loop

Endre Bakken Stovner16:06:05

Thanks for reminding me of the distinction 🙂

noisesmith16:06:21

I'd skip it in any channel other than #beginners tbh :D

Jim Strieter13:06:20

Why doesn't the REPL recognize the names of defrecords but understands the names of everything else in an imported module? At the prompt, I type (use 'lib-name.file-name :reload-all). Then the REPL tab-completes other names in the file, but not this one: (defrecord SomeRecord [^String name1 ^String name2])

delaguardo13:06:31

repl doesn’t provide autocompletion functionality out of the box. it probably comes with your editor

delaguardo13:06:14

which one you are using?

Jim Strieter13:06:02

I'm using the REPL in a Mac terminal. Editor is Vim, but I'm not using that thing that puts the REPL at the bottom of the Vim window.

Jim Strieter13:06:12

Autocompletion isn't really the point though. (Although I can see how I might have given that impression.) The problem is that even when I type (SomeRecord. v1 v2) I get an error that Clojure does not recognize that name.

noisesmith14:06:24

(Class. arg) is not a function, it's a class constructor invocation (a shorthand for calling new). use does not import classes. Use ->Class instead (which is auto-generated by defrecord / deftype) - it's a function

noisesmith14:06:38

also, use is generally frowned upon these days

noisesmith14:06:49

(it's handy sometimes in a repl of course)

noisesmith14:06:17

more generally, though deftype and defrecord create classes and implement methods, they also provide function versions of all the many class operations, and the functions should be preferred over interop (though both functions and interop will access the same functionality)

Jim Strieter11:06:21

Thank you guys!

Binny Zupnick15:06:03

How do I test contains? for multiple keys? I want a simpler (and more dynamic) way to write this: (and (contains? mapped :username) (contains? mapped :password)))) But I want the 'name' and 'password' to be a given vector like ['name' 'password'] And I can't wrap my head around how to reduce this in a dynamic way

noisesmith15:06:11

I typically use = and select-keys for this I misread (= keys-wanted (set (keys (select-keys m keys-wanted))))

Jan K15:06:17

(every? #(contains? mapped %) [:username :password])

noisesmith15:06:55

and if username and password can't be nil, you can just use (every? mapped [:username :password])

3
Binny Zupnick15:06:32

wo okay. Thanks for the answers! Now I gotta sit with the Clojure docs and decypher all of them 😜 I've only been working in Clojure for ~2 weeks so getting used to it. Thanks!

ghadi15:06:30

a key is understanding that maps {} are functions too

ghadi15:06:09

(map {:a :b :c :d :e :f} [:a :e]) returns (:b :f)

Chase15:06:52

is there a std function to assoc in a key value pair only if the key is not already present?

noisesmith15:06:46

not in core, but it's a one-liner

noisesmith15:06:30

(fn [m k v] (if (contains? m k) m (assoc m k v))

noisesmith15:06:22

or, if you also want to replace nils: (fn [m k v] (update m k (fnil identity v)))

Chase15:06:20

Sounds good. I was heading that route but was curious. I'm constantly browsing the cheatsheet but always seem to stumble on a new, super useful core fn

noisesmith15:06:35

(ins)user=> (defn impose [m k v] (update m k (fnil identity v)))
#'user/impose
(ins)user=> (impose {:a 0} :a 1)
{:a 0}
(ins)user=> (impose {:a nil} :a 1)
{:a 1}
(cmd)user=> (impose {} :a 1)
{:a 1}

noisesmith15:06:32

though that function is small enough, I'd usually just call update with fnil identity instead of using the function in practice

noisesmith15:06:46

small enough, and to me unsurprising enough

Chase15:06:09

I haven't use fnil before. I'll have to wrap my head around that

noisesmith15:06:50

the tl;dr version is it returns the function with an added nil-check / default

noisesmith15:06:50

(ins)user=> ((fnil + 1 2) 2 2)
4
(ins)user=> ((fnil + 1 2) nil 2)
3
(ins)user=> ((fnil + 1 2) 2 nil)
4
(ins)user=> ((fnil + 1 2) nil nil)
3

dpsutton15:06:02

there's also merge for this

💯 3
noisesmith15:06:33

oh! facepalm - that's actually the common / simple solution and I somehow forgot it momentarily

noisesmith15:06:13

(merge {:a 1} m) - does exactly what was asked for, for any number of k/v pairs

Chase16:06:33

lol, that's great. Thanks folks.

Chase16:06:19

And there is no way I didn't read about that behavior with merge before either so that's on me too.

noisesmith16:06:12

this happens a lot - clojure emulates math (set theory in particular) in ways that mean you find simple (once you learn them) but not always intuitive solutions to things

Jim Strieter16:06:45

When you use reify to associate an object with a protocol, how do you then call a method on that object? For instance, if I have (defprotocol Whatever (method1 [ ] ...)

Jim Strieter16:06:53

(method 2 [ ] ...))

Jim Strieter16:06:42

Then define a function: (defn f [x] (reify Whatever (method1 [ ] ...) ;; method body here (method2 [ ] ...)) And then create an instance of the protocol (and I'm probably butchering the proper way to say that) (def uut (f ... )) How do I then call method1 on uut?

noisesmith16:06:02

(method1 uut) - but remember that method1 belongs to the namespace that defined the protocol, not the namespace that defined uut

noisesmith16:06:42

protocol methods are first class and belong to the namespace where they are defined (like any other function you define into a var)

noisesmith16:06:59

it's valid to use interop syntax also, but that should be reserved for the cases where it's needed

pooya16:06:46

hi everyone, what does the hyphen mean here? #(= nil (%2 % -))

noisesmith16:06:26

- isn't a syntax, it's literally the function that subtracts

noisesmith16:06:00

the second arg must expect a function as its second argument

🤯 3
Endre Bakken Stovner16:06:51

It might look better/be more understandable in context

noisesmith16:06:24

right, or it's just bad code (using % and %2 in the same block make me think that it's just bad code)

👍 3
noisesmith16:06:46

bad as in poorly written, even if it works correctly

pooya16:06:55

This was someone's solution for a 4clojure problem: http://www.4clojure.com/problem/134

(true?  (__ :a {:a nil :b 2}))
(false? (__ :b {:a nil :b 2}))
(false? (__ :c {:a nil :b 2}))

noisesmith16:06:58

oh yeah, some people treat 4clojure as a code golf (win by having the lowest character count total)

pooya16:06:24

but I don't understand why the - operator should be there at the end?

pooya16:06:31

this makes more sense to me #(not (%2 % 1))

noisesmith16:06:54

the second arg to a map is treated as the default value to return - hmm, working this out

phronmophobic16:06:46

for code golf purposes, I think it would work with any non nil 1 character value

noisesmith16:06:19

yup, that would be it

noisesmith16:06:08

odd choice to use - instead of eg. 0

pooya16:06:48

Ok that makes sense now. Thanks!

noisesmith16:06:16

haha they could lose a character if they used nil?

noisesmith16:06:04

or two if they used not

noisesmith16:06:36

#(not(%2 % -)) though #(not(%2 % %)) is more aesthetically appealing somehow, and also works

phronmophobic16:06:02

it depends on if the function is supposed to specifically check for nil

phronmophobic16:06:15

or any falsey value

noisesmith16:06:40

the test cases were already shared

phronmophobic16:06:07

I haven’t done 4clojure, so I wasn’t sure if there was descriptive text with hidden tests

noisesmith16:06:28

aha - sometimes there are certain forbidden functions

noisesmith16:06:14

but the page was linked, and my #(not(%2 % %)) passes

noisesmith16:06:14

in fact, #(not(%2% %)) passes, but yuck!

noisesmith16:06:31

that's probably the shortest possible, and also evil

🙈 3
😈 3
noisesmith17:06:47

haha! even worse, tied for minimum char count

#(not(%2%1%))

😀 3
noisesmith16:06:21

I'm not saying it makes the function coherent, just saying how it's interpreted by the compiler

Chase17:06:04

I'm confused on how to switch to a different namespace in the repl. I have a project practice so lein repl throws me in practice.core=> but if I try to switch to practice.foo using (in-ns practice.foo) I get an unable to resolve symbol error.

seancorfield17:06:35

@chase-lambert You need to require a namespace before you use in-ns -- otherwise you won't get any Clojure symbols referred in.

noisesmith17:06:55

also you need to quote the arg to in-ns (in-ns 'practice.foo) - if practice.foo didn't exist, it wouldn't be an unresolved symbol error, it would just place you in a broken ns

Chase17:06:16

yup, it was the quote I was missing.

seancorfield17:06:51

(doto 'my.namespace (require) (in-ns)) is a safe way to enter a namespace -- @noisesmith I think I saw you recommend that recently?

noisesmith17:06:07

haha, it's my goto

ghadi17:06:40

I learned that one from @semperos way back in the day

noisesmith17:06:03

yeah, I picked it up from technomancy

ghadi17:06:03

the parens around require and in-ns aren't necessary

seancorfield17:06:49

Yeah, I know, but I've kind of gotten into the habit of using them around functions (but not around keywords) to make the intent clearer in my mind 🙂

Chase17:06:06

Awesome. Any gotchas I should know when using things like (read-line in the repl? I was going crazy using lein run to run through such things. If I change a function in practice.foo and save it, how do I make sure that change is reflected in my repl when calling that function again

noisesmith17:06:40

my version of the snippet above is actually (doto 'my.ns (require :reload) in-ns)

3
noisesmith17:06:53

often with a test invocation thrown in there too

mathpunk17:06:49

I'm getting tripped up by a lazy sequence. In my REPL, (map csv/output-for-import applications) gives me a bunch of strings, which is what I want. But I need those strings to be put into a file, and (spit "applications.csv" (map csv/output-for-import applications)) creates a file which reads solely "clojure.lang.LazySeq@95d817b⏎"

mathpunk17:06:05

I tried sticking a doall in there but not the right place, and it was just superstition

mathpunk17:06:22

i don't actually know what's happening

noisesmith17:06:41

you want to call pr-str on the map

noisesmith17:06:23

what is happening is calling str on a lazy-seq gives you a useless output, pr-str gives you the same representation you'd see in a repl

noisesmith17:06:55

(ins)user=> (str (map inc (range 10)))
"clojure.lang.LazySeq@c5d38b66"
(ins)user=> (pr-str (map inc (range 10)))
"(1 2 3 4 5 6 7 8 9 10)"

noisesmith17:06:28

general rule - if str gives you nonsense, check if pr-str is nicer (it often is, and you can extend it to new types too)

mathpunk17:06:35

HMM ok, thanks!

Joseph Hurtado18:06:47

Hey nice to join the Clojurians Slack again ;-)

12
Spaceman22:06:47

I'm dividing (/ 2158 3600) and printing the result. But I get 1079/1800 as the print and not the decimal

Spaceman22:06:50

Why would that be

noisesmith22:06:14

it's called a rational, it's used because it's accurate

noisesmith22:06:31

you can force a floating point result by using at least one floating point argument

noisesmith22:06:21

(cmd)user=> (-> 1.0 (+ 10000000000000001) (/ 10) (* 10) (- 10000000000000001))
0.0
(cmd)user=> (-> 1 (+ 10000000000000001) (/ 10) (* 10) (- 10000000000000001))
1N

Spaceman22:06:40

and how to round the floating number to x digits after the decimal?

noisesmith22:06:10

you want format (in clj) or goog.format (cljs)

noisesmith22:06:50

unless you mean decimal rounding of the number itself?

noisesmith22:06:42

the typical trick is #(/ (Math/floor (* % 100)) 100) - I don't think there's anything built in

👍 3
noisesmith22:06:55

you'd change 100 depending on how many digits you want

Spaceman22:06:02

how to use goog.format btw?

noisesmith22:06:47

last I checked cljs didn't have format, and used goog.format instead

noisesmith22:06:00

I thought you wanted to truncate the printed representation at first

Spaceman22:06:01

flooring sometimes leaves only one digit at the end, when it's floored to a .%d0

Spaceman22:06:12

I want two digits always

Spaceman22:06:21

so a combination of both is needed

noisesmith22:06:24

that's a printing question - 1.10 is 1.1

noisesmith22:06:08

so of course, that's my initial answer, format or goog.format

Spaceman22:06:24

I guessed (http://good.fo/format "%.2f" my-num) but that didn't work

Spaceman22:06:55

can you please share the documentation?

noisesmith22:06:37

I should have double checked

(cmd)cljs.user=> (require 'goog.string)
nil
(cmd)cljs.user=> (goog.string/format "%.2f" 1.111)
"1.11"

Spaceman23:06:39

how to comment-out or toggle all prn statements in a buffer in emacs?

noisesmith23:06:24

if you use tap> instead of prn you can do (add-tap prn) to start printing, and (remove-tap prn) to make it stop

noisesmith23:06:13

though tap> only takes a single arg, so you might need to wrap the args in [] or whatever

noisesmith23:06:58

you could use a global substitution to turn all (prn into #_(prn now that I think about it