【发布时间】:2018-04-25 13:39:52
【问题描述】:
我想使用自定义比较器对原始 Java 数组进行排序,但出现类型错误。我认为comparator 函数正在创建Comparator<java.lang.Object> 而不是Comparator<Long>,但我不知道如何解决这个问题。
这是一个最小的例子:
x.core=> (def x (double-array [4 3 5 6 7]))
#'x.core/x
x.core=> (java.util.Arrays/sort x (comparator #(> %1 %2)))
ClassCastException [D cannot be cast to [Ljava.lang.Object; x.core/eval1524 (form-init5588058267991397340.clj:1)
我尝试向比较器函数添加不同的类型提示,但坦率地说,我对这门语言比较陌生,基本上只是在扔飞镖。
我特意简化了上面的示例,以专注于关键问题,即类型错误。在下面的部分中,我尝试提供更多细节来激发问题并展示我为什么使用自定义比较器。
动机
我想要做的是复制 R 的 order 函数,它的工作原理如下:
> x = c(7, 2, 5, 3, 1, 4, 6)
> order(x)
[1] 5 2 4 6 3 7 1
> x[order(x)]
[1] 1 2 3 4 5 6 7
如您所见,它返回将对其输入向量进行排序的索引排列。
这是 Clojure 中的一个有效解决方案:
(defn order
"Permutation of indices sorted by x"
[x]
(let [v (vec x)]
(sort-by #(v %) (range (count v)))))
x.core=> (order [7 2 5 3 1 4 6])
(4 1 3 5 2 6 0)
(请注意,R 是 1-indexed 而 Clojure 是 0-indexed。)诀窍是对一个向量(即 x [0, 1, ..., (count x)] 的索引按向量 x 本身进行排序。
R 与 Clojure 性能
不幸的是,我对这个解决方案的性能感到困扰。 R 解决方案要快得多:
> x = runif(1000000)
> system.time({ y = order(x) })
user system elapsed
0.041 0.004 0.046
对应的Clojure代码:
x.core=> (def x (repeatedly 1000000 rand))
#'x.core/x
x.core=> (time (def y (order x)))
"Elapsed time: 2857.216452 msecs"
#'x.core/y
原始数组解决方案?
我发现原始数组的排序时间与 R 相当:
> x = runif(1000000)
> system.time({ y = sort(x) })
user system elapsed
0.061 0.005 0.069
对比
x.core=> (def x (double-array (repeatedly 1000000 rand)))
#'x.core/x
x.core=> (time (java.util.Arrays/sort x))
"Elapsed time: 86.827277 msecs"
nil
这是我尝试将自定义比较器与 java.util.Arrays 类一起使用的动机。我希望速度能和R相媲美。
我应该补充一点,我可以使用带有 ArrayList 的自定义 Comparator,如下所示,但性能并不比我的起始函数好:
(defn order2
[x]
(let [v (vec x)
compx (comparator (fn [i j] (< (v i) (v j))))
ix (java.util.ArrayList. (range (count v)))]
(java.util.Collections/sort ix compx)
(vec ix)))
我们将不胜感激,即使您只是想提供一些一般的 Clojure 建议。我仍在学习这门语言,并从中获得了很多乐趣。 :-)
编辑
根据下面 Carcigenicate 的回答,
(defn order
[x]
(let [ix (int-array (range (count x)))]
(vec (-> (java.util.Arrays/stream ix)
(.boxed)
(.sorted (fn [i j] (< (aget x i) (aget x j))))
(.mapToInt
(proxy [java.util.function.ToIntFunction] []
(applyAsInt [^long d] d)))
(.toArray)))))
会起作用:
x.core=> (def x (double-array [5 3 1 3.14 -10]))
#'x.core/x
x.core=> (order x)
[4 2 1 3 0]
x.core=> (map #(aget x %) (order x))
(-10.0 1.0 3.0 3.14 5.0)
不幸的是,它超级慢。我想原语可能毕竟不是答案。
【问题讨论】:
-
我认为问题在于
java.util.Arrays/sort对于接受比较器的双数组似乎没有过载。唯一这样做的是Object数组,它假设您的意思是。 -
如果要对基元进行排序,为什么要使用自定义比较器?唯一的选择是按升序或降序排序,对吗?如果需要,为什么不直接使用本机排序然后反转结果?
-
这只是一个简单的例子。我实际上想对一个数组进行排序,我只是希望原语上的 java.util.Arrays 排序方法比天真的解决方案更快。
-
@broken.eggshell 请注意,在您的编辑底部,您写道“我猜原语可能不是答案”。据我所知,任何使用
java.util.Arrays/sort的解决方案都需要拳击。问题不在于原始数组的速度,而在于您可以在不需要装箱的情况下实际使用它们做什么的限制。如果您只想处理原始数组,您可能需要查看第三方库。不幸的是,我不能推荐这样的图书馆。
标签: clojure