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)
        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.
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
      (&lt; pos len-1) [pos 0]
      (&lt; pos len-2) [width (- pos len-1)]
      (&lt; 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.