Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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!

like image 818
Christopher Shroba Avatar asked Jan 31 '26 13:01

Christopher Shroba


2 Answers

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.

like image 188
leetwinski Avatar answered Feb 03 '26 07:02

leetwinski


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
like image 35
Leonid Beschastny Avatar answered Feb 03 '26 06:02

Leonid Beschastny



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!