简介
假设您要确定列表中哪个点最接近另一个给定点。函数应该返回点本身和距离。
例如,这些数据:
(def pts [[2 4] [1 9] [9 4] [2 8]])
(def p [7 6])
首先,需要一些辅助函数:
(def abs js/Math.abs)
(def pow js/Math.pow)
(def sqrt js/Math.sqrt)
(def pow2 #(pow % 2))
(defn distance [p1 p2]
(sqrt (+ (pow2 (abs (- (p1 0) (p2 0))))
(pow2 (abs (- (p1 1) (p2 1)))))))
两项提案
我的第一个方法如下:
(defn find-closest [p pts]
(->> (map #(vector (distance p %) %) pts)
(reduce (fn [m v]
(if (< (v 0) (m 0))
v
m)))))
(find-closest p pts)
=> [2.8284271247461903 [9 4]] ;; this is a correct result
通过尝试使该功能更具性能,我提出了第二个版本:
(defn find-closest2 [p pts]
(let [init (first pts)]
(reduce (fn [m v]
(let [d (distance p v)]
(if (< d (m 0))
[d v]
m)))
[(distance p init) init]
(rest pts))))
事实上,后来的功能变得更快(在chrome-browser 49中测试):
=> (time (dotimes [_ 100000] (find-closest p pts)))
"Elapsed time: 445.720000 msecs"
=> (time (dotimes [_ 100000] (find-closest2 p pts)))
"Elapsed time: 248.900000 msecs"
除了一个注释:有没有人提示,为什么在Clojure中相同的功能要慢一些:?
user> (time (dotimes [_ 100000] (find-closest p pts)))
"Elapsed time: 6886.850965 msecs"
user> (time (dotimes [_ 100000] (find-closest2 p pts)))
"Elapsed time: 6574.486679 msecs"
这会慢10倍以上,我觉得很难相信。
问题
无论如何,由于我需要ClojureScript项目的功能,这是我的问题:你如何解决这个问题? find-closest
看起来对我很好,但速度较快的版本find-closest2
看起来有点令人困惑。有没有更好的方法呢?
答案 0 :(得分:1)
与您根据微观基准做出决策的所有情况一样,值得使用基准库(如criterium)来确保您看到统计上显着的结果。
在这种情况下,区别在于计算立即丢弃的中间懒惰序列。 map
正在生成所有潜在答案的序列,并为每个答案分配内存。由于您对使用中间结果不感兴趣,因此浪费了此时间并且仅降低版本的速度更快。
直到最近,Clojure程序有时必须在使用map reduce filter等进行简单和可组合之间进行选择,并且通过不产生中间结果来快速进行选择。 这是通过trannsducers 修复的,所以现在您可以使用地图版本而不会引入中间结果,并且您可以以非常通用且适应性强的方式执行此操作。
user> (import '[java.lang.Math])
nil
user> (def pow2 #(Math/pow % 2))
(defn distance [p1 p2]
(Math/sqrt (+ (pow2 (Math/abs (- (p1 0) (p2 0))))
(pow2 (Math/abs (- (p1 1) (p2 1)))))))
#'user/pow2
#'user/distance
user> (defn closer-point
([] [Long/MAX_VALUE [Long/MAX_VALUE Long/MAX_VALUE]])
([p1] p1)
([[distance1 point1 :as p1]
[distance2 point2 :as p2]]
(if (< distance1 distance2)
p1
p2)))
#'user/closer-point
user> (transduce (map #(vector (distance p %) %))
closer-point
pts)
[2.8284271247461903 [9 4]]
答案 1 :(得分:1)
min-key
函数专为此问题而设计。这是JVM版本。请注意,我们只是最小化平方距离,而不用计算Math/sqrt
的实际距离:
(ns clj.core
(:use tupelo.core)
(:require [clojure.core :as clj]
[schema.core :as s]
[tupelo.types :as tt]
[tupelo.schema :as ts]
[criterium.core :as crit]
))
; Prismatic Schema type definitions
(s/set-fn-validation! true) ; #todo add to Schema docs
(def pts [[2 4] [1 9] [9 4] [2 8]])
(def p [7 6])
(defn square [x] (* x x))
(defn dist2 [p1 p2]
(+ (square (- (p1 0) (p2 0)))
(square (- (p1 1) (p2 1)))))
(doseq [curr-p pts]
(println "curr-p: " curr-p " -> " (dist2 p curr-p)))
(newline)
(spyx (apply min-key #(dist2 p %) pts))
(newline)
(crit/quick-bench (apply min-key #(dist2 p %) pts))
(defn -main [] )
我不会过分担心代码的过早优化,只需先让它变得简单易懂。使用内置函数几乎总是一个良好的开端(就像在你真的不需要平方根时最小化数量的平方的旧技巧)。请注意,由于(abs ...)
会自动执行此操作,因此我也摆脱了(square ...)
调用。
以下是试运行的结果:
curr-p: [2 4] -> 29
curr-p: [1 9] -> 45
curr-p: [9 4] -> 8
curr-p: [2 8] -> 29
(apply min-key (fn* [p1__8701#] (dist2 p p1__8701#)) pts) => [9 4]
WARNING: Final GC required 7.5524163302816705 % of runtime
Evaluation count : 1132842 in 6 samples of 188807 calls.
Execution time mean : 527.711887 ns
Execution time std-deviation : 3.437558 ns
Execution time lower quantile : 524.840276 ns ( 2.5%)
Execution time upper quantile : 531.911280 ns (97.5%)
Overhead used : 1.534138 ns