Reputation: 9206
I have been trying to think how to implement an algorithm to compute the winding number of a polygon with respect to a point. Currently the implementation is as follows: (note updated so code works)
(defn winding-num
"Return winding number of polygon
see Alciatore "
[poly point]
; translate poly such that point is at origin
(let [translated-poly (map #(vec-f - % point) poly)]
; w is wind-num
(loop [vertices translated-poly w 0]
(cond
(= (count vertices) 1)
w
:else
(let [x1 (first (first vertices))
x2 (first (second vertices))
y1 (second (first vertices))
y2 (second (second vertices))]
(cond
(and (< (* y1 y2) 0)
(> (+ x1 (/ (* y1 (- x2 x1))
(- y1 y2)))
0))
(if (< y1 0)
(recur (rest vertices) (inc w))
(recur (rest vertices) (dec w)))
(and (zero? y1)
(> x1 0))
(if (> y2 0)
(recur (rest vertices) (+ w 0.5))
(recur (rest vertices) (- w 0.5)))
(and (zero? y2)
(> x2 0))
(if (< y1 0)
(recur (rest vertices) (+ w 0.5))
(recur (rest vertices) (- w 0.5)))
:else
(recur (rest vertices) w)))))))
My problems with this are
map
, for
, reduce
, etc.I could think of an implementation using for
and indices, but I also hear it is preferable to not use indices.
Is there an idiomatic way for dealing with vector algorithms which in each iteration need access to consecutive values?
Upvotes: 4
Views: 265
Reputation: 6315
The following code is using (map func seq (rest seq))
to handle the pair of points used by the algorithm. It also fixes two problems with the original implementation:
It works whether or not the polygon is specified by repeating the first point as the last, i.e. giving the same result for both
[[1 1][-1 1][-1 -1][1 -1]] and
[[1 1][-1 1][-1 -1][1 -1][1 1]]
It also works for polygons that have successive points on the positive x-axis, whereas the original (and the refered pseudo code) will substract 1/2
for each line segment along the x-axis.
(defn translate [vec point]
(map (fn [p] (map - p point)) vec))
(defn sign [x]
(cond (or (not (number? x)) (zero? x)) 0
(pos? x) 1
:else -1))
(defn winding-number [polygon point]
(let [polygon (translate (conj polygon (first polygon)) point)]
(reduce +
(map (fn [[x1 y1][x2 y2]]
(cond (and (neg? (* y1 y2))
(pos? (- x2 (* y2 (/ (- x2 x1) (- y2 y1))))))
(sign y2)
(and (zero? y1) (pos? x1))
(sign y2)
(and (zero? y2) (pos? x2))
(sign y1)
:else 0))
polygon (rest polygon)))))
Upvotes: 0
Reputation: 31641
It really depends on the shape of your algorithm. Generally speaking higher-level constructs are more understandable than explicit recursion, but sometimes the shape of the problem makes this less clear.
Other things to note:
rest
returns a sequence, not a list. This shouldn't matter here.
You should make use of destructuring. For example:
(let [x1 (first (first vertices))
x2 (first (second vertices))
y1 (second (first vertices))
y2 (second (second vertices))
This can be replaced by:
(let [[x1 y1] [x2 y2]] vertices] ... )
However this is not a very difficult algorithm to implement with reduce
:
(defn inc-dec
"Convenience function for incrementing and decrementing"
([condition i] (if condition (inc i) (dec i)))
([condition i amount] (if condition (+ i amount) (- i amount))))
(defn winding-num
[poly point]
(let [translated-poly (map #(map - % point) poly)
winding-reducer
(fn winding-reducer [w [[x1 y1] [x2 y2]]]
(cond
(and (< (* y1 y2) 0)
; r
(> (+ x1 (/ (* y1 (- x2 x1))
(- y1 y2)))
0))
(inc-dec (< y1 0) w)
(and (zero? y1) (> x1 0))
(inc-dec (> y2 0) w 0.5)
(and (zero? y2) (> x2 0))
(inc-dec (< y1 0) w 0.5)
:else w))
]
(reduce winding-reducer 0 (partition 2 1 translated-poly))))
Upvotes: 1
Reputation: 14479
In general if you want to access consecutive values of a sequence, two at a time, you can use the partition function. Partition allows you to specify a group size as well as a step size:
user> (partition 2 1 (range 10))
((0 1) (1 2) (2 3) (3 4) (4 5) (5 6) (6 7) (7 8) (8 9))
Upvotes: 4