clojure - ClojureScript zipmap tricks me or what?


Keywords:clojure 


Question: 

I use Clojurescript to develop webbrowser-games. (Actually a friend of mine teaches me, we started only a few weeks ago).

I wanted to generate a map in which keys are vectors and values are numbers. For e.g.: {[0 0] 0, [0 1] 1, [0 2] 2, ...}.

I used this formula:

 (defn oxo [x y]
   (zipmap (map vec (combi/cartesian-product (range 0 x) (range 0 y))) (range (* x y))))

(where combi/ refers to clojure.math.combinatorics).

When it generates the map, key-value pairs are ok, but they are in a random order, like:

{[0 1] 1, [6 8] 68, [6 9] 69, [5 7] 57, ...}

What went wrong after using zipmap and how can i fix it?


3 Answers: 

Clojure maps aren't guaranteed to have ordered/sorted keys. If you want to ensure the keys are sorted, use a sorted-map:

(into (sorted-map) (oxo 10 10))
=>
{[0 0] 0,
 [0 1] 1,
 [0 2] 2,
 [0 3] 3,
 [0 4] 4,
 [0 5] 5,
...

If your map has fewer than 9 keys then insertion order is preserved because the underlying data structure is different depending on the number of keys:

  1. clojure.lang.PersistentArrayMap for <9 keys
  2. clojure.lang.PersistentHashMap otherwise.

array-map produces a clojure.lang.PersistentArrayMap and sorted-map produces a clojure.lang.PersistentTreeMap. Note that associng onto an array map may produce a hash map, but associng on to a sorted map still produces a sorted map.

 

zipmap produces a hash-map where order of the keys is not guaranteed. If you want ordered keys you can use either sorted-map or array-map.

 

As far as my knowledge goes, you should not rely on Map/Hash/Dictionary for ordering in any languages.

If the order is important but you don't need O(1) lookup performance of the map, a vector of vector pairs is a good option for you.

(defn oxo [x y]
  (mapv vector (map vec (combi/cartesian-product (range 0 x) (range 0 y))) (range (* x y))))

You will get something like this.

=> (oxo 10 10)
[[[0 0] 0] [[0 1] 1] [[0 2] 2] [[0 3] 3] [[0 4] 4] [[0 5] 5] ...]