How can I solve this programming situation with Clojure in functional mode?

I have a programming problem, I know how I can get my hands on Ruby, but I donโ€™t know the best way in Clojure (and drawing might be an elegant way to do this with a functional mindset in mind).

The problem can be simplified like this:


I have a 3 liter bucket filled with water. There is an opening at the bottom of the bucket that flows 10 ml / s (i.e. it should take 300 seconds / 5 minutes). I have a 100ml glass of water that I can use to pour new water into the bucket.

I can only pour the entire contents of the glass into the bucket, no partial pouring. The bay is instant.

Project a set of time steps where I can pour glasses of water into a bucket.


I know there is a fairly obvious algebraic way to do this, but the actual problem is with the "leak rate" which changes over time and the "new glass volumes" which are not always 100 ml and as such are not easy solve in a closed form.

The ruby โ€‹โ€‹way to solve this problem would be to track the volume of the bucket with a "Bucket instance" and check multiple time steps to see if the bucket has 100 ml of room. If so, discard the glass and add water to the "container instance". Continue the steps of time while observing the volume of the bucket.

I hope I have described this.

+3


source to share


2 answers


One of the most important concepts in functional programming is that any mutation without external side effects can be pushed onto immutable function parameter bindings.

Here, the simulation time and the bucket level are the main parameters of the function, and they are updated for each recursive call. Other parameters are modeled as a function of time. We could imagine that each of these functions is actually a lazy sequence based on time deltas, just like the function itself fill-times

. Or piecewise linear equations simulated by searching in a vector, or whathaveyou.



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)

      

When there is enough space, the glass is reset (and of course fills up instantly) and we mark the time by calling the function again to get the next time. When there is no room, we repeat, update the time and bucket level. The result is a (hypothetically infinite) lazy sequence of times when the glass can be emptied (assuming that the glass fills up instantly and is immediately discarded).

+5


source


I don't have much experience with Clojure, but one way to think about this is with a lazy series of state values โ€‹โ€‹in time steps. Easily compute each state value from the previous state value.

This is a recurrent equation, also known as a difference equation. It calculates new values โ€‹โ€‹as a function of previous values โ€‹โ€‹without overwriting them.



The state value can only be the bucket level or the tuple holding (time, bucket_level, pour_in_count).

+3


source







All Articles