【问题标题】:How to solve this programming situation using Clojure in a functional manner?如何以函数式的方式使用 Clojure 解决这种编程情况?
【发布时间】:2014-10-23 01:34:57
【问题描述】:

我有一个编程问题,我知道如何在 Ruby 中解决,但不知道在 Clojure 中的最佳方法(我认为可能有一种优雅的方法来解决此问题,采用函数式思维方式)。

问题可以简化为:


我有一个 3 升的桶,里面装满了水。桶底有一个孔,泄漏量为 10 mL/s(即需要 300 秒 / 5 分钟才能清空)。我有一杯容量为 100 毫升的水,可以用来向桶中倒入新水。

我只能把杯子里的东西全部倒进桶里,不能倒一部分。倾倒是瞬间发生的。

计划出一组时间步长,我可以将一杯水倒入桶中。


我知道使用代数有一种非常明显的方法可以做到这一点,但实际问题涉及随时间变化的“泄漏率”,以及并不总是等于 100 mL 的“新玻璃体积”,因此以封闭的形式解决并不简单。

解决此问题的 Ruby 方法是使用“Bucket 实例”来跟踪存储桶的容量,并在多个时间步长上进行测试以查看存储桶是否有 100 毫升的空间。如果是这样,倒掉玻璃杯,并添加到“桶实例”中的水中。继续时间步长,观察桶的体积。

我希望我所描述的很清楚。

【问题讨论】:

  • 你能说明为什么答案并不总是 290mL 吗?
  • @LeonGrapenthin 请看问题描述后的段落。

标签: algorithm clojure functional-programming


【解决方案1】:

函数式编程最重要的概念之一是任何没有外部副作用的突变都可以卸载到不可变的函数参数绑定上。

这里模拟的时间和bucket的级别是主要的函数参数,每次递归调用都会更新。其他参数被建模为时间的函数。我们可以将这些函数中的每一个想象成一个基于时间增量的惰性序列,就像fill-times 函数本身一样。或者用向量中的查找建模的分段线性方程,或者你有什么。

user> 
(defn fill-times
  [time level
   {:keys [sample-rate calc-bucket-size calc-leak-rate calc-glass-size]
    :as params}]
  (loop [t time l level]
    (let [input-capacity (calc-glass-size time)
          bucket-capacity (calc-bucket-size time)
          has-room (> (- bucket-capacity l) input-capacity)
          leak-delta (* (calc-leak-rate) sample-rate -1)]
      (if has-room
        (lazy-seq (cons t (fill-times t (+ l input-capacity)
                                      params)))
        (recur (+ t sample-rate) (+ l leak-delta))))))

#'user/fill-times
user> (take 50 (fill-times 0 0 {:sample-rate 1
                                 :calc-bucket-size (constantly 3000)
                                 :calc-leak-rate (constantly 10)
                                 :calc-glass-size (constantly 100)}))
(0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 11 21 31 41 51 61 71 81 91 101 111 121 131 141 151 161 171 181 191 201)

当有足够的空间时,玻璃杯被倾倒(当然是立即装满),我们记下时间,再次调用该函数以获取以下时间。当没有空间时,我们会重复,更新时间和存储桶级别。结果是一个(假设是无限的)懒惰的时间序列,在这些时间可以倒空玻璃杯(假设玻璃杯立即装满,并立即倾倒)。

【讨论】:

  • 一切都是无单位的,但当然你输入的单位必须一致
  • 我真希望我能投票两次。 1)您为我的问题提供了一个完全合理的解决方案。 2)你教会了我一个非常重要的概念:函数式编程。我想知道,当我试图弄清楚如何以一种功能性的方式做事时,将输入的数据突变视为不可变参数的这种思考方式是否是我的大脑不断遇到的问题。
  • 我是否可以在某个地方阅读更多关于这个概念的信息(以及其他我不会意识到来自业余 OO 背景的类似概念?)
  • Clojure 的作者 Rich Hickey 进行了各种录像演讲,他在其中谈到了这个问题(以及通过明确考虑时间来消除可变性的相关概念)。 infoq.com/author/Rich-Hickey 。此外,任何像样的 Clojure 书籍都可以解决这些问题(包括 Programming Clojure、Clojure Programming 和在线书籍 Clojure from the Ground Up 和 Clojure for the Brave and True)
  • 小注——泄漏增量逻辑错误,我刚刚更新了它(泄漏应该只在时间增加时应用)
【解决方案2】:

我对 Clojure 没有太多经验,但一种思考方式是在时间步长的状态值的惰性序列。从前一个状态值懒惰地计算每个状态值。

这是一个递推方程,也称为差分方程。它计算新值作为先前值的函数而不覆盖它们。

状态值可以只是存储桶级别或包含 (time, bucket_level, pour_in_count) 的元组。

【讨论】:

  • 感谢您向我介绍递推方程!我不知道这个概念。实际上,我设法找到了另一个 SO 问题,其中有人提出了 clojure 递归方程的广义形式。
猜你喜欢
  • 2011-04-19
  • 2022-12-22
  • 2021-03-07
  • 2017-06-19
  • 2010-09-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多