我有一对独特项目的x
和y
个向量,我知道每个项目都要排序。我希望有两者的交集,维持排序顺序。理想情况下,结果将是另一个向量,用于快速随机访问。
下面的代数仅仅是为了举例,我的x
和y
将预先分类并预先区分(实际上是时间样本)。
(defn gen-example [c] (-> (repeatedly c #(-> c rand int)) distinct sort vec))
user=> (def x (gen-example 100000)) (count x)
#'user/x
63161
user=> (def y (gen-example 100000)) (count y)
#'user/y
63224
我知道Clojure有clojure.set/intersection
可以使用sorted-set
。我的x
和y
具有相同的属性(已排序的不同元素),但类型不同。
问题1:是否有更好/更快的方式将x
和y
转换为sorted-set
而不是(apply sorted-set x)
,因为它们已经是明确的并已排序吗
user=> (time (def ssx (apply sorted-set x)))
"Elapsed time: 607.642592 msecs"
user=> (time (def ssy (apply sorted-set y)))
"Elapsed time: 617.046022 msecs"
现在我准备好执行我的交集了
user=> (time (count (clojure.set/intersection ssx ssy)))
"Elapsed time: 355.42534 msecs"
39992
这有点令人失望的表现,粗略看一下(source clojure.set/intersection)
似乎没有对这些集合进行排序的事实表现出任何特殊处理。
问题2:是否有更好/更快的方式来执行sorted-set
的交叉而不是clojure.set/intersection
?
(defn intersect-sorted-vector [x y]
(loop [x (seq x) y (seq y) acc []]
(if (and x y)
(let [x1 (first x)
y1 (first y)]
(cond
( < x1 y1) (recur (next x) y acc)
( > x1 y1) (recur x (next y) acc)
:else (recur (next x) (next y) (conj acc x1))))
acc)))
事实证明这是一个很好的交易(接近10倍)。
user=> (time (count (intersect-sorted-vector x y)))
"Elapsed time: 40.142532 msecs"
39992
但是,我不禁觉得我的代码过于程序/迭代。
问题3:有人可以建议在Clojure中处理一对向量的更惯用的方法吗?
答案 0 :(得分:7)
通常情况下,快速的Clojure代码看起来有点必要。功能代码通常很优雅,但需要支付一些相关的性能成本(懒惰,来自丢弃的不可变对象的额外GC压力等)。
此外,转换成套装总是会更贵。构建集合本身就是O(n log n)
操作,但您可以利用已经支持向量的事实来在O(n)
时间内实现交集操作。
您的代码已经相当不错,但您仍可以进行更多优化:
结果代码可能类似于:
(defn intersect-sorted-vector [x y]
(loop [i (long 0), j (long 0), r (transient [])]
(let [xi (nth x i nil), yj (nth y j nil)]
(cond
(not (or xi yj)) (persistent! r)
(< xi yj) (recur (inc i) j r)
(> xi yj) (recur i (inc j) r)
:else (recur (inc i) (inc j) (conj! r xi))))))
(time (count (intersect-sorted-vector x y)))
=> "Elapsed time: 5.143687 msecs"
=> 40258
所以你可以看到,这可能会让你额外加速6-8倍。