2013-11-27 41 views
4

我是clojure的新手,我想创建一个函数,它返回某个数的所有除数的向量。clojure中的除数函数

例如:
[1 2 3] 6作为输入

(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))) 
    )))) 

该函数返回以下列格式的输出:
[1] [2] [4] [5] [ 10] [20] [25] [50]为100,但我想要在一个向量中获得输出。看起来,seq变量不断被每个循环覆盖。任何人都可以解释这种行为并为我提供解决方法吗?

提前感谢和问候

+1

这并不是说每个步骤都会覆盖“seq”。这是你永远不会保存'(conj seq(nth r i))'的返回值。你认为'conj'应该改变'seq'“到位”,但这不是Clojure数据结构的工作原理。当你使用“修改”数据结构的函数时,该函数将返回“新”数据结构,而“旧”数据结构将保持不变。 –

回答

4

我觉得你的做法是不正确的,你可以去看看:

  • loop功能来解决这类问题(我指的情况下,你需要遍历和屈服值),
  • iterate功能
  • 递归功能(即调用自身的函数)

而这种代码(使用“for”功能),可以轻松解决您的规格

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

误引欧比旺:“使用功能,卢克!”。我对Clojure的最低经验告诉我,循环播放一个集合是一个经典的案例,为所有正确的理由做出错误的事情,还有更好的方法。 (这也是重新打击最后的战争 - 在这种情况下,用功能性语言编写程序代码)。 –

+1

谢谢@Bob_Jarvis!并回答欧比旺!“我以为我在用功能!” ,...为什么“循环”功能存在?其实我不喜欢使用“循环”,但你如何维护/更新集合遍历的状态(除了减少[每次只能使用一个集合])?...和我认为的'for'函数被设计用于列表理解(不完全是循环函数) – tangrammer

+0

@tangrammer:那么,'reduce'通常是答案。如果您需要同时减少两个集合(例如,出于某种原因,您的密钥在一个集合中,而您的值在另一个集合中),则可以使用'(map vector collection1 collection2 ...)'和'((减少(fn [备忘录[ab]] ...)压缩)'。说你永远不应该使用'loop'是不对的(因为Clojure没有tail-call消除,这对于一些传统的递归算法是必须的),但是它总是让我说,“嗯,是我想着这个正确的方式?“ – Chuck

9

你能避免使用一个较为惯用的解决方案循环:

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

轻微重构 '(过滤器#(零?(rem%))(范围1 n))' – kittyminky

1

你的根本问题是,你是试图采取一种必要的方法,但Clojure集合是不可改变的。此外,我认为dotimes将始终返回nilprint返回nil后打印其参数。

有一个更好的办法,但让我们先来看看我们如何能够利用atoms获得必要的解决方案:

(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 

请注意,我们已经消除了print并期望seq是一个原子(我们更新使用swap!)。现在,我们可以称之为div如下:

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

我们可以通过从参数列表移动seqlet里面的函数,然后取消引用它,当我们回到改善这个。但首先避免突变会更好。作为tangrammer表示他的回答,这是使用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) 

在生产代码中,你可能会内嵌在这种情况下r,但我想保留原始结构,尽可能地。我们也可以在:when条款中使用zero?。但是我们在for循环中所做的只是一个简单的过滤器,所以我们可以使用Guillermo's approach并使用filter来代替。

0

div取整数x和作为参数的序列seq

  • 它首先声明r范围1..(x/2);

  • 它然后遍历用于i0(count r),即从0(x/2) - 1

    • 每个i,它打印的(conj seq (nth r i))的结果,即(conj seq (+ i 1))结果。

      Clojure使用不可变的数据结构,这意味着(conj seq d)返回一个新的序列,包含所有seq的元素加上d;特别是,如果seq是空向量[],则返回[d]

最终结果:(div n [])打印[d]如果d划分x,对于每个d1(x/2)

您的代码失败,因为您尝试以命令的方式编写Clojure代码;它不是标准的(也不推荐),但可以通过使用可变引用来实现。 atoms(见@NathanDavis 's answer)或transient(可变)的数据结构:

(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))) 

一种惯用的解决方案将使用for(如@tangrammer suggested);它不是一个环结构(如Java中),但它返回一个懒惰的序列宏:

(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. 

一个for形式没有身体说话的感觉有点虐待(尽管顺眼,恕我直言);另一种方法是在@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 
+0

最后的版本有两个小的错别字: - 短的尾随paren - 范围应该是1..n/2不是0..n/2 – rmp

+0

@rmp谢谢,我做了所需的更改。 – omiel