【问题标题】:Clojure: Find even numbers in a vectorClojure:在向量中查找偶数
【发布时间】:2016-11-29 18:47:27
【问题描述】:

我来自 Java 背景,正在尝试学习 Clojure。由于最好的学习方法是实际编写一些代码,因此我举了一个非常简单的例子,在向量中查找偶数。下面是我写的一段代码:

`

(defn even-vector-2 [input]
  (def output [])
  (loop [x input]
    (if (not= (count x) 0)
      (do
        (if (= (mod (first x) 2) 0)
          (do
            (def output (conj output (first x)))))
        (recur (rest x)))))
  output)

`

此代码有效,但我必须使用全局符号才能使其工作,这很蹩脚。我必须使用全局符号的原因是因为我想在每次在向量中找到偶数时更改符号的状态。 let 不允许我更改符号的值。有没有一种方法可以在不使用全局符号/原子的情况下实现这一点。

【问题讨论】:

  • 你为什么要改变输出?只需将其设为循环的另一个参数即可。永远不要在函数中使用def。除非您在做语言级别的工作,否则从来没有必要。
  • 无论如何,这可以像(filter #(= (rem % 2) 0)) [1 2 3 4]) 一样解决(对不起,如果我放错括号了。Clojure 很难在手机上编写)。
  • 等我回到家,我会写一个答案来详细说明。

标签: clojure


【解决方案1】:

惯用的解决方案很简单:

(filter even? [1 2 3])
; -> (2)

出于教育目的,使用循环/递归实现

(defn filter-even [v]
  (loop [r []
         [x & xs :as v] v]
    (if (seq v) ;; if current v is not empty
      (if (even? x)
        (recur (conj r x) xs) ;; bind r to r with x, bind v to rest
        (recur r xs)) ;; leave r as is
      r))) ;; terminate by not calling recur, return r

【讨论】:

  • 将内部if 推入单个recur 更简单:(recur (if (even? x) (conj r x) r) xs)
  • @Thumbnail True,然后我会在 r 上使用 cond->。我选择以显式风格编写示例,以使 Clojure 新手更容易访问它。
【解决方案2】:

您的代码的主要问题是您使用def 污染了命名空间。你永远不应该在函数中真正使用def。如果您绝对需要可变性,请使用 atom 或类似对象。

现在,回答你的问题。如果您想以“硬方式”执行此操作,只需将 output 设为循环的一部分:

(defn even-vector-3 [input]
  (loop [[n & rest-input] input ; Deconstruct the head from the tail
         output []] ; Output is just looped with the input
    (if n ; n will be nil if the list is empty
        (recur rest-input
               (if (= (mod n 2) 0)
                 (conj output n)
                 output)) ; Adding nothing since the number is odd
        output)))

但很少需要显式循环。这是折叠的典型情况:您想要累积一个列表,该列表是另一个列表的可变长度版本。这是一个快速版本:

(defn even-vector-4 [input]
  (reduce ; Reducing the input into another list
    (fn [acc n]
      (if (= (rem n 2) 0)
        (conj acc n)
        acc))
    [] ; This is the initial accumulator.
    input))

实际上,您只是在过滤一个列表。只需使用核心的filter

(filter #(= (rem % 2) 0) [1 2 3 4])

注意,filter 是懒惰的。

【讨论】:

    【解决方案3】:

    试试

    #(filterv even? %)
    

    如果你想返回一个向量或

    #(filter even? %)
    

    如果你想要一个惰性序列。

    如果您想将此与更多转换相结合,您可能需要使用转换器:

    (filter even?)
    

    【讨论】:

      【解决方案4】:

      如果您想使用loop/recur 编写它,我会这样做:

      (defn keep-even 
        "Accepts a vector of numbers, returning a vector of the even ones."
        [input]
        (loop [result []
               unused input]
          (if (empty? unused)
            result
            (let [curr-value    (first unused)
                  next-result   (if (is-even? curr-value)
                                  (conj result curr-value)
                                  result)
                  next-unused   (rest unused) ]
              (recur next-result next-unused)))))
      

      这与内置 filter 函数的结果相同。

      【讨论】:

        【解决方案5】:

        看看过滤器,甚至?和向量 查看http://cljs.info/cheatsheet/

        (defn even-vector-2 [input](vec(filter even? input)))
        

        【讨论】:

          【解决方案6】:

          如果您想要惰性解决方案,filter 是您的朋友。

          这是一个非懒惰的简单解决方案(如果您始终应用相同的功能而无需精确工作,则可以避免loop/recur):

          (defn keep-even-numbers
            [coll]
            (reduce
              (fn [agg nb]
                (if (zero? (rem nb 2)) (conj agg nb) agg))
              [] coll))
          

          如果您喜欢“有趣”的可变性,这里有一个临时可变集合的解决方案:

          (defn mkeep-even-numbers
            [coll]
              (persistent!
                (reduce
                  (fn [agg nb]
                    (if (zero? (rem nb 2)) (conj! agg nb) agg))
                  (transient []) coll)))
          

          ...稍微快一点!

          如果将奇/偶定义扩展到负整数,mod 会比 rem 更好

          您也可以将 [] 替换为您想要的集合,这里是一个向量!

          【讨论】:

            【解决方案7】:

            在 Clojure 中,您通常不需要使用 loop/recur 编写低级循环。这是一个快速演示。

            (ns tst.clj.core
              (:require
                [tupelo.core :as t]  ))
            (t/refer-tupelo)
            
            (defn is-even? 
              "Returns true if x is even, otherwise false."
              [x]
              (zero? (mod x 2)))
            
            ; quick sanity checks
            (spyx (is-even? 2))
            (spyx (is-even? 3))
            
            (defn keep-even 
              "Accepts a vector of numbers, returning a vector of the even ones."
              [input]
              (into []    ; forces result into vector, eagerly
                (filter is-even? input)))
            
            ; demonstrate on [0 1 2...9]
            (spyx (keep-even (range 10)))
            

            结果:

            (is-even? 2) => true
            (is-even? 3) => false
            (keep-even (range 10)) => [0 2 4 6 8]
            

            您的project.clj 需要以下条件才能使spyx 工作:

             :dependencies [
                [tupelo "0.9.11"]
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2017-03-25
              • 1970-01-01
              • 2020-05-13
              • 1970-01-01
              相关资源
              最近更新 更多