简体   繁体   中英

Divisor function in clojure

I'm new to clojure and I want to create a function which returns a vector of all divisors of a certain Number.

For instance:
[1 2 3] for 6 as input

(defn div [x seq]
  (let [r  (range 1 (+ (/ x 2) 1)) ]   
    (dotimes [i (count (range 1 (+ (/ x 2) 1) ))]      
     (cond
      (= (mod x (nth r i )) 0)(print (conj  seq (nth r i ))) 
     ))))

This function returns the output in the following format:
[1][2][4][5][10][20][25][50] for 100 but I want to get the output in one vector. It seems that the seq variable is constantly overwritten with each loopstep. Could anyone explain this behaviour and provide me a workaround?

Thanks in advance and best regards

You can avoid looping using a somewhat more idiomatic solution:

(defn divisors 
  [n]
  (filter (comp zero? (partial rem n)) (range 1 n)))

I think your approach is not correct, you can take a look to:

  • the loop function to resolve this kind of problems (i mean the cases where you need iterate and yield value),
  • iterate function
  • recursive functions (a function that calls itself)

And this code (using ' for ' function ) can easily resolve your specification

(let [n 100]
  (for [x (range 1 n)
       :when (zero? (rem n x))]
   x))
=>(1 2 4 5 10 20 25 50)

Your fundamental problem is that you are attempting to take an imperative approach, but Clojure collections are immutable. Also, I think that dotimes will always return nil , and print returns nil after printing its parameter(s).

There is a better way, but let's first see how we can use atoms to obtain an imperative solution:

(defn div [x seq]
  (let [r  (range 1 (+ (/ x 2) 1)) ]   
    (dotimes [i (count (range 1 (+ (/ x 2) 1) ))]      
      (cond
       ;; "Append" the ith element of r to seq
       ;; (Technically, we are replacing the value stored in seq
       ;; with a new list -- the result of conj-ing (nth r i)
       ;; to the current value stored in seq)
       (= (mod x (nth r i )) 0) (swap! seq conj (nth r i )))))  ;; <= don't print
  seq) ;; <== seq is what we're interested in, so we return it here.
       ;;     Otherwise, we would return the result of dotimes,
       ;;     which is nil

Note that we have eliminated the print and expect seq to be an atom (which we update using swap! ). We can now call div as follows:

user> (deref (div 6 (atom [])))
[1 2 3]

We could improve this by moving seq from the parameter list to a let inside the function, then dereferencing it when we return. But it would be better to avoid mutation in the first place. As tangrammer indicates in his answer, this is easily accomplished using for :

(defn div [x]
  (let [r (range 1 (+ (/ x 2) 1))]
    ;; Loop over r, taking only elements that are divisors of x
    (for [i r
          :when (= (mod x i) 0)]
      i))) ;; <= for accumulates the result of this expression (here simply i) into a sequence

user> (div 6)
(1 2 3)

In production code, you would probably inline r in this case, but I wanted to retain your original structure as much as possible. We could also use zero? in the :when clause. But all we're really doing in the for loop is a simple filter, so we could take Guillermo's approach and use filter instead.

div takes an integer x and a sequence seq as arguments:

  • it first declares r the range 1..(x/2) ;

  • it then iterates for i from 0 to (count r) , ie from 0 to (x/2) - 1 :

    • for each i , it prints the result of (conj seq (nth ri)) , ie the result of (conj seq (+ i 1)) .

      Clojure uses immutable data structures which means that (conj seq d) returns a new sequence containing all of seq 's elements plus d ; in particular, if seq is the empty vector [] , it returns [d] .

End result: (div n []) prints [d] if d divides x , for each d from 1 to (x/2) .

Your code fails because you try to write Clojure code in an imperative way ; it is not standard (nor recommended), but possible by using mutable references eg atoms (see @NathanDavis 's answer ) or transient (mutable) data structures:

(defn div [n]
  (let [coll (transient [])]
    (dotimes [i (quot n 2)]
      (let [d (inc i)]
        (when (zero? (rem n d))
          (conj! coll d))))
    (persistent! coll)))

An idiomatic solution would use for (as @tangrammer suggested ) ; it is not a loop structure (like in Java) but a macro that returns a lazy sequence:

(defn div [n]
  (for [:let [from 1                  ;; :let bindings are great for
              upto (inc (quot n 2))   ;; readability
              divides? #(zero?
                         (rem % %2))]

        d (range from upto)           ;; This binding form translates to
                                      ;; "each d from 1 to x/2".
                                      ;; You can have several binding forms
                                      ;; in a single for, to iterate over
                                      ;; several variables.

        :when (divides? n d)]         ;; Selects d only if it divides n.
                                      ;; You can also define termination
                                      ;; conditions with :while.

    d))                               ;; Each valid d is appended to a growing
                                      ;; lazy sequence i.e. a sequence that is
                                      ;; constructed the first time it is
                                      ;; traversed.

A for form with no body to speak of feels a bit abusive (though pleasing to the eye, IMHO) ; an alternative would be the filter displayed in @GuillermoWinkler 's answer :

(defn divisors 
  [n]
  (->> (range 1 (inc (quot n 2)))     ;; the threading macros ->, ->> and .. are
       (filter #(zero? (rem n %)))))  ;; great to write sequence-manipulation
                                      ;; code as a sequence of step, though
                                      ;; maybe overkill in the present case

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM