2
\$\begingroup\$

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.

\$\endgroup\$
  • \$\begingroup\$ Is this just some random rectangle? \$\endgroup\$ – Alan Thompson Apr 4 at 2:34
  • \$\begingroup\$ @AlanThompson It's just a rectangle, yes. The bounds start from 0, and extend to the width/height of the rectangle. \$\endgroup\$ – Carcigenicate Apr 4 at 2:56
1
\$\begingroup\$

Easy-peasy. Just imagine walking around the rectangle starting at the origin in a counter-clockwise direction, and keeping track of the length traversed:

(defn random-edge-coord
  [width height]
  (let [len-1 width
        len-2 (+ width height)
        len-3 (+ (* 2 width) height)
        len-4 (* 2 (+ width height))
        pos   (rand len-4)]
    (cond
      (< pos len-1) [pos 0]
      (< pos len-2) [width (- pos len-1)]
      (< pos len-3) [(- len-3 pos) height]
      :else [0 (- len-4 pos)])))

(defn print-1 [[x y]]
  (printf "[%3.1f %3.1f] \n" (double x) (double y)) )

(dotest
  (nl)
  (dotimes [i 10]
    (print-1 (random-edge-coord 2 3))))

with result:

[1.4 3.0] 
[0.0 0.2] 
[2.0 2.1] 
[1.2 0.0] 
[2.0 1.4] 
[1.7 0.0] 
[1.5 3.0] 
[1.5 3.0] 
[2.0 0.7] 
[1.0 3.0] 

and

[1.0 0.0] 
[1.4 0.0] 
[1.4 3.0] 
[0.5 0.0] 
[0.0 2.6] 
[2.0 2.7] 
[0.0 1.5] 
[2.0 2.8] 
[0.9 3.0] 
[1.1 3.0] 

For a more general shape, you could calculate the length breakpoints in a loop and use a loop to search them in order instead of a cond. You could also use generalized line-segments, then interpolate so you don't depend on simply vertical/horizontal coordinates.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.