【问题标题】:Clojure for loop not returning updated values of atomClojure for循环不返回原子的更新值
【发布时间】:2016-12-03 05:47:36
【问题描述】:

我正在尝试编写一个函数来计算给定字符串中元音和辅音的数量。返回值是一个带有两个键的映射,vowelsconsonants。每个相应键的值只是计数。

我目前能够开发的功能是

(defn count-vowels-consenants [s]
  (let [m (atom {"vowels" 0 "consenants" 0})
        v #{"a" "e" "i" "o" "u"}]
    (for [xs s]
      (if
          (contains? v (str xs))
            (swap! m update-in ["vowels"] inc)
            (swap! m update-in ["consenants"] inc)
       ))
    @m))

但是(count-vowels-consenants "sldkfjlskjwe") 返回{"vowels":0 "consenants": 0}

我做错了什么?

编辑:将我的输入从 str 更改为 s,因为 str 是 Clojure 中的一个函数。

【问题讨论】:

  • 是否需要更新原子?
  • @jmargolisvt 不,对原子没有特定要求。在我看来,这似乎是使用 Clojure 改变状态的自然方式,但我对替代方案完全持开放态度。
  • 虽然您已经收到了问题的答案,但我想强调一下您现在可能已经意识到的根本误解——clojure 中没有“for 循环”。 for 是一个 list comprehension 构造,用于创建一个列表,因此它不会“循环”(正如您所见,loop 的名称很恰当)。在循环上下文中使用单词 for 的命令式语言可能会让人感到困惑,但这不是 clojure 中的意思。

标签: clojure


【解决方案1】:

我认为for 很懒惰,所以在你尝试意识到它之前你不会真正做任何事情。我在for 循环中添加了一个first,该循环实现了该列表并导致您通过使用str 字符串覆盖str 函数而产生错误。理想情况下,您只需要在没有原子繁琐的情况下执行此操作。

(defn count-vowels-consonants [s]
  (let [v #{\a \e \i \o \u}
        vowels (filter v s)
        consonants (remove v s)]
    {:consonants (count consonants)
     :vowels (count vowels)}))

如果原子是您想要的,则使用doseq 而不是for,它将更新字符串中所有内容的原子。还要确保不要在函数绑定中使用 str 函数来覆盖它。

【讨论】:

  • 仅供参考,group-by 可能更好,因为它只会对字符串/序列进行一次迭代。
  • @Brandon H ahhhh,str 很好,这对我来说真的很草率。
  • 这绝对比我的解决方案更优雅。感谢您将我指向doseq
【解决方案2】:

如果这种副作用方案是不可避免的(出于教育原因,我想)只需将for 替换为doseq,它是for 的副作用急切等价物 (顺便说一句:您的初始代码中有一个错误:您使用str 作为输入参数名称,然后尝试将其用作函数。所以您正在从clojure.core 中隐藏def,只是尝试避免使用命名为核心函数的参数):

(defn count-vowels-consenants [input]
  (let [m (atom {"vowels" 0 "consenants" 0})
        v #{"a" "e" "i" "o" "u"}]
    (doseq [s input]
      (if (contains? v (str s))
        (swap! m update-in ["vowels"] inc)
        (swap! m update-in ["consenants"] inc)))
    @m))
#'user/count-vowels-consenants

user> (count-vowels-consenants "asdfg")
;; {"vowels" 1, "consenants" 4}

否则你可以这样做:

user> (reduce #(update %1
                       (if (#{\a \e \i \o \u} %2) 
                         "vowels" "consonants")
                       (fnil inc 0))
              {} "qwertyui")
;;{"consonants" 5, "vowels" 3}

user> (frequencies (map #(if (#{\a \e \i \o \u} %)
                           "vowels" "consonants")
                        "qwertyui"))
;;{"consonants" 5, "vowels" 3}

或者这个(如果你喜欢使用true/false而不是“元音/辅音”):

user> (frequencies (map (comp some? #{\a \e \i \o \u}) "qwertyui"))
;;{false 5, true 3}

【讨论】:

    【解决方案3】:

    @Brandon H 提到,for 是惰性的。如果需要,可以使用循环递归。这里我用 loop-recur 改变 for。

    (defn count-vowels-consenants [input]
      (let [m (atom {"vowels" 0 "consenants" 0})
            v #{"a" "e" "i" "o" "u"}]
        (loop [s input]
          (when (> (count s) 0)
          (if
              (contains? v (first (str s) ))
                (swap! m update-in ["vowels"] inc)
                (swap! m update-in ["consenants"] inc)
           ))
           (recur (apply str (rest s))))
        @m))
    

    【讨论】:

      【解决方案4】:

      这个问题以及所有现存的答案都假设每个字符都是元音或辅音:并非如此。即使在 ASCII 中,也有小写和大写字母。我会这样做...

      (defn count-vowels-consonants [s]
        (let [vowels  #{\a \e \i \o \u
                        \A \E \I \O \U}
              classify (fn [c]
                         (if (Character/isLetter c)
                           (if (vowels c) :vowel :consonant)))]
          (map-v count (dissoc (group-by classify s) nil))))
      

      ... 其中map-v 是一个函数,map 是映射的值:

      (defn map-v [f m] (reduce (fn [a [k v]] (assoc a k (f v))) {} m))
      

      例如,

      (count-vowels-consonants "s2a Boo!")
      ;{:vowel 3, :consonant 2}
      

      这只会遍历字符串一次。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-05-06
        • 2011-11-25
        • 2016-09-03
        • 1970-01-01
        • 2013-04-25
        • 2016-08-23
        • 2019-04-24
        • 2016-12-30
        相关资源
        最近更新 更多