无序对的良好Clojure表示?

时间:2013-11-05 17:58:09

标签: clojure set

我正在创建无序的数据元素对。 @Chouser对此question的评论表示,每个节点使用32个子节点实现散列集,而每个节点使用2个子节点实现排序集。这是否意味着如果我使用排序集而不是散列集来实现它们,我的对将占用更少的空间(假设数据元素是可比较的,即可以排序)? (我怀疑它在实践中对我来说很重要。我只会有数百个这样的对,并且在一个双元素数据结构中进行查找,甚至在向量或列表中进行顺序查找,应该很快。但我'好奇。)

4 个答案:

答案 0 :(得分:1)

当明确地查看列表的前两个元素时,使用Clojure的内置集我运行它一千万次时没有看到显着差异:

user> (defn my-lookup [key pair] 
         (condp = key 
               (first pair) true 
               (second pair) true false))
#'user/my-lookup

user> (time (let [data `(1 2)] 
              (dotimes [x 10000000] (my-lookup (rand-nth [1 2]) data ))))
"Elapsed time: 906.408176 msecs"
nil

user> (time (let [data #{1 2}] 
               (dotimes [x 10000000] (contains? data (rand-nth [1 2])))))
"Elapsed time: 1125.992105 msecs"
nil

当然,像这样的微基准测试本质上存在缺陷,很难真正做好,所以不要试图用它来表明一个比另一个好。我只打算证明它们非常相似。

答案 1 :(得分:1)

如果我正在使用无序对进行操作,我通常喜欢使用 map ,因为这样可以轻松查找其他元素。例如,如果我的对是 [2 7] ,那么我将使用{2 7, 7 2},我可以执行({2 7, 7 2} 2),这会给我7

至于空间,PersistentArrayMap实施实际上非常注重空间。如果您查看源代码(请参阅上一个链接),您将看到它分配了保存所有键/值对所需的确切大小的Object[]。我认为这被用作所有不超过8个键/值对的地图的默认地图类型。

这里唯一的问题是你需要注意重复键。 {2 2, 2 2}会导致异常。您可以通过执行以下操作来解决此问题:(merge {2 2} {2 2}),即(merge {a b} {b a}),其中ab可能具有相同的值。

这是我的副本中的一小段代码:

user=> (def a (array-map 1 2 3 4))
#'user/a
user=> (type a)
clojure.lang.PersistentArrayMap
user=> (.count a) ; count simply returns array.length/2 of the internal Object[]
2

请注意,我在上面明确调用了array-map。这与我前一段时间与地图文字相关的问题以及代理中的def相关:Why does binding affect the type of my map?

答案 2 :(得分:1)

这应该是一个评论,但我的声誉太短,太急于分享信息。 如果你担心性能,Zachary Tellman的clj-tuple可能比普通列表/向量快2-3倍,如ztellman / clj-tuple所述。

答案 3 :(得分:0)

我现在不打算对不同的对表示进行基准测试,但是@ArthurUlfeldt的答案和@DaoWen让我这样做了。以下是使用标准bench宏的结果。源代码如下。总而言之,正如预期的那样,我测试的七种表示之间没有大的差异。但是,最快的阵列映射和散列映射以及其他时间之间存在差距。这与DaoWen和Arthur Ulfeldt的言论一致。

平均执行时间(以秒为单位),从最快到最慢(MacBook Pro,2.3GHz Intel Core i7):

array-map:5.602099

hash-map:5.787275

vector:6.605547

排序集:6.657676

hash-set:6.746504

列表:6.948222

编辑:我在下面添加了一个test-control的运行,它只执行所有其他不同测试的共同点。 test-control平均花费了5.571284秒。似乎-map表示与其他表示之间存在比我想象的更大的差异:访问哈希映射或两个条目的数组映射基本上是即时的(在我的计算机上,操作系统,Java,其他表示需要大约一秒钟进行1000万次迭代。考虑到它的10M迭代,这意味着那些操作仍然几乎瞬间完成。 (我的猜测是test-arraymaptest-control更快的事实是由于计算机背景中发生的其他事情造成的噪音。或者它可能与编译的特性有关。)

(一个警告:我忘了提到我从标准中得到警告:“JVM参数TieredStopAtLevel = 1处于活动状态,并且可能导致意外结果,因为JIT C2编译器可能无法激活。”我相信这意味着Leiningen使用命令行选项启动Java,该选项面向-server JIT编译器,但是使用默认的-client JIT编译器运行。所以警告说“你认为你正在运行-server,但是你“不是,所以不要指望-server行为。”使用-server运行可能会改变上面给出的时间。)

(use 'criterium.core)

;; based on Arthur Ulfedt's answer:
(defn pairlist-contains? [key pair] 
  (condp = key 
    (first pair) true 
    (second pair) true 
    false))

(defn pairvec-contains? [key pair] 
  (condp = key 
    (pair 0) true 
    (pair 1) true
    false))

(def ntimes 10000000)

;; Test how long it takes to do what's common to all of the other tests
(defn test-control []
    (print "=============================\ntest-control:\n")
    (bench
      (dotimes [_ ntimes]
        (def _ (rand-nth [:a :b])))))

(defn test-list []
  (let [data '(:a :b)] 
    (print "=============================\ntest-list:\n")
    (bench
      (dotimes [_ ntimes]
        (def _ (pairlist-contains? (rand-nth [:a :b]) data))))))

(defn test-vec []
  (let [data [:a :b]] 
    (print "=============================\ntest-vec:\n")
    (bench
      (dotimes [_ ntimes]
        (def _ (pairvec-contains? (rand-nth [:a :b]) data))))))

(defn test-hashset []
  (let [data (hash-set :a :b)]
    (print "=============================\ntest-hashset:\n")
    (bench
      (dotimes [_ ntimes]
        (def _ (contains? data (rand-nth [:a :b])))))))

(defn test-sortedset []
  (let [data (sorted-set :a :b)]
    (print "=============================\ntest-sortedset:\n")
    (bench
      (dotimes [_ ntimes]
        (def _ (contains? data (rand-nth [:a :b])))))))

(defn test-hashmap []
  (let [data (hash-map :a :a :b :b)]
    (print "=============================\ntest-hashmap:\n")
    (bench
      (dotimes [_ ntimes]
        (def _ (contains? data (rand-nth [:a :b])))))))

(defn test-arraymap []
  (let [data (array-map :a :a :b :b)]
    (print "=============================\ntest-arraymap:\n")
    (bench
      (dotimes [_ ntimes]
        (def _ (contains? data (rand-nth [:a :b])))))))

(defn test-all []
  (test-control)
  (test-list)
  (test-vec)
  (test-hashset)
  (test-sortedset)
  (test-hashmap)
  (test-arraymap))