Clojure宏将保存关联映射顺序

时间:2012-06-21 01:35:39

标签: casting macros clojure hashmap associative-array

前言,我在Windows 7(64位)上运行Java版本6(更新33),使用clooj作为我的IDE。我没有尝试在任何其他系统中重现我的问题。我对Clojure很有经验,但对Java却没有。

我试图解决的问题的全部内容很难描述,但它可以归结为:假设我想创建一个带有一个参数的宏,一个关联映射,并返回一个向量保存订单的地图元素。

=>(defmacro vectorize-a-map
    [associative-map]
    (vec associative-map))
=>#'ns/vectorize-a-map
=>(vectorize-a-map {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8}
=>[[:a 1] [:b 2] [:c 3] [:d 4] [:e 5] [:f 6] [:g 7] [:h 8]]

这样可行,但是在地图中添加了另一个元素,并且顺序混乱......

=>(vectorize-a-map {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9}
=>[[:a 1] [:c 3] [:b 2] [:f 6] [:g 7] [:d 4] [:e 5] [:i 9] [:h 8]]

我相信我已经发现了为什么会这样。似乎任何具有8个或更少元素的东西都被实例化为PersistentArrayMap,这正是我想要的,因为据我所知,这个类保留了顺序。但是,任何包含9个或更多元素的东西都会被实例化为PersistentHashMap,而不会保留顺序。

=>(type {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8}
=>clojure.lang.PersistentArrayMap
=>(type {:a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9}
=>clojure.lang.PersistentHashMap

我希望我的宏能够获取任何大小的关联映射,所以这是一个问题。我尝试过类型提示,解构绑定, 列表理解和unquote拼接,但都没有成功。为了绘制它,以下任何一个都不起作用:

(defmacro vectorize-a-map
  [^clojure.lang.PersistentArrayMap associative-map]
  (vec associative-map))

(defmacro vectorize-a-map
  [[& associative-map]]
  (vec associative-map))

(defmacro vectorize-a-map
  [associative-map]
  (vec
    (for [x associative-map]
      x)))

(defmacro vectorize-a-map
  [associative-map]
  `(vector ~@associative-map))

由于我提出这个玩具问题,我意识到我可以像这样编写我的宏,并完全避免这个问题:

=>(defmacro vectorize-kvs
    [& elements]
    (vec (map vec (partition 2 elements))))
=>#'ns/vectorize-kvs
=>(vectorize-kvs :a 1 :b 2 :c 3 :d 4 :e 5 :f 6 :g 7 :h 8 :i 9)
=>[[:a 1] [:b 2] [:c 3] [:d 4] [:e 5] [:f 6] [:g 7] [:h 8] [:i 9]]

但是,对于我试图解决的实际问题(我还没有进入),重要的是(虽然不是100%必要)宏可以采用关联映射。看起来我正在寻找如何在任何事情发生之前将参数转换为PersistentArrayMap。可能还有其他一些解决方案,我根本就没有考虑或意识到这一点。

我已经研究过最好的我已经知道如何并且还没有找到任何有用的东西。有没有人有任何想法/建议?

2 个答案:

答案 0 :(得分:5)

您可以使用array-map

制作地图
user> (map vec (array-map 1 2 3 4 5 6))
([1 2] [3 4] [5 6])

或使用更大的地图

user> (map vec (apply array-map (range 50)))
([0 1] [2 3] [4 5] [6 7] [8 9] [10 11] [12 13] [14 15] [16 17] [18 19] [20 21] [22 23] [24 25] [26 27] [28 29] [30 31] [32 33] [34 35] [36 37] [38 39] [40 41] [42 43] [44 45] [46 47] [48 49])

作为奖励,你可以避免使用宏,这很有用,因为宏不是一流的,不能很好地构成*

<小时/> 关于您在array map

上的文档中的第一条评论的说明
 Note that an array map will only maintain sort order when un-'modified'. 
 Subsequent assoc-ing will eventually cause it to 'become' a hash-map.

如果您发现自己取决于地图中按键的顺序,您可能需要考虑sorted-map是否能满足您的需求。它比array-map更好地扩展。在上面的例子中,输出是相同的:

(map vec (apply sorted-map (range 5000)))
[0 1] [2 3] ... [4998 4999]

*这是我的意见

<小时/> 编辑:

sorted-maparray-map

的时间比较
user> (time (dorun (map vec (apply sorted-map (range 500000)))))
"Elapsed time: 391.520491 msecs"
nil
user> (time (dorun (map vec (apply array-map (range 500000)))))
"Elapsed time: 674517.821669 msecs"

答案 1 :(得分:1)

所述问题不可解决。地图被定义为没有订单;您在array-map中看到的任何订单都是偶然的。如果您要求您的宏接收地图,您已经丢失了所需的信息。