用于ith和i + 1元素循环的惯用语clojure

时间:2013-04-29 22:10:31

标签: recursion for-loop clojure lisp

我一直在尝试思考如何实现algorithm来计算相对于点的多边形的匝数。目前实现如下:(注意更新,因此代码有效)

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

我的问题是

  • 人们说最好尽可能使用循环结构,它比明确递归更高级别运行;例如mapforreduce
  • rest函数将向量转换为列表

我可以想到使用for和索引的实现,但我也听说最好不要使用索引。

是否存在处理矢量算法的惯用方法,在每次迭代中需要访问连续值?

3 个答案:

答案 0 :(得分:4)

通常,如果要访问序列的连续值,一次两个,则可以使用分区功能。分区允许您指定组大小和步长:

user> (partition 2 1 (range 10))
((0 1) (1 2) (2 3) (3 4) (4 5) (5 6) (6 7) (7 8) (8 9))

答案 1 :(得分:1)

这实际上取决于算法的形状。一般来说,更高级别的结构比显式递归更容易理解,但有时问题的形状会使它不那么清晰。

其他注意事项:

rest返回序列,而不是列表。这应该不重要。

你应该利用解构。例如:

    (let [x1 (first (first vertices))
          x2 (first (second vertices))
          y1 (second (first vertices))
          y2 (second (second vertices))

这可以替换为:

(let [[x1 y1] [x2 y2]] vertices] ... )

然而,使用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))))

答案 2 :(得分:0)

以下代码使用(map func seq (rest seq))来处理算法使用的点对。它还解决了原始实现的两个问题:

无论是否通过将第一个点重复为最后一个点来指定多边形,它都有效,即为两者提供相同的结果

[[1 1][-1 1][-1 -1][1 -1]] and 

[[1 1][-1 1][-1 -1][1 -1][1 1]] 

它也适用于在正x轴上具有连续点的多边形,而原始(和引用的伪代码)将沿着x减去每个线段的1/2 -轴。

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