【问题标题】:Divisor function in clojureclojure 中的除数函数
【发布时间】:2013-12-13 10:09:16
【问题描述】:

我是 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 变量不断被每个循环步骤覆盖。谁能解释这种行为并为我提供解决方法?

提前致谢并致以最诚挚的问候

【问题讨论】:

  • seq 并不是每一步都被“覆盖”。就是你永远不会保存(conj seq (nth r i)) 的返回值。您认为conj 应该“就地”更改seq,但这不是Clojure 数据结构的工作方式。当您使用“修改”数据结构的函数时,该函数将返回“新”数据结构,而“旧”数据结构将保持不变。

标签: function clojure functional-programming


【解决方案1】:

您可以使用更惯用的解决方案来避免循环:

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

【讨论】:

  • 轻微重构(filter #(zero? (rem n %)) (range 1 n))
【解决方案2】:

我认为你的做法不正确,你可以看看:

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

【讨论】:

  • 错误地引用欧比旺的话:“使用函数,卢克!”。我对 Clojure 的最小经验告诉我,循环遍历集合是一个典型案例,即出于所有正确的原因做错事,并且有更好的方法。 (这也是重新战斗最后一场战争——在这种情况下,用函数式语言编写程序代码)。
  • 谢谢@Bob_Jarvis!并回复欧比旺!,“我以为我在使用函数!” ,以及...为什么“循环”功能存在?实际上,我不喜欢使用“循环”,但是如何在遍历集合时维护/更新状态(除了 reduce [每次只能使用一个集合])?...以及我认为的“for”函数专为列表理解而设计(不完全是循环函数)
  • @tangrammer:嗯,reduce 通常是答案。如果您需要同时减少两个集合(例如,由于某种原因,您的键在一个集合中,而您的值在另一个集合中),您可以将它们与(map vector collection1 collection2 ...) 压缩在一起,然后使用(reduce (fn [memo [a b]] ...) zipped)。说你永远不应该使用 loop 是不对的(因为 Clojure 没有尾调用消除,这对于一些传统的递归算法是必要的),但它是那些总是让我说,“嗯,我是以正确的方式思考这个问题?”
  • @Chuck:感谢您的 cmets!我目前对循环函数的防御与这个 4clojure 问题4clojure.com/problem/82 有关我正在尝试对动态数据树进行建模,其上层由每个分支的下层决定。在问题的上下文中:如果您在链(分支)中选择一个单词,则下一个可能的单词受到使用中的单词的限制。因此,要减少的集合是根据使用的运行时数据在运行时定义的。使用loop 函数,我可以拥有 2 个替代集合,这些集合会自行提供,直到数据限制...
【解决方案3】:

您的基本问题是您试图采用命令式方法,但 Clojure 集合是不可变的。另外,我认为dotimes 将始终返回nil,而print 在打印其参数后返回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]

我们可以通过将seq 从参数列表移动到函数内部的let 来改进这一点,然后在我们返回时取消引用它。但最好首先避免突变。正如 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 代替。

【讨论】:

    【解决方案4】:

    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]

    最终结果:如果d 除以x,则(div n []) 打印(div n []),每个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 (as @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
    

    【讨论】:

    • 上一个版本有两个小错别字: - 短一个尾随括号 - 范围应该是 1..n/2 而不是 0..n/2
    • @rmp 谢谢,我做了所要求的更改。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-09
    • 2011-07-16
    • 2013-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多