Fork me on GitHub
#beginners
<
2017-07-27
>
rorysmith08:07:14

hello! looking for a shove in the right direction. I am trying to do this codewars kata: Collatz Conjecture https://www.codewars.com/kata/collatz-conjecture-3n-plus-1 here is my code so far:

(ns clojure.collatz)

(defn hotpo [n]
  (loop [m n, count 0]
    (when (> m 1)
      (recur 
        (cond
           (even? m) (int (/ m 2))
           :else (int (+ (* m 3) 1)))
        (+ count 1)))))
the value of count is what i want to return. how can i do this?

noisesmith08:07:41

change the when into if, and put count as the third form in the if

noisesmith08:07:58

you return from a loop by having a form that doesn't end in a recursion

viveke10:07:17

Hi, Someone share the code to use FlatList example ?

rorysmith14:07:45

@noisesmith sorry i don't follow, what do you mean by: > put count as the third form in the if ?

dpsutton14:07:40

(when false :hi) will return nil. but (if (> m 1) (recur for collatz) count) will return the count if the collatz procedure has finally reduced the number to 1

dpsutton14:07:11

the when form returns nil when the predicate fails. But your predicate is "do i need to reduce some more". if not, you need to return the count

dpsutton14:07:20

so use an if expression rather than a when

rorysmith14:07:42

so now my function looks like this:

(defn hotpo [n]
  (loop [m n, val 0]
    (if (> m 1)
      (recur 
        (cond
           (even? m) (int (/ m 2))
           :else (int (+ (* m 3) 1)))
        (+ val 1))
      val)))
does this look unreadable? it looks hard to read for me. how can i make it neater?

ajpierce14:07:31

Doesn't look that bad to me. I'm not sure you need the cond if you only have two options though. Maybe another if?

(if (even? m)
  (int (/ m 2))
  (int (+ (* m 3) 1))

rorysmith14:07:39

nice, thanks

sundarj14:07:56

a lot of readability is familiarity - since you're new to clojure, it's bound to feel a little strange to begin with

rorysmith14:07:35

in previous exercises i have made my code more readable by using thread first and last

ajpierce14:07:57

Yeah i love threading macros :thumbsup:

sundarj14:07:29

there's also as->, cond->, and some->

sundarj14:07:36

(and the ->> versions)

rorysmith14:07:46

nice, i'll take a look at those

donaldball14:07:05

A thing you might want to consider is separating the iteration fn from the loop

donaldball14:07:32

e.g. (defn hotpo-iter [n] (if (even? n) (/ n 2) (inc (* n 3))))

donaldball14:07:03

Then you could use the core iterate fn and normal seq fns to identify the fixpoint

donaldball14:07:51

(take-while (partial not= 1) (iterate hopto-iter 23))

dpsutton14:07:36

and if you can prove if that is an infinite sequence or not in general you can get a million bucks

noisesmith16:07:02

another trick here is you can use (int (if p? foo bar)) to replace (if p? (int foo) (int bar))

rorysmith17:07:14

using all your help my function now looks like this

(defn hotpo [n]

  (defn hotpo-iter [n]
    (int (if (even? n) (/ n 2) (+ (* n 3) 1))))

  (loop [m n, val 0]
    (if (<= m 1)
      val
      (recur (hotpo-iter m) (+ val 1)))))
it's much nicer now imo

noisesmith18:07:22

don’t put defn inside defn

noisesmith18:07:40

defn is only able to create namespace level bindings, for local bindings use fn inside let

manutter5115:07:46

@rorysmith I like to put the short option first, which in this case means flipping the sense of the boolean, so something like

manutter5115:07:40

So if we’re done, we return val immediately. Otherwise, the rest of the code is all talking about the “else” clause, so there’s less confusion about where the “if” ends and the “else” begins.

timgilbert15:07:29

Hey, I have kind of a dumb question, think I'm missing something obvious. Can anyone tell me what's wrong with this?

=> (let [m {:n {}}] (update m :n merge-with + {:a 1}))
IllegalArgumentException contains? not supported on type: clojure.core$_PLUS_  clojure.lang.RT.contains (RT.java:829)

rauh15:07:16

@timgilbert What you wrote: (update m :n #(merge-with % + {:a 1})) what you want: (update m :n #(merge-with + % {:a 1}))

retrazil17:07:12

I am doing an exercise where I have to create a function which takes in a sequence and returns a sequence of all possible rotations.

retrazil17:07:19

The code works as expected but I find it messy. Is there any way to improve this ?

retrazil17:07:55

output is something like this

retrazil17:07:07

(rotations [1 2 3]) ;=> ((1 2 3) (2 3 1) (3 1 2))

retrazil17:07:54

Is there anyway I can club that code into a single function ? I couldn't come up with a base condition for single function so I used count.

noisesmith17:07:29

you can make it one function with multiple arities

noisesmith17:07:48

also fyi you can replace '() with () - it’s unambiguous so the reader accepts it

noisesmith17:07:04

of course '(()) still needs quoting

noisesmith17:07:05

(defn rotations ([s] (rotations s (count s))) ([s count] ...)) is the general form of the multiple arity version

retrazil17:07:25

ah, I didn't know I could use ( ) without quoting. That's interesting!

retrazil17:07:08

@noisesmith I am not familiar with the concept of arities yet. I will look that up. Thanks 🙂 . I am going by the course I linked. It hasn't introduced arities yet.

noisesmith17:07:37

it’s the same code you have already, both functions have the same name, they are distinct by argument count

noisesmith17:07:46

and they are in the same defn body

retrazil17:07:20

I have no idea what arities are. I will look that up first. Thank you @noisesmith

retrazil17:07:02

Is it common to write recursive functions the way I wrote it in case I don't have a natural base condition or just use arities instead ?

noisesmith17:07:43

@retrazil “arity” is a fancy word for “number of arguments”

noisesmith17:07:07

@retrazil the common way is to use distinct arities, but the logic looks the same

retrazil18:07:16

Is this what you mean @noisesmith ?

retrazil18:07:28

I think I got you 🙂

noisesmith18:07:40

if that runs, you got it right

retrazil18:07:50

yes it runs lol

retrazil18:07:10

this is interesting

rorysmith21:07:29

@noisesmith how can i use a function local to the function i am in? i tried using let but it seems you can't do the same thing as with defn. for example, can't i just put the first-letter and last-letter functions inside the generate-band-name function to avoid having to pass noun into them?

(defn first-letter [noun] (subs noun 0 1))

(defn last-letter [noun]
  (def length (count noun))  
  (subs noun 
    (dec length)
    length))
    
(defn generate-band-name [noun]
  (if (= (first-letter noun) (last-letter noun))
    (clojure.string/capitalize (str noun (subs noun 1)))
    (str "The " (clojure.string/capitalize noun))))

noisesmith21:07:41

yes, you can put them in there in a let

noisesmith21:07:46

(defn generate-band-name [noun]
  (let [first-letter (fn first-letter [] ...)
        last-letter (fn last-letter [] ...)]
    ...)

noisesmith21:07:23

also don’t use def inside defn, that’s the primary purpose of let

noisesmith21:07:37

and like defn, def only creates namespace level bindings, it doesn’t create locals

rorysmith21:07:59

it was the syntax that tripped me up

noisesmith21:07:21

the name inside fn is optional, but makes stack traces more readable

rorysmith21:07:32

ok, i was just about to ask about that 🙂

jeremys21:07:21

@rorysmith @noisesmith Hey you can also use letfn

(defn foo [& args]
  (letfn [(bar [x] ...)
          (baz 
            ([] ...)
            ([x y]))]
    (->> args
         bar
         (baz 1))))

noisesmith21:07:33

yes, that’s true

noisesmith21:07:01

but let is more flexible since you will usually end up wanting to bind local data too

noisesmith21:07:14

letfn is great for when two functions each need to call the other though

noisesmith21:07:34

eg. for trampoline

noisesmith21:07:51

(probably no longer in #beginners territory on that note)

jeremys21:07:38

@rorysmith but as @noisesmith said don’t you def or defn insisde of themselves these forms are side effects (they modify the namespace) you are better of with letand letfn

rorysmith21:07:54

got it :thumbsup:

jeremys22:07:53

@noisesmith I didn’t know letfn lets you use the function as if they were declared before hand. Nice.

seancorfield22:07:02

@rorysmith Just FYI, you can call first and last directly on a string, so (first noun) and (last noun) will work. Note: last operates in linear time!

rorysmith22:07:09

thanks, i missed that!

seancorfield22:07:08

I believe that (get noun (dec (count noun))) would be the constant time equivalent to (last noun) -- just in case you care about performance enough (for long band names!).

seancorfield22:07:28

(took me a while to trace through the source there but String implements CharSequence so (count noun) is (.length noun) and (get noun n) is (.charAt noun n) under the hood)

moogey23:07:28

@seancorfield is there a place that can tell you the time complexity of some things oin clojure? or just knowing the source code?