vettipayyan
vettipayyan

Reputation: 3308

Clojure: into {} doesn't preserve sort order

I have a nested map which is structured like this (Clojurescript):

{"6841"
 {"primaryTitle" "First name",
  "secondaryTitle" "last name"},
 "7944"
 {"primaryTitle" "Test 2 first name",
  "secondaryTitle" "Test 2 last name"}}

I then proceed to sort the map with the keys inside the nested map, like this:

(defn compare-title [x y]
  (compare [(get (second x) "primaryTitle") (get (second x) "secondaryTitle")]
           [(get (second y) "primaryTitle") (get (second y) "secondaryTitle")]))

(sort compare-title @loaded-assets)

So far the sorting works fine, but since sort function return the data structure like this:

    ["6841"
     {"primaryTitle" "First name",
      "secondaryTitle" "last name"}],
    ["7944"
     {"primaryTitle" "Test 2 first name",
      "secondaryTitle" "Test 2 last name"}]}

I have to use into {} to transform the map back to the initial structure:

(into {} (sort compare-title my-map))

But this completely reverses the sorting which is made by sort. I've tried to replace into {} with:

  1. flatten (which transforms this into list)
  2. apply hash-map (which behaves similar to into {})
  3. reduce hash-map (which preserves the order, but deeply nests each map into each other)

So, is it possible to sort the map while preserving the structure? Or how to transform back to the above original structure while preserving the sorted structure returned by sort?

Upvotes: 1

Views: 483

Answers (2)

rmcv
rmcv

Reputation: 1976

You can use priority-map

(use 'clojure.data.priority-map)

(defn compare-title [x y]
  (compare [(get x "primaryTitle") (get x "secondaryTitle")]
           [(get y "primaryTitle") (get y "secondaryTitle")]))

(apply priority-map-by compare-title
       ["7944"
        {"primaryTitle"   "Test 2 first name"
         "secondaryTitle" "Test 2 last name"}
        "6841"
        {"primaryTitle"   "First name"
         "secondaryTitle" "last name"}])

;; => {"6841" {"primaryTitle" "First name", "secondaryTitle" "last name"}, "7944" {"primaryTitle" "Test 2 first name", "secondaryTitle" "Test 2 last name"}}

Upvotes: 6

Alan Thompson
Alan Thompson

Reputation: 29958

As already mentioned, maps cannot be sorted by values. They can be sorted by keys. Here is the easiest way with some helper functions:

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

(def data
  {"6841"
   {"primaryTitle"   "First name",
    "secondaryTitle" "last name"},
   "7944"
   {"primaryTitle"   "Test 2 first name",
    "secondaryTitle" "Test 2 last name"}})

(def data-sorted ; maybe use `postwalk` instead
  (->sorted-map
    (map-vals data ->sorted-map)))

(dotest
  (is= data-sorted
    {"6841" {"primaryTitle"   "First name",
             "secondaryTitle" "last name"},
     "7944" {"primaryTitle"   "Test 2 first name",
             "secondaryTitle" "Test 2 last name"}}))

If you want to sort by the primary/secondary title, put those 2 items in a "sort key", then sort based on that:

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

(def all-data {"7944" {"primaryTitle"   "Test 2 first name"
                       "secondaryTitle" "Test 2 last name"}
               "6841" {"primaryTitle"   "First name"
                       "secondaryTitle" "last name"}})

(dotest
  (let [data-keyed  (forv [entry all-data]
                      (let [[id title-map] entry]
                        {:sort-key [(grab "primaryTitle" title-map)
                                    (grab "secondaryTitle" title-map)]
                         :id       id}))
        data-sorted (vec (sort-by :sort-key data-keyed))]

    (is= (spy-pretty data-keyed)
      [{:sort-key ["Test 2 first name" "Test 2 last name"] :id "7944"}
       {:sort-key ["First name" "last name"] :id "6841"}])

    (is= (spy-pretty data-sorted)
      [{:sort-key ["First name" "last name"] :id "6841"}
       {:sort-key ["Test 2 first name" "Test 2 last name"] :id "7944"}])))

Upvotes: 1

Related Questions