我写了一个二进制搜索函数作为一个更大的程序的一部分,但它似乎比它应该慢,并且分析显示了很多对clojure.lang.Numbers中的方法的调用。
我的理解是,当Clojure可以确定它可以这样做时,它可以使用原语。对clojure.lang.Numbers中方法的调用似乎表明它不是在这里使用原语。
如果我将循环变量强制为int,它会正确地抱怨recur参数不是原始的。如果我也强迫那些,代码再次起作用,但又很慢。我唯一的猜测是(quot (+ low-idx high-idx) 2)
没有产生原始但我不知道从哪里去。
这是我在Clojure的第一个程序,所以如果有更清洁/功能/ Clojure的方法可以随时告诉我。
(defn binary-search
[coll coll-size target]
(let [cnt (dec coll-size)]
(loop [low-idx 0 high-idx cnt]
(if (> low-idx high-idx)
nil
(let [mid-idx (quot (+ low-idx high-idx) 2) mid-val (coll mid-idx)]
(cond
(= mid-val target) mid-idx
(< mid-val target) (recur (inc mid-idx) high-idx)
(> mid-val target) (recur low-idx (dec mid-idx))
))))))
(defn binary-search-perf-test
[test-size]
(do
(let [test-set (vec (range 1 (inc test-size))) test-set-size (count test-set)]
(time (count (map #(binary-search2 test-set test-set-size %) test-set)))
)))
答案 0 :(得分:9)
首先,您可以使用java.util.Collections
提供的二进制搜索实现:
(java.util.Collections/binarySearch [0 1 2 3] 2 compare)
; => 2
如果你跳过compare
,搜索速度会更快,除非收集包含bigint,在这种情况下它会中断。
至于你的纯Clojure实现,你可以在参数向量中用coll-size
提示^long
- 或者只是在函数体的开头询问向量的大小(这是一个非常快的,常数时间操作),用(quot ... 2)
替换(bit-shift-right ... 1)
调用,并使用未经检查的数学进行索引计算。通过一些额外的调整,可以按如下方式编写二进制搜索:
(defn binary-search
"Finds earliest occurrence of x in xs (a vector) using binary search."
([xs x]
(loop [l 0 h (unchecked-dec (count xs))]
(if (<= h (inc l))
(cond
(== x (xs l)) l
(== x (xs h)) h
:else nil)
(let [m (unchecked-add l (bit-shift-right (unchecked-subtract h l) 1))]
(if (< (xs m) x)
(recur (unchecked-inc m) h)
(recur l m)))))))
这仍然明显慢于Java变体:
(defn java-binsearch [xs x]
(java.util.Collections/binarySearch xs x compare))
上面定义的 binary-search
似乎比java-binsearch
花费的时间多25%。
答案 1 :(得分:1)
在Clojure 1.2.x中,你只能强制局部变量而且它们不能跨越函数调用。
从Clojure 1.3.0开始,Clojure可以在函数调用中使用原始数字,但不能通过map
等高阶函数。
如果您使用的是clojure 1.3.0+,那么您应该可以使用类型提示
来完成此操作与任何clojure优化问题一样,第一步是打开(set! *warn-on-reflection* true)
然后添加类型提示,直到它不再抱怨。
user=> (set! *warn-on-reflection* true)
true
user=> (defn binary-search
[coll coll-size target]
(let [cnt (dec coll-size)]
(loop [low-idx 0 high-idx cnt]
(if (> low-idx high-idx)
nil
(let [mid-idx (quot (+ low-idx high-idx) 2) mid-val (coll mid-idx)]
(cond
(= mid-val target) mid-idx
(< mid-val target) (recur (inc mid-idx) high-idx)
(> mid-val target) (recur low-idx (dec mid-idx))
))))))
NO_SOURCE_FILE:23 recur arg for primitive local: low_idx is not matching primitive,
had: Object, needed: long
Auto-boxing loop arg: low-idx
#'user/binary-search
user=>
要删除它,您可以键入提示coll-size参数
(defn binary-search
[coll ^long coll-size target]
(let [cnt (dec coll-size)]
(loop [low-idx 0 high-idx cnt]
(if (> low-idx high-idx)
nil
(let [mid-idx (quot (+ low-idx high-idx) 2) mid-val (coll mid-idx)]
(cond
(= mid-val target) mid-idx
(< mid-val target) (recur (inc mid-idx) high-idx)
(> mid-val target) (recur low-idx (dec mid-idx))
))))))
将第10行的自动装箱连接到coll-size参数是很难理解的,因为它经过cnt
然后high-idx
然后mid-ixd
等等,所以我一般通过类型提示一切来解决这些问题,直到找到使警告消失的那个,然后删除提示,只要它们保持不变