I'm writing a Diffusion-limited aggregation simulation, and need to pick a random point along the edge of an area defined by a dimension pair of
[width, height]. The point shouldn't be deep within the area; it should always be on the border of one of the sides. This is the intended behavior:
(let [rand-gen (Random.) dims [21 11]] (dotimes [_ 25] ; I'm casting to int so the output is legible ; I actually need doubles (println (mapv int (random-edge-coord dims rand-gen))))) [13 0] [0 8] [15 10] [8 10] [20 0] [0 6] [20 2] [0 5] [7 10] [0 7] [0 10] [20 1] [6 0] [20 0] [0 2] [20 10] [15 0] [14 0] [7 0] [0 7] [12 0] [20 7] [20 4] [20 8] [20 4]
This is the mess I came up with:
; These two functions are just for the sake of a MCVE (defn- random-boolean [^Random rand-gen] (.nextBoolean rand-gen)) (defn- random-double [min, max, ^Random rand-gen] (let [r (.nextDouble rand-gen) spread (- max min) rand (* spread r)] (+ rand min))) (defn random-edge-coord [dimensions, ^Random rand-gen] (let [[w h] dimensions vertical? (random-boolean rand-gen) start? (random-boolean rand-gen) x (if vertical? ; Pick either the left or right edge (if start? 0 (dec w)) ; Else, pick a random point along the top/bottom egde (random-double 0 w rand-gen)) y (if vertical? (random-double 0 h rand-gen) (if start? 0 (dec h)))] [x y]))
The redundancy is killing me though. I can't think of how to clean it up. I feel like this should be fairly straightforward to generalize, but I'm stuck. I figured I could clean it up introducing some anonymous functions:
(defn random-edge-coord [dimensions rand-gen] (let [[w h] dimensions vertical? (g/random-boolean rand-gen) start? (g/random-boolean rand-gen) edge #(if start? 0 (dec %)) rand-edge #(g/random-double 0 % rand-gen) x (if vertical? (edge w) (rand-edge w)) y (if vertical? (rand-edge h) (edge h))] [x y]))
But that's not a huge gain.
Any input here would be appreciated.