Christopher Shroba
Christopher Shroba

Reputation: 7554

Is it okay to mutate data in a loop in clojure?

I'm super new to Clojure, and I'm going through Project Euler challenges to get a feel for the language. I'm on this one, and I want to solve it by using a map to map factors to number of max number of occurrences in all the numbers, so to do this in an imperative language, I would simply update the map in each iteration of the loop.

I'm tempted to do this same sort of thing in Clojure: create a map, and then update it (or rather retrieve it, add the new value, and save it in the old map's place, since data is immutable) for each number from 1-20, and then use the final map to compute the answer.

This feels wrong though, as if I'm not thinking functionally. Am I missing something, or is there some kind of mapping between how things would normally be done imperatively, and what functional constructs I could use to do the same sort of thing?

Thanks!

Upvotes: 3

Views: 374

Answers (3)

Thumbnail
Thumbnail

Reputation: 13473

A Clojure loop is just a let that doubles as a point of tail recursion: where, roughly speaking, the loop form evaluates to the recursive call as such, without doing anything to it. recur invokes tail recursion to the enclosing loop, and performs it by a goto instead of using the call stack.

Should you mutate loop data? The question doesn't arise. You can't! You can no more mutate loop bindings than let bindings. recur re-enters the loop with renewed bindings.

loop is idiomatic Clojure, and is the simplest way to solve many problems. However, as you can see from the other answers, the sequence library (map, filter, reduce ...) encapsulates many common data/control patterns, which often express a solution more clearly and concisely.

Upvotes: 1

Leonid Beschastny
Leonid Beschastny

Reputation: 51470

I'm totally agree with leetwinski - you should never mutate variables in Clojure (unless there is no other way to do the job).

The only thing I want to add to leetwinski's answer is a more elegant solution:

(defn multiple [numbers]
  (reduce #(let [n (/ %1 %2)]
            ; try to divide accumulator by the next number in input collection
            (if (ratio? n)
                ; multiply accumulator by resulting denominator
                (* %1 (denominator n))
                ; or leave it unchanged if it already evenly divisible
                %1))
          numbers))
(multiple (range 1N 11)) ; => 2520N
(multiple (range 1N 21)) ; => 232792560N

Upvotes: 3

leetwinski
leetwinski

Reputation: 17859

the first rule of the clojure club: you never mutate anything in the clojure club!

seriousely, do not mutate anything, unless it is inevitable (like keeping the global state of the application). To keep the loop state, you usually just pass it as one of the loop parameters. Speaking of your task, for example here is an example of factors function:

(defn factors [n]
  (loop [n n d 2 f []]
    (cond (== 1 n) f
          (zero? (rem n d)) (recur (/ n d) d (conj f d))
          :else (recur n (inc d) f))))

so, you just "accumulate" factors in f, and pass it to the next iteration.

and for the rest of the task you should use higher order functions:

(->> (range 2 20)
     (map (comp frequencies factors))
     (apply merge-with max)
     (reduce-kv #(apply * %1 (repeat %3 %2)) 1))

(map (comp frequencies factors)) creates the sequence of maps, where each map is a prime factor to power of this factor in the number, for each number in range:

({2 1} {3 1}           ; 2 3
 {2 2} {5 1}           ; 4 5
 {2 1, 3 1} {7 1}      ; 6 7
 {2 3} {3 2}           ; 8 9
 {2 1, 5 1} {11 1}     ; 10 11
 {2 2, 3 1} {13 1}     ; 12 13
 {2 1, 7 1} {3 1, 5 1} ; 14 15
 {2 4} {17 1}          ; 16 17
 {2 1, 3 2} {19 1})    ; 18 19

(apply merge-with max) merges these maps, using the maximum of two values if the keys are equal

{2 4, 3 2, 5 1, 7 1, 11 1, 13 1, 17 1, 19 1}

then you just multiply k ^ val

by the way, this task has a better solution which can be fulfilled with paper and pencil, or one line of code.

Upvotes: 3

Related Questions