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! 🚀