2
votes

I'm trying to calculate factors of a number, given the prime numbers, so for instance:

REPL=> (find-factors 1176 #{2 3 7})
#{7 24 4 21 1176 294 56 168 196 6 28 588 3 12 2 14 392 98 147 42 8 49 84}

I'm using reduce and declaring inner step functions, but it seems like the code could be cleaner. I appreciate any suggestions on how to make the code more idiomatic.

(defn mul-all [find-for prod-multiples factors]
  (let [step (fn [[prod-multiples factors] mult]
           (let [step2 (fn [[prod-mults factors] mult2]
               (let [multiple (* mult mult2)]
                   (if (zero? (rem find-for multiple)) 
                     [(into prod-mults [mult mult2 multiple]) (into factors [multiple])] 
                     [(reduce disj prod-mults [mult mult2]) factors])))]   
             (reduce step2 [prod-multiples factors] prod-multiples)))]
(reduce step [prod-multiples factors] factors)))

(defn find-factors [find-for prime-factors]
  (loop [result (mul-all find-for prime-factors prime-factors)]
    (if (zero? (count (first result)))
     (second result) 
     (recur (mul-all find-for (first result) (second result))))))

-------EDIT---------

@A. Webb - Thanks for the code. I'm used to imperative programming, so I rewrote your example in Groovy to understand what it's doing. The findFactorsInject() method uses Groovy's inject() method which is equivalent to reduce in Clojure.

static List recurFn(Set a, Integer n, Integer p ) {
    println "a $a n $n p $p"
    if(n%p == 0) {
        a.addAll(a.collect{Integer it ->it*p})
        recurFn(a, (Integer)(n/p), p);
    } else {
        return [a, n]
    }
}

static findFactors(Integer findFor, Set<Integer> primes) {
    List result = []
    for(Integer prime in primes) {
        if(result.size() == 0) {
            result = recurFn([1] as Set, findFor, prime)
        } else {
            result = recurFn((Set)result[0], (Integer)result[1], prime)
        }
    }
    return result
}

static findFactorsInject(Integer findFor, Set<Integer> primes) {
    primes.inject ([[1] as Set, findFor], 
        { List accumulate, Integer prime ->  
            recurFn((Set)accumulate[0], (Integer)accumulate[1], prime)  
        })
}
static main(args) {
    println findFactors(1176, ( (Set) [2, 3, 7 ] as Set))
    println findFactorsInject(1176, ( (Set) [2, 3, 7 ] as Set))
}
3

3 Answers

4
votes

Consider factoring your code into 3 functions to compose

  1. Find each factor and its multiplicity
  2. Generate all subsets (use clojure.math.combinatorics if desired)
  3. Map apply multiply through the subsets

But you could do this all together

(defn factors [n primes] 
  (-> (reduce 
        (fn [[a n] p] 
          (if (zero? (rem n p)) 
            (recur [(concat a (map #(* p %) a)) (quot n p)] p)
            [a n])) 
        [[1] n] primes)
      first set))

(factors 1176 [2 3 5 7])
;=> #{7 1 24 4 21 1176 294 56 168 196 6 28 588 3 12 2 14 392 98 147 42 8 49 84}
2
votes

... or, leaning heavily on the sequence functions:

(defn factors [n primes]
  (set
    (reduce
      (fn [xs ys] (for [x xs, y ys] (* x y)))
      [1]
      (map
        (fn [p] (take-while #(zero? (mod n %)) (iterate #(* p %) 1)))
        primes))))

For example,

(factors 1176 [2 3 5 7])
; #{1 2 98 3 4 196 6 294 7 8 168 392 42 12 588 14 49 147 84 21 24 56 1176 28}

This may be easier to read if we name the functions and use the ->> threading macro:

(defn factors [n primes]
  (letfn [(products [xs ys] (for [x xs, y ys] (* x y)))
          (factor-powers [p] (take-while #(zero? (mod n %)) (iterate #(* p %) 1)))]
    (->> primes
         (map factor-powers)
         (reduce products [1])
         set)))

... which produces the same results as before:

(factors 1176 [2 3 5 7])
; #{1 2 98 3 4 196 6 294 7 8 168 392 42 12 588 14 49 147 84 21 24 56 1176 28}
1
votes

Not sure i fully understand your question but if

(find-factors 1176 #{147 }) 
;user=> (147 294 588 1176)

then this works

(defn find-factors [n c]
      (->> n
           ((comp rest range inc) )
           (remove #(ratio? (/ n %))) 
           (#(for [x c 
                   y % 
                   :when (not (ratio? (/ y x)))]
               y ))))