【问题标题】:How to break out from nested doseqs如何摆脱嵌套的doseqs
【发布时间】:2010-05-15 22:09:12
【问题描述】:

我对嵌套的 doseq 循环有疑问。在 start 函数中,一旦我找到答案,我将 atom 设置为 true,这样使用 :while 的外循环验证就会失败。但是,它似乎并没有破坏它,并且循环继续进行。它有什么问题?

我也对 atom、refs、agents 的使用感到很困惑(为什么它们的更新函数名称不同,而机制几乎相同?)等。 在这种情况下可以使用原子作为标志吗?显然我需要一个类似对象的变量来存储状态。

(def pentagonal-list (map (fn [a] (/ (* a (dec (* 3 a))) 2)) (iterate inc 1)))


(def found (atom false))


(defn pentagonal? [a]
  (let [y (/ (inc (Math/sqrt (inc (* 24 a)))) 6)
        x (mod (* 10 y) 10)]
  (if (zero? x)
    true
    false)))


(defn both-pent? [a b]
  (let [sum (+ b a)
       diff (- a b)]
    (if (and (pentagonal? sum) (pentagonal? diff))
        true
        false)))

(defn start []
 (doseq [x pentagonal-list :while (false? @found)]
  (doseq [y pentagonal-list :while (<= y x)]
       (if (both-pent? x y)
           (do
            (reset! found true)
             (println (- x y)))))))

【问题讨论】:

    标签: clojure


    【解决方案1】:

    即使将 atom 设置为 true,您的版本也无法停止运行,直到内部 doseq 完成(直到 y > x)。一旦内循环完成,它将终止外循环。当我运行它时,它最终会终止。不知道你看到了什么。

    您不需要两个doseqs 来执行此操作。一个doseq 可以同时处理两个序列。

    user> (doseq [x (range 0 2) y (range 3 6)] (prn [x y]))
    [0 3]
    [0 4]
    [0 5]
    [1 3]
    [1 4]
    [1 5]
    

    for 也是如此。)据我所知,除了throw/catch,没有“突破”嵌套doseqs 的机制,但这是相当不习惯的。不过,您根本不需要 atom 或 doseq

    (def answers (filter (fn [[x y]] (both-pent? x y))
                         (for [x pentagonal-list
                               y pentagonal-list :while (<= y x)]
                           [x y])))
    

    您的代码在风格上非常重要。 “遍历这些列表,然后测试值,然后打印一些东西,然后停止循环。”在 Clojure 中,像这样使用原子进行控制并不是很习惯。

    一种更实用的方法是获取一个 seq(五边形列表)并将其包装在将其转换为其他 seq 的函数中,直到您获得一个可以提供您想要的东西的 seq。首先,我使用for 将此序列的两个副本转换为一个 y filter 将该序列转换为过滤掉我们不关心的值的序列。

    filterfor 是惰性的,因此一旦找到 first 有效值(如果您只需要一个),它将停止运行。这将返回您想要的两个数字,然后您可以将它们相减。

    (apply - (first answers))
    

    或者您可以进一步将该函数包装在另一个 map 中,以便为您计算差异。

    (def answers2 (map #(apply - %) answers))
    (first answers2)
    

    以这种方式进行函数式编程有一些优势。 seq 被缓存(如果你像我在这里做的那样抓住头部),所以一旦计算出一个值,它就会记住它,你可以从那时起立即访问它。如果不重置原子,您的版本将无法再次运行,然后必须重新计算所有内容。使用我的版本,您可以(take 5 answers) 获得前 5 个结果,或者根据需要映射结果以执行其他操作。您可以在上面doseq 并打印这些值。等等等等。

    我确信还有其他(也许更好)方法可以在不使用原子的情况下做到这一点。除非在 Clojure 中 100% 有必要,否则您通常应该避免更改引用。

    更改原子/代理/引用的函数名称不同可能是因为机制不同。 Refs 通过事务同步和协调。代理是异步的。原子是同步的和不协调的。它们都是一种“更改引用”,并且可能某种超级函数或宏可以将它们全部包装在一个名称中,但这会掩盖它们在幕后做着截然不同的事情的事实。完全解释差异可能超出了 SO 帖子的解释范围,但 http://clojure.org 完全解释了差异的所有细微差别。

    【讨论】:

    • 谢谢。没有意识到我可以在单个doseq / for中进行“循环”。在过滤和解构的五边形列表上使用 first 的技巧很棒。
    猜你喜欢
    • 1970-01-01
    • 2019-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-15
    • 1970-01-01
    • 2020-08-04
    • 1970-01-01
    相关资源
    最近更新 更多