Clojure: sorting
Sorting collections of items is something that comes up frequently in software development. This post covers the multitude of ways you can sort things in Clojure.
Sorting numbers
To sort numbers we use the sort
function.
(sort [9 3 2 8 1 9])
=> (1 2 3 8 9 9)
To reverse the sort order we pass the >
function into the sort
function.
(sort > [9 3 2 8 1 9])
=> (9 9 8 3 2 1)
Sorting strings and dates
Sorting strings and dates in ascending order is exactly the same as sorting numbers.
(sort ["b" "a" "f" "c" "x"])
=> ("a" "b" "c" "f" "x")
(sort [#inst "2019-02-03"
#inst "2019-05-03"
#inst "2018-09-03"])
=> (#inst "2018-09-03T00:00:00.000-00:00"
#inst "2019-02-03T00:00:00.000-00:00"
#inst "2019-05-03T00:00:00.000-00:00")
However, passing the >
function into the sort
function gives us an error.
(sort > ["b" "a" "f" "c" "x"])
=>
Execution error (ClassCastException) at java.util.TimSort/countRunAndMakeAscending (TimSort.java:355).
java.lang.String cannot be cast to java.lang.Number
(sort > [#inst "2019-02-03"
#inst "2019-05-03"
#inst "2018-09-03"])
=>
Execution error (ClassCastException) at java.util.TimSort/countRunAndMakeAscending (TimSort.java:355).
java.util.Date cannot be cast to java.lang.Number
To reverse the sort we need to make our own comparator. The easiest way to do this is with the compare
function. By changing the argument order to the compare function we can change the order of the sort.
(defn desc [a b]
(compare b a))
(sort desc ["b" "a" "f" "c" "x"])
=> ("x" "f" "c" "b" "a")
(sort desc [#inst "2019-02-03"
#inst "2019-05-03"
#inst "2018-09-03"])
=> (#inst "2019-05-03T00:00:00.000-00:00"
#inst "2019-02-03T00:00:00.000-00:00"
#inst "2018-09-03T00:00:00.000-00:00")
Sorting with nil values
The sort
function moves nil
values to the front.
(sort [2 6 nil 7 4])
=> (nil 2 4 6 7)
We can reverse this sort with the desc
comparator we defined earlier. This, will move nil
to the back but also reverse the sort.
(sort desc [2 6 nil 7 4])
=> (7 6 4 2 nil)
If we want nil
values to be at the back without changing the sort order, we can use the juxt
, nil?
and identity
functions (for more on juxt
check out this post).
(sort-by (juxt nil? identity) [2 6 nil 7 6])
=> (2 6 6 7 nil)
If we want to reverse the order but keep nil
values at the front, we can pass in the desc
comparator we defined earlier.
(sort-by (juxt nil? identity) desc [2 6 nil 7 6])
=> (nil 7 6 6 2)
Sorting maps by key
To sort maps by key we use the sort-by
function, passing in the key we want to sort by as an argument.
(sort-by :a [{:a 2 :b 1} {:a 3 :b 1} {:a 1 :b 2}])
=> ({:a 1, :b 2} {:a 2, :b 1} {:a 3, :b 1})
Like sort
, the sort-by
function can also take a comparator to change the order of the sort.
(sort-by :a > [{:a 2 :b 1} {:a 3 :b 1} {:a 1 :b 2}])
=> ({:a 3, :b 1} {:a 2, :b 1} {:a 1, :b 2})
You can also use comp
and -
to change the sort order.
(sort-by (comp - :a) [{:a 2 :b 1} {:a 3 :b 1} {:a 1 :b 2}])
=> ({:a 3, :b 1} {:a 2, :b 1} {:a 1, :b 2})
Secondary sort
Secondary sorts can be achieved using the juxt
function.
(sort-by (juxt :a :b) [{:a 2 :b 1} {:a 1 :b 3} {:a 1 :b 2}])
=> ({:a 1, :b 2} {:a 1, :b 3} {:a 2, :b 1})
Sorting by count
If you want to sort by count, just pass the count function to sort-by
(note: we pass it as the key function not the comparator).
(sort-by count ["car" "airplane" "bike"])
=> ("car" "bike" "airplane")
Stable sort
Both sort
and sort-by
are stable, meaning equal elements will not be reordered.
clojure.core/sort
([coll] [comp coll])
Returns a sorted sequence of the items in coll. If no comparator is
supplied, uses compare. comparator must implement
java.util.Comparator. Guaranteed to be stable: equal elements will
not be reordered. If coll is a Java array, it will be modified. To
avoid this, sort a copy of the array.
clojure.core/sort-by
([keyfn coll] [keyfn comp coll])
Returns a sorted sequence of the items in coll, where the sort
order is determined by comparing (keyfn item). If no comparator is
supplied, uses compare. comparator must implement
java.util.Comparator. Guaranteed to be stable: equal elements will
not be reordered. If coll is a Java array, it will be modified. To
avoid this, sort a copy of the array.
This is useful as it means it doesn't matter what order you apply your sorts to a collection. You can sort by the secondary sort and then sort by your primary sort and you won't lose the secondary sort order.
(sort-by :b [{:a 2 :b 1} {:a 1 :b 3} {:a 1 :b 2}])
=> ({:a 2, :b 1} {:a 1, :b 2} {:a 1, :b 3})
(sort-by :a [{:a 2, :b 1} {:a 1, :b 2} {:a 1, :b 3}])
=> ({:a 1, :b 2} {:a 1, :b 3} {:a 2, :b 1})
Hopefully this covers most of your day to day sorting needs.