Skip to content

Commit 295fb60

Browse files
authored
Merge pull request #71 from bobisageek/collections-sort
add `sort` and `sort-by` to the collections operations section
2 parents df258f1 + bc8aad6 commit 295fb60

File tree

2 files changed

+81
-2
lines changed

2 files changed

+81
-2
lines changed

content/md/articles/language/collections_and_sequences.md

+80-1
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,7 @@ Do not confuse `empty?` with `empty`. This can be a source of great confusion:
790790

791791
### contains?
792792

793-
`contains` returns true if the provided *key* is present in a collection. `contains` is similar to `get` in that vectors treat the key as an index. `contains` will always return false for lists.
793+
`contains?` returns true if the provided *key* is present in a collection. `contains?` is similar to `get` in that vectors treat the key as an index. `contains?` does not work for lists.
794794

795795
```klipse-clojure
796796
(contains? {:a 1 :b 2 :c 3} :c)
@@ -996,6 +996,85 @@ returns falsey. This is because if the item is present in the set it is returned
996996
;; ⇒ (:things :someone nil false :pigeons)
997997
```
998998

999+
### sort, sort-by
1000+
1001+
`sort` and `sort-by` are flexible higher-order functions for sorting sequential collections like lists and vectors. Both take an optional `Comparator` which defaults to `compare`, and `sort-by` takes a function that transforms each value before comparison.
1002+
1003+
#### sort
1004+
1005+
`sort` using the default comparator can handle numbers...
1006+
```klipse-clojure
1007+
(sort [0.0 -1 1.3 nil 0.18 7])
1008+
;; ⇒ (nil -1 0.0 0.18 1.3 7)
1009+
```
1010+
... and strings
1011+
```klipse-clojure
1012+
(sort ["the case matters" "lexicographic ordering" "The case matters" nil "%%"])
1013+
;; ⇒ (nil "%%" "The case matters" "lexicographic ordering" "the case matters")
1014+
```
1015+
... and vectors whose elements are element-wise comparable
1016+
```klipse-clojure
1017+
(sort [[1 "banana"] [1 "apple"] [0 "grapefruit"]])
1018+
;; ⇒ ([0 "grapefruit"] [1 "apple"] [1 "banana"])
1019+
```
1020+
... but it can't "cross" types.
1021+
```klipse-clojure
1022+
; `compare` doesn't know how to compare Strings and numbers
1023+
(sort [5 1.0 "abc"])
1024+
;; Execution error (ClassCastException)...
1025+
;; class java.lang.Double cannot be cast to class java.lang.String
1026+
```
1027+
1028+
In order to do more complicated sorting, we can create our own `Comparator`. There's a wealth of information
1029+
about comparators in the [clojure.org comparators guide](https://www.clojure.org/guides/comparators), but for now, one possible comparator is a
1030+
function that takes two arguments and returns a negative, positive, or zero integer when the first argument is 'less than', 'greater than', or equal to (respectively) the second argument.
1031+
1032+
```klipse-clojure
1033+
(letfn [(strings-before-numbers
1034+
[x y]
1035+
(cond
1036+
; string is 'less than' number
1037+
(and (string? x) (number? y)) -1
1038+
; number is 'greater than' string
1039+
(and (number? x) (string? y)) 1
1040+
; otherwise we can use `compare`
1041+
:else (compare x y)))]
1042+
(sort strings-before-numbers [1 0.0 nil "abc"]))
1043+
;; ⇒ (nil "abc" 0.0 1)
1044+
```
1045+
1046+
A common way to reverse a sort is to `comp` the `-` function with a comparator that returns a number, which effectively
1047+
swaps 'greater than' and 'less than' returns.
1048+
1049+
```klipse-clojure
1050+
(sort (comp - compare) ["charlie" "delta" "alpha" "bravo"])
1051+
;; ⇒ ("delta" "charlie" "bravo" "alpha")
1052+
```
1053+
1054+
#### sort-by
1055+
1056+
`sort-by` takes a `keyfn` function and uses `sort` based on the result of appying `keyfn` to the values to be sorted.
1057+
It's typically a good candidate for sorting collections of maps/records/objects.
1058+
1059+
```klipse-clojure
1060+
(sort-by :last [{:first "Fred" :last "Mertz"}
1061+
{:first "Lucy" :last "Ricardo"}
1062+
{:first "Ricky" :last "Ricardo"}
1063+
{:first "Ethel" :last "Mertz"}])
1064+
;; ⇒ ({:first "Fred", :last "Mertz"}
1065+
;; {:first "Ethel", :last "Mertz"}
1066+
;; {:first "Lucy", :last "Ricardo"}
1067+
;; {:first "Ricky", :last "Ricardo"})
1068+
```
1069+
1070+
Because `compare` compares vectors element-wise, it's possible to use `juxt` to effectively sort by a few values without a custom comparator.
1071+
1072+
Sort the strings from shortest to longest, and then alphabetically (ignoring case):
1073+
```klipse-clojure
1074+
(sort-by (juxt count clojure.string/lower-case) ["Alpha" "bravo" "Charlie" "Delta" "echo"])
1075+
;; ⇒ ("echo" "Alpha" "bravo" "Delta" "Charlie")
1076+
```
1077+
9991078
### iterate
10001079

10011080
`iterate` takes a function and an initial value, returns the result of

deps.edn

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
ring/ring-devel {:mvn/version "1.12.1"}
44
compojure/compojure {:mvn/version "1.6.2"}
55
cryogen-flexmark/cryogen-flexmark {:mvn/version "0.1.5"}
6-
cryogen-core/cryogen-core {:mvn/version "0.4.4"}}
6+
cryogen-core/cryogen-core {:mvn/version "0.4.6"}}
77
:aliases {;; Run with `clojure -M:build`
88
:build {:main-opts ["-m" "cryogen.core"]}
99
;; Start a server serving the blog: `clojure -X:serve`

0 commit comments

Comments
 (0)