# Picking a random point along the edge of an area

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)
(+ 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.

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

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.