【问题标题】:Clojure macro that will conserve associative map order将保留关联映射顺序的 Clojure 宏
【发布时间】:2012-06-23 05:35:54
【问题描述】:

首先,我在 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

我希望我的宏能够获取任何大小的关联地图,所以这是个问题。我尝试过类型提示、解构绑定、for 列表理解和取消引用拼接,均未成功。要画出来,以下都行不通:

(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 。可能还有其他一些我根本没有考虑或不知道的解决方案。

我已经研究了我所知道的最好的方法,但还没有发现任何有用的东西。有人有什么想法/建议吗?

【问题讨论】:

  • 宏俱乐部的第一条规则是“不要写宏”……(你能猜到第二条吗)
  • 我想第二条规则是:“闭嘴。说真的,不要写宏。”

标签: casting macros clojure hashmap associative-array


【解决方案1】:

上述问题无法解决。地图被定义为没有顺序;您在array-map 中看到的任何订单都是巧合。如果您要求您的宏接收地图,那么您已经丢失了您想要的信息。

【讨论】:

  • " 通常希望有一个维护键顺序的映射。数组映射就是这样的映射"clojure.org/data_structures#Data%20Structures-ArrayMaps
  • 我不同意它是可取的断言,也不同意它对代码转换有用的暗示。例如,{25 (println 25), 26 (println 26), 27 (println 27), 28 (println 28), 29 (println 29), 30 (println 30), 31 (println 31), 32 (println 32), 33 (println 33), 34 (println 34)} 读取为哈希映射,因此以与词法不同的顺序打印;保持它的顺序是不可能的,所以在任何情况下都不应该依赖它。
【解决方案2】:

您可以使用 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 文档中您的第一条评论的注释
 请注意,数组映射仅在未“修改”时才会保持排序顺序。
 随后的关联最终会导致它“成为”一个哈希映射。

如果您发现自己取决于地图中键的顺序,您可能需要考虑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"

【讨论】:

  • 感谢您的回复。可悲的是,我必须违反宏俱乐部的第一条规则,因为我制作的“函数”必须接收未评估的代码。我不会掉以轻心。我确实已经尝试了数组映射函数(以及排序映射),但它并没有真正解决我的问题,因为我的文字代码看起来像(数组映射:a 1 :b 2 ... etc.) 而不是 {:a 1 :b 2 ... etc.}。事实上,我可以做到这一点,但是当我要使用我的宏时,我不一定将 {} 用于地图,但可能必须使用(数组地图等),我宁愿避免这种情况。
猜你喜欢
  • 1970-01-01
  • 2016-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-18
  • 2010-12-26
  • 1970-01-01
相关资源
最近更新 更多