【问题标题】:Clojure: sequence back to vectorClojure:序列返回向量
【发布时间】:2010-01-01 18:50:40
【问题描述】:

如何在序列生成操作(如排序)之后将序列转换回向量?在作为向量的序列上使用 (vec..) 成本高吗?

一个(不好的?)可能性是创建一个不按顺序的新向量:

(vec (sort [1 2 3 4 5 6]))

我之所以这么问,是因为我需要随机访问(第 n 个 ..)对巨大的排序向量 - 排序后现在是巨大的序列,具有可怕的 O(n) 随机访问时间

【问题讨论】:

    标签: clojure


    【解决方案1】:

    Meikel Brandmeyer 刚刚在 Clojure 小组上发布了一个解决方案。

    (defn sorted-vec
      [coll]
      (let [arr (into-array coll)]
        (java.util.Arrays/sort arr)
        (vec arr)))
    

    Clojure 的 sort 返回一个跨排序数组的序列;这种方法做的事情大致相同,但返回的是一个向量,而不是一个序列。

    如果您愿意,您甚至可以跳过转换回 Clojure 持久数据结构:

    (defn sorted-arr
      "Returns a *mutable* array!"
      [coll]
      (doto (into-array coll)]
        (java.util.Arrays/sort))
    

    但生成的 Java 数组(在大多数情况下您可以将其视为 Clojure 集合)将是可变的。如果你不把它交给其他代码也没关系,但要小心。

    【讨论】:

    • 应该是 java.util.Arrays/sort (他忘记了 s)。但是如果是同样的东西,怎么会快4倍呢?我在 Clojure 组中发布了时间安排。
    • 至少在服务器虚拟机(您应该使用它)上,速度不会快 4 倍。 c.c.sort 使用显式比较器,并返回一个序列。它还调用 to-array,而不是 into-array:前者返回一个对象数组,而后者返回一个类型化数组。除了那些使排序更通用的东西之外,它是相同的代码。这些一般性成本。这个练习的全部目的是避免一些工作;这就是为什么这个版本更快。
    【解决方案2】:

    根据我自己的测试(不是科学的),在您进行大量排序的情况下,直接处理数组可能会更好。但是,如果您很少排序并且有很多随机访问要做,那么使用向量可能是更好的选择,因为随机访问时间平均快 40% 以上,但是由于将向量转换为一个数组,然后返回一个向量。这是我的发现:

    (def foo (int-array (range 1000)))
    
    (time
      (dotimes [_ 10000]
        (java.util.Arrays/sort foo)))
    
    ; Elapsed time: 652.185436 msecs
    
    (time
      (dotimes [_ 10000]
        (nth foo (rand-int 1000))))
    
    ; Elapsed time: 7.900073 msecs
    
    (def bar (vec (range 1000)))
    
    (time
      (dotimes [_ 10000]
        (vec (sort bar))))
    
    ; Elapsed time: 2810.877103 msecs
    
    (time
      (dotimes [_ 10000]
        (nth bar (rand-int 1000))))
    
    ; Elapsed time: 5.500802 msecs
    

    P.S.:请注意,矢量版本实际上并未将排序后的矢量存储在任何地方,但这不会显着改变结果,因为您会在循环中使用简单的绑定来提高速度。

    【讨论】:

    • 不错。现在很容易看出,对已排序的向量执行 (vec) 比对直接数组排序慢 4 倍!向量和数组的随机访问时间都非常快,我认为 40% 无关紧要。
    【解决方案3】:

    如果您需要对具有大量向量的排序结果进行随机访问,那么调用 vec 所花费的时间应该远远超过这样做所节省的时间。

    如果您分析并发现它太慢,您可能不得不使用 java 数组。

    【讨论】:

    • 这就是我现在正在做的事情。调用 vec。但我想知道是否有更好的方法
    【解决方案4】:

    作为一个新的 Clojure 开发者,很容易混淆集合和序列。

    这个排序的向量函数:

    (排序 [1 2 3 4 5 6]) => (1 2 3 4 5 6) ;返回一个序列

    但是我需要一个向量来进行下一个操作,因为这不起作用...

    (暂时(部分> 3)(1 2 3 4 5 6))

    =>ClassCastException java.lang.Long 无法转换为 clojure.lang.IFn 用户/eval2251 (NO_SOURCE_FILE:2136)

    让我们尝试将序列转换为向量:

    (vec (1 2 3 4 5 6))

    =>ClassCastException java.lang.Long 无法转换为 clojure.lang.IFn 用户/eval2253 (NO_SOURCE_FILE:2139)

    不!但是如果你把它们放在一起,它就很好用。

    (take-while (partial > 3) (sort [1 2 3 4 5 6]))

    =>(1 2)

    教训:您不能直接使用序列!它们是该过程的中间步骤。 当 REPL 尝试评估 (1 2 3 4 5 6) 时,它会看到一个 a 函数并抛出异常:

    (1 2 3 4 5 6) =>ClassCastException java.lang.Long 无法转换为 clojure.lang.IFn 用户/eval2263 (NO_SOURCE_FILE:2146)

    【讨论】:

    • 这是误导。在 REPL 中评估 (sort [1 2 3 4 5 6]) 后跟 (take-while (partial > 3) *1) 就可以了。如果只取序列的字符串表示,则会丢失类型信息。
    猜你喜欢
    • 1970-01-01
    • 2012-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多