Dipti
Dipti

Reputation: 85

How to order keys in Clojure map? Ideally sort them against a vector of keys

I found different solutions on how to order a map by values, but none around how I can get the key to appear in a certain order.

We know that the keys are auto sorted and don't appear in the way they were inserted, but could we force it afterwards somehow?

For example, given a map: (def my-map {:one 1 :two 2 :three 3 :four 4 :five 5}), I would like to change the order in which they're displayed.

The desired keys are in the vector (def sorted-keys-here [:four :three :five :two :one])

Such that, I would like the map to appear after the sort-fn applied as:

=> {:four 4 :three 3 :five 5 :two 2 :one 1}

The keys are always fixed, and I'd be doing this to a vector of maps using map, but I couldn't get this sort to be applied.

Any ideas?

(Edit: The problem isn't that the order isn't maintained. The actual problem is how to conform keys in a map to a specified order. The map would be coming from somewhere else and be going through transformations in the middle, so it's not possible to get it in the right order from the start anyway).

Upvotes: 2

Views: 932

Answers (3)

Terbiy
Terbiy

Reputation: 690

Please also consider the following approach of getting a map with keys sorted as described in a vector:

(defn get-map-sorted-by-order
  [some-map keys-order]
  (into (sorted-map-by (fn [first-key second-key]
                         (< (.indexOf keys-order first-key)
                            (.indexOf keys-order second-key))))
        some-map))

It uses the Java's .indexOf. However, I find it very convenient.

(get-map-sorted-by-order
 {:hello "Hello", :world "World", :comma-space ", ", :exclamation "!"}
 [:hello :comma-space :world :exclamation])

Leads to the following result:

{:hello "Hello", :comma-space ", ", :world "World", :exclamation "!"}

Upvotes: 0

Alan Thompson
Alan Thompson

Reputation: 29958

You need something like the following:

(ns tst.demo.core
  (:use tupelo.core tupelo.test))

(dotest 
  (let [keys-ordered  [:one :two :three :four :five :six]
        keys->idx     (zipmap keys-ordered (range))
        order-fn      (fn [x y] (< (keys->idx x) (keys->idx y))) ; a comparitor
        m             {:one 1 
                       :two 2
                       :three 3
                       :four 4
                       :five 5 
                       :six 6}

        m-sorted-norm (into (sorted-map) m)
        m-sorted-custom (into (sorted-map-by order-fn) m) ]

    (spyx-pretty m-sorted-norm)
    (spyx-pretty m-sorted-custom)
  ))

which will produce:

m-sorted-norm =>
{:five 5, :four 4, :one 1, :six 6, :three 3, :two 2}

m-sorted-custom =>
{:one 1, :two 2, :three 3, :four 4, :five 5, :six 6}

using my favorite template project.

However, please remember that sorted maps only ever make a difference in how they print at the terminal. For all other uses, there is no benefit to using a sorted map (and there can be much pain in maintaining them).

Please see also this list of documentation, especially the Clojure CheatSheet.

Update

If all you need is to export some maps to CSV, you may find the tupelo.csv library useful. The unit tests show the code in action.

Upvotes: 1

Shawn Zhang
Shawn Zhang

Reputation: 1852

I guess this will get you there

(def my-map {:one 1 :two 2 :three 3 :four 4 :five 5})
(map my-map [:four :three :five :two :one])  ;

you can put your generic vector of keys on the fly

(def vector-seq [:two :one])
(map my-map vector-seq)  ; this will return list
(mapv my-map vector-seq)  ; this will return vector

or you can do is "weight" the input vector

(let [my-map {:one 1 :two 2 :three 3 :four 4 :five 5}
      vector-seq [:five :three :two :four :one]
      weight (apply hash-map (interleave vector-seq (range (count vector-seq))))]
  (into 
        (sorted-map-by 
            (fn [key1 key2]
                (> (get weight key2)
                  (get weight key1))))
        my-map ))

this will produce a sorted-map with sequence of vector-seq

Upvotes: 3

Related Questions