Fixed Order Sorting in Clojure

Here’s another tidy bit of Clojure that makes me happy…

Of course it’s easy to sort items in the natural order:

(sort [1 3 4 2]) ; => (1 2 3 4)

Or via a mapping to elements that sort in a natural order:

(sort-by count ["xx" "xxx" "x"]) ; => ("x" "xx" "xxx")

But how do we sort in a user-defined, fixed order?

For example, let’s say we have projects, and each project has a status which is one of:

(def statuses [:draft :in-progress :completed :archived])

(Note that those statuses are ordered in a logical, temporal progression.)

Here’s a quick helper function:

(defn fixed-order [xs]
  (let [pos (zipmap xs (range))]
    (fn [x y]
      (compare (pos x) (pos y)))))

To sort statuses, we simply do:

(def status-order (fixed-order statuses))

(sort status-order [:archived :in-progress :draft :completed])
;; =>
(:draft :in-progress :completed :archived)

Now let’s say we want to display all of our projects, sorted by their status:

(def projects
  [{:name "Paint the house", :status :completed}
   {:name "Update the blog", :status :in-progress}
   {:name "Fly to the moon"  :status :draft}
   {:name "Master Clojure"   :status :in-progress}])

(sort-by :status status-order projects)
;; =>
({:name "Fly to the moon", :status :draft}
 {:name "Update the blog", :status :in-progress}
 {:name "Master Clojure",  :status :in-progress}
 {:name "Paint the house", :status :completed})

Maybe our app should group those sorted in the right order:

(->> projects
     (group-by :status)
     (into (sorted-map-by status-order)))
;; =>
{:draft       [{:name "Fly to the moon", :status :draft}]
 :in-progress [{:name "Update the blog", :status :in-progress}
               {:name "Master Clojure",  :status :in-progress}]
 :completed   [{:name "Paint the house", :status :completed}]}

Ship it! 🚀