Vector 两个先验排序向量相交的惯用/高效Clojure方法?

Vector 两个先验排序向量相交的惯用/高效Clojure方法?,vector,clojure,Vector,Clojure,我有一对向量x和y,它们是唯一的项,我知道每个项都要排序。我希望两者相交,维持排序顺序。理想的结果是另一个向量,用于快速随机访问 下面的生成只是为了举例,myx和y将进行预排序和预区分(它们实际上是时间样本) 我知道Clojure有Clojure.set/intersection,可以处理排序集。我的x和y具有相同的属性(排序的不同元素),但类型不同 问题1:如果x和y转换为排序集比(应用排序集x)更好/更快,因为它们已经是不同的和排序的吗? user=> (time (def ssx (

我有一对向量
x
y
,它们是唯一的项,我知道每个项都要排序。我希望两者相交,维持排序顺序。理想的结果是另一个向量,用于快速随机访问

下面的生成只是为了举例,my
x
y
将进行预排序和预区分(它们实际上是时间样本)

我知道Clojure有
Clojure.set/intersection
,可以处理
排序集
。我的
x
y
具有相同的属性(排序的不同元素),但类型不同

问题1:如果
x
y
转换为
排序集
(应用排序集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"
(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)))
现在我已准备好执行交叉

user=> (time (count (clojure.set/intersection ssx ssy)))
"Elapsed time: 355.42534 msecs"
39992
这在性能上有些令人失望,粗略地看一下
(source clojure.set/intersection)
似乎没有显示出对这些集合进行排序这一事实的任何特殊处理

问题2:是否有比
clojure.set/intersection
更好/更快的方法来执行
排序集
s的交集?

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"
(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)))
但是,我忍不住觉得我的代码过于程序化/迭代化


问题3:有谁能建议一种更惯用的方法来处理Clojure中的一对向量吗?

通常情况下,快速Clojure代码看起来有点必要。函数式代码通常是优雅的,但会带来一些相关的性能成本,您必须为此付出代价(惰性、来自被丢弃的不可变对象的额外GC压力等)

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"
(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)))
而且,转换成套装的成本总是会更高。构建集合本身就是一个
O(n log n)
操作,但是您可以利用向量已经得到支持的事实,在
O(n)
时间内实现交集操作

您的代码已经相当不错了,但您还可以进行一些优化:

  • 使用向量来收集结果。对于许多连续的conj操作,它们比常规的持久性向量快一点
  • 使用原语到向量的索引访问,而不是使用first/next遍历序列。这样可以避免创建临时的seq对象(和相关的GC)
生成的代码可能类似于:

(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 (and 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
(定义相交排序向量[xy]
(环路[i(长0)、j(长0)、r(瞬态[])]
(设[xi(n次x i零),yj(n次y j零)]
(续)
(不(和席YJ))(坚持!R)
(席YJ)(ReCurr(Inc)J R)
(>席YJ)(ReCuri I(Inc J)R)
:else(recur(inc i)(inc j)(conj!r xi()())))
(时间(计数(相交排序向量x y)))
=>“运行时间:5.143687毫秒”
=> 40258

如您所见,这可能会给您带来6-8倍左右的额外加速。

非常感谢您的回复。在我继续学习Clojure的过程中,你给了我很多思考和研究的机会。然而,我认为这里的额外速度很大程度上是由于您优越的运行时环境。在我的系统上,这需要同样多的时间(几乎到毫秒),或者,可能是相同的,只是重复运行我的代码会在你之前触发GC。我得进一步探索。好东西!我怀疑mikera尝试使用primitive
long
s会让他慢下来,因为
get
不支持primitive。因此,他不断地对整数进行装箱和拆箱,这与他删除的
下一次调用的成本差不多。我预测,用
nth
int
来代替会加快速度。@amalloy-好地方!切换到NTH,它似乎又提供了X2性能提升。我想你想写<代码>(不(和席YJ))< /C>。
版本在
上出现NullPointerException时失败(intersect sorted vector[1 2 3][2 3 4])