Clojure

比較器指南

注意:本文件說明 Clojure 1.10 和 Java 8,但大部分其他版本也適用。

摘要

比較器是一個函數,它接收兩個參數xy,並傳回一個值,表示xy應該排序的相對順序。它可以是一個傳回整數的三向比較器,或是一個傳回布林值​​的二向比較器。請參閱下列注意事項,了解傳回值應為何,具體取決於xy的順序。

在 Clojure 中,您需要比較器來對值集合進行排序,或以所需的排序順序維護值集合,例如sorted-mapsorted-setpriority-map(也稱為優先佇列)。

預設比較器compare非常適合以遞增順序對數字進行排序,或以字典順序(即詞典順序)對字串、關鍵字或符號進行排序,以及其他一些情況。請參閱以下範例和更多詳細資訊。

如果compare無法滿足您的需求,您必須提供自己的比較器。以下建議的每一個都將在本文件中稍後詳細說明。

注意事項

  • 確保您的比較器基於您要比較的數值的全序。它應該能夠比較資料集中可能出現的任何一對數值,並確定哪個數值應先出現(或它們相等)。

  • 撰寫三向比較器或布林比較器

    • 三向比較器接收 2 個數值xy,並傳回一個 Java 32 位元int,如果x出現在y之前,則為負值;如果x出現在y之後,則為正值;如果它們相等,則為 0。如果您沒有理由偏好其他傳回值,請使用值 -1、0 和 1。

    • 布林比較器接收 2 個數值xy,並傳回 true(如果x出現在y之前)或 false(否則,包括xy相等的情況)。<>是良好的範例。<=>=則不是。效能注意事項:您的布林比較器可能會被呼叫兩次,以區分「出現在之後」或「相等」的情況。

  • 透過反轉您提供給現有比較器參數的順序,反轉排序。

  • 比較包含「排序鍵」且長度相等的 Clojure 向量,以在數值之間進行多欄位比較。

  • 在對集合進行排序之前,從您的資料中移除或取代「非數字」(##NaN)的出現,並避免將它們用作已排序集合中鍵的一部分。

注意事項

  • 請勿撰寫在數值相等時傳回 true 的布林比較器。此類比較器不一致。它將導致已排序集合行為不正確,並導致排序產生無法預測的順序。

  • 請勿對已排序的集合和地圖使用將兩個數值視為相等的比較器,除非您希望這兩個數值中最多只有一個出現在已排序的集合中。

  • 撰寫三向比較器時,除非你真的知道自己在做什麼,否則不要使用減法。

簡介

這裡我們描述函數 compare 提供的預設排序順序。之後我們提供其他比較器的範例,以及撰寫自己的比較器時應遵循的一些準則和避免的錯誤。

Clojure 的預設比較器

如果您未指定自己的比較器,則排序會由內建函數 compare 執行。compare 可用於許多類型的值,並以特定方式對其排序

  • 數字會按遞增數字順序排序,如果兩個數字在數字上相等,則傳回 0,即使 = 傳回 false。例外:儘管 (== ##NaN x) 對所有數字 x(甚至 ##NaN)都是 false,但 (compare ##NaN x) 對所有數字 x(包括 ##NaN)都是 0。

  • 字串會按字順(又稱字典順序)排序,依其表示為 UTF-16 編碼單元的順序。對於限制在 ASCII 子集的字串,這是字母順序(區分大小寫)。

  • 符號會先按其命名空間(如果有)排序,如果它們有相同的命名空間,則按其名稱排序。命名空間和名稱都按其字串表示形式進行字順比較。所有沒有命名空間的符號都會在任何有命名空間的符號之前排序。

  • 關鍵字的排序方式與符號相同,但如果您嘗試將關鍵字與符號比較,則會擲回例外。

  • 向量會依據元素數量由少到多排序,長度相同的向量則依據 字順 排序。

  • Clojure 參考會依據建立順序排序。

  • 所有實作 Comparable 介面的 Java 類型(例如字元、布林值、File、URI 和 UUID)會透過其 compareTo 方法進行比較。

  • nil:可以與上述所有值進行比較,且會被視為小於其他所有值。

如果給予 compare 兩個類型「差異過大」的值,則會擲回例外,例如它可以比較整數、長整數和浮點數,但無法比較字串與關鍵字或關鍵字與符號。它無法比較清單、序列、集合或映射。

以下使用 sortsorted-setsorted-map 的範例都使用預設的比較器。

user> (sort [22/7 2.71828 ##-Inf 1 55 3N])
(##-Inf 1 2.71828 3N 22/7 55)

user> (sorted-set "aardvark" "boo" "a" "Antelope" "bar")
#{"Antelope" "a" "aardvark" "bar" "boo"}

user> (sorted-set 'user/foo 'clojure.core/pprint 'bar 'clojure.core/apply 'user/zz)
#{bar clojure.core/apply clojure.core/pprint user/foo user/zz}

user> (sorted-map :map-key 10, :amp [3 2 1], :blammo "kaboom")
{:amp [3 2 1], :blammo "kaboom", :map-key 10}

user> (sort [[-8 2 5] [-5 -1 20] [1 2] [1 -5] [10000]])
([10000] [1 -5] [1 2] [-8 2 5] [-5 -1 20])

user> (import '(java.util UUID))
java.util.UUID

user> (sort [(UUID. 0xa 0) (UUID. 5 0x11) (UUID. 5 0xb)])
(#uuid "00000000-0000-0005-0000-00000000000b"
 #uuid "00000000-0000-0005-0000-000000000011"
 #uuid "00000000-0000-000a-0000-000000000000")

user> (sort [:ns2/kw1 :ns2/kw2 :ns1/kw2 :kw2 nil])
(nil :kw2 :ns1/kw2 :ns2/kw1 :ns2/kw2)

如果您使用 compare 呼叫不同的類型,則會擲回例外。上述任何數字類型都可以彼此比較,但無法與非數字類型比較。如果您對清單、集合、映射或上述未提及的任何其他類型使用 compare,也會擲回例外。如果您想要排序此類值,則必須實作自己的比較器。

現成的比較器

首先考慮使用其他人開發的經過良好測試的比較器,特別是如果它們很複雜。

一個完美的範例是針對不同語言的 Unicode 字串進行排序,並依據不同語言環境的特定順序。Java Collator 類別和 ICU(Unicode 的國際元件)提供此類別庫。

撰寫您自己的比較器

反向順序

若要以遞減順序排序數字,只需撰寫一個會以相反順序呼叫 compare 的比較器

user> (sort [4 2 3 1])
(1 2 3 4)

user> (defn reverse-cmp [a b]
        (compare b a))
#'user/reverse-cmp

user> (sort reverse-cmp [4 3 2 1])
(4 3 2 1)

此類簡短函式通常會使用 Clojure 的 #() 表示法撰寫,其中兩個引數依序為 %1 和 %2。

user> (sort #(compare %2 %1) [4 3 2 1])

reverse-cmp 也適用於 compare 可用的所有其他類型。

多欄位比較器

由於等長 Clojure 向量會以字典順序進行比較,因此可使用它們對應對應映射或記錄等值進行多欄位排序。這僅在欄位已按您希望的順序(或相反順序)由 compare 排序時才有效。

首先,我們將展示一種不比較向量的執行方式。

(def john1 {:name "John", :salary 35000.00, :company "Acme"})
(def mary  {:name "Mary", :salary 35000.00, :company "Mars Inc"})
(def john2 {:name "John", :salary 40000.00, :company "Venus Co"})
(def john3 {:name "John", :salary 30000.00, :company "Asteroids-R-Us"})
(def people [john1 mary john2 john3])

(defn by-salary-name-co [x y]
  ;; :salary values sorted in decreasing order because x and y
  ;; swapped in this compare.
  (let [c (compare (:salary y) (:salary x))]
    (if (not= c 0)
      c
      ;; :name and :company are sorted in increasing order
      (let [c (compare (:name x) (:name y))]
        (if (not= c 0)
          c
          (let [c (compare (:company x) (:company y))]
            c))))))

user> (pprint (sort by-salary-name-co people))
({:name "John", :salary 40000.0, :company "Venus Co"}
 {:name "John", :salary 35000.0, :company "Acme"}
 {:name "Mary", :salary 35000.0, :company "Mars Inc"}
 {:name "John", :salary 30000.0, :company "Asteroids-R-Us"})

以下是較短的方式,透過比較 Clojure 向量。它的行為與上述方式完全相同。請注意,與上述方式一樣,欄位 :salary 會按遞減順序排序,因為已交換 xy

(defn by-salary-name-co2 [x y]
    (compare [(:salary y) (:name x) (:company x)]
             [(:salary x) (:name y) (:company y)]))

user> (pprint (sort by-salary-name-co2 people))
({:name "John", :salary 40000.0, :company "Venus Co"}
 {:name "John", :salary 35000.0, :company "Acme"}
 {:name "Mary", :salary 35000.0, :company "Mars Inc"}
 {:name "John", :salary 30000.0, :company "Asteroids-R-Us"})

上述方式適用於從正在排序的值中計算成本較低的鍵值。如果鍵值計算成本較高,最好為每個值計算一次。請參閱 sort-by 文件中所述的「裝飾排序取消裝飾」技術。

布林比較器

Java 比較器都是 3 向的,表示它們會傳回負整數、0 或正整數,視第一個引數應視為小於、等於或大於第二個引數而定。

在 Clojure 中,您也可以使用布林比較器,如果第一個引數應在第二個引數之前,則傳回 true,否則傳回 false(即應在第二個引數之後,或與其相等)。函數 < 是個完美的範例,只要您只需要比較數字即可。> 可用於以遞減順序排序數字。在幕後,當此類 Clojure 函數 bool-cmp-fn 「作為比較器呼叫」時,Clojure 會執行類似以下的程式碼以傳回 int

(if (bool-cmp-fn x y)
  -1     ; x < y
  (if (bool-cmp-fn y x)  ; note the reversed argument order
    1    ; x > y
    0))  ; x = y

您可以透過呼叫任何 Clojure 函數的 compare 方法來查看這一點。以下是自訂版本 my-< 的範例,其中 < 會在呼叫時印出其引數,因此您可以看到其呼叫次數超過一次的情況

user> (defn my-< [a b]
        (println "(my-<" a b ") returns " (< a b))
        (< a b))
#'user/my-<

;; (. o (compare a b)) calls the method named compare for object
;; o, with arguments a and b.  In this case the object is the
;; Clojure function my-<
user> (. my-< (compare 1 2))
(my-< 1 2 ) returns  true
-1
user> (. my-< (compare 2 1))
(my-< 2 1 ) returns  false
(my-< 1 2 ) returns  true
1
user> (. my-< (compare 1 1))
(my-< 1 1 ) returns  false
(my-< 1 1 ) returns  false
0

;; Calling a Clojure function in the normal way uses its invoke
;; method, not compare.
user> (. my-< (invoke 2 1))
(my-< 2 1 ) returns  false
false

如果您想要所有詳細資訊,請參閱 Clojure 原始檔 src/jvm/clojure/lang/AFunction.java 方法 compare

比較器的通用規則

任何比較器,無論是 3 向還是布林,都應傳回與您要比較的值的 全序 一致的答案。

全序只是將所有值從最小到最大排序,其中某些值群組可以彼此相等。每對值都必須彼此可比較(即比較器不會傳回「我不知道如何比較它們」的答案)。

例如,你可以按照通常在數學中執行的步驟,將所有寫成 m/n 形式的分數(其中 m 和 n 為整數)從最小到最大排序。許多分數會相等,例如 1/2 = 2/4 = 3/6。實作該全序的比較器應視它們為相同。

3 向比較器 (cmp a b) 應在 a 在全序中在 b 之前、之後或被視為等於 b 時,分別傳回負值、正值或 0 int

布林比較器 (cmp a b) 應在 a 在全序中在 b 之前時傳回 true,或在 ab 之後或被視為等於 b 時傳回 false。亦即,它應像數字的 < 一樣運作。如後續說明,它不應像數字的 <= 一樣運作(請參閱「排序的集合和映射的比較器很容易出錯」一節)。

應避免的錯誤

小心在排序的集合中將「非數字」值視為比較值

Clojure 的預設比較器 compare 將「非數字」(##NaN)值視為等於所有其他數字。如果你對包含 ##NaN 出現的數字序列呼叫 sort,它可能會擲回例外狀況。

user> (sort [##NaN 5 13 ##NaN 3 7 12 ##NaN 8 4 2 20 6 9 ##NaN 50 83 19 -7 0 18 26 30 42 ##NaN 57 90 -8 -12 43 87 38])
Execution error (IllegalArgumentException) at java.util.TimSort/mergeHi (TimSort.java:899).
Comparison method violates its general contract!

即使它沒有擲回例外狀況,傳回的序列也可能未排序。這是因為 compare 沒有將 ##NaN 放入與其他數字的全序中,而比較器應如此才能讓 sort 正確運作

user> (sort [##NaN 10 5 13 ##NaN 3 7 12 ##NaN 8 4 2 20 6 9 ##NaN 50 83 19 -7])
(##NaN -7 2 3 4 5 6 7 8 10 12 13 ##NaN ##NaN 9 19 20 ##NaN 50 83)

由於 ##NaN 不等於任何其他值,因此你無法使用類似這樣的程式碼從數字序列中移除值

user> (remove #(= % ##NaN) [9 3 ##NaN 4])
(9 3 ##NaN 4)

你可以使用函數 NaN? 來判斷值是否為 ##NaN。函數 NaN? 已新增至 Clojure 版本 1.11.0。你可以對任何版本的 Clojure 使用 Java 方法 Double/isNaN

user> (remove NaN? [9 3 ##NaN 4])
(9 3 4)
user> (remove #(Double/isNaN %) [9 3 ##NaN 4])
(9 3 4)

排序的集合和映射的比較器很容易出錯

這與「比較器很容易出錯」的說法一樣準確,但當你對已排序的集合和映射使用錯誤的比較器時,通常會更明顯。如果你撰寫本節中這類錯誤的比較器,並使用它們呼叫 sort,通常不會出錯或幾乎不會出錯(儘管不一致的比較器也不適合排序)。使用已排序的集合和映射時,這些錯誤的比較器可能會導致值未新增到已排序的集合,或在搜尋時已新增但找不到。

假設你想要一個已排序的集合,其中包含兩個元素的向量,每個元素都是字串後接數字,例如 ["a" 5]。你希望集合依據數字排序,並允許多個具有相同數字但不同字串的向量。你的第一次嘗試可能是撰寫類似 by-2nd 的內容

(defn by-2nd [a b]
  (compare (second a) (second b)))

但看看當你嘗試新增多個具有相同數字的向量時會發生什麼事。

user> (sorted-set-by by-2nd ["a" 1] ["b" 1] ["c" 1])
#{["a" 1]}

集合中只有一個元素,因為 by-2nd 將這三個向量都視為相等。集合不應包含重複的元素,因此不會新增其他元素。

在這種情況下,一個常見的想法是使用基於 <= 而不是 < 的布林比較器函數

(defn by-2nd-<= [a b]
  (<= (second a) (second b)))

布林比較器 by-2nd-<= 似乎在建立集合的第一個步驟中運作正確,但在測試元素是否在集合中時失敗。

user> (def sset (sorted-set-by by-2nd-<= ["a" 1] ["b" 1] ["c" 1]))
#'user/sset
user> sset
#{["c" 1] ["b" 1] ["a" 1]}
user> (sset ["c" 1])
nil
user> (sset ["b" 1])
nil
user> (sset ["a" 1])
nil

這裡的問題是 by-2nd-<= 給出不一致的答案。如果你詢問 ["c" 1] 是否在 ["b" 1] 之前,它會傳回 true(Clojure 的布林轉整數比較器轉換會將其轉換為 -1)。如果你詢問 ["b" 1] 是否在 ["c" 1] 之前,它也會傳回 true(Clojure 會再次將其轉換為 -1)。如果你給它一個不一致的比較器,就不能合理地期望已排序資料結構的實作對其行為提供任何保證。

上面「多欄位比較器」中描述的技術為此範例提供了正確的比較器。一般來說,請小心只將值的某些部分彼此比較。考慮在你比較過所有感興趣的欄位後,要有某種打破平手的條件。

補充說明:如果你不希望集合中有具有相同數字的多個向量,則 by-2nd 是你應該使用的比較器。它會提供你想要的確切行為。(待辦事項:這裡有任何注意事項嗎?sorted-set 會因為任何原因使用 = 來比較元素,還是只會使用提供的比較器函數?)

小心在比較器中使用減法

如果第一個引數要被視為小於第二個引數,則 Java 比較器會傳回負整數值;如果第一個引數要被視為大於第二個引數,則會傳回正整數值;如果相等,則會傳回 0。

user> (compare 10 20)
-1
user> (compare 20 10)
1
user> (compare 20 20)
0

因此,你可能會想透過將一個數字值減去另一個數字值來撰寫比較器,如下所示。

user> (sort #(- %1 %2) [4 2 3 1])
(1 2 3 4)

雖然這在許多情況下都能運作,但在使用此技術之前,請三思(或四思)。使用明確的條件檢查並傳回 -1、0 或 1,或使用布林比較器,比較容易避免錯誤。

為什麼?Java 比較器必須傳回 32 位元 int 類型,因此當 Clojure 函數用作比較器,且傳回任何類型的數字時,該數字會使用 Java 方法 intValue 在幕後轉換為 int。如果您想要詳細資訊,請參閱 Clojure 原始檔 src/jvm/clojure/lang/AFunction.java 的方法 compare

對於比較浮點數和比率,這會導致相差不到 1 的數字被視為相等,因為 -1 到 1 之間的傳回值會被截斷為 int 0

;; This gives the correct answer
user> (sort #(- %1 %2) [10.0 9.0 8.0 7.0])
(7.0 8.0 9.0 10.0)

;; but this does not, because all values are treated as equal by
;; the bad comparator.
user> (sort #(- %1 %2) [1.0 0.9 0.8 0.7])
(1.0 0.9 0.8 0.7)

;; .intValue converts all values between -1.0 and 1.0 to 0
user> (map #(.intValue %) [-1.0 -0.99 -0.1 0.1 0.99 1.0])
(-1 0 0 0 0 1)

當您將整數值截斷為 32 位元 int(捨棄所有位元,只保留最低有效位元 32 位元)時,也會導致錯誤,因為整數值相差的量會在截斷後改變符號。使用減法作為比較器,會錯誤地比較約一半的長整數值對。

;; This looks good
user> (sort #(- %1 %2) [4 2 3 1])
(1 2 3 4)

;; What the heck?
user> (sort #(- %1 %2) [2147483650 2147483651 2147483652 4 2 3 1])
(3 4 2147483650 2147483651 2147483652 1 2)

user> [Integer/MIN_VALUE Integer/MAX_VALUE]
[-2147483648 2147483647]

;; How .intValue truncates a few selected values.  Note especially
;; the first and last ones.
user> (map #(.intValue %) [-2147483649 -2147483648 -1 0 1
                            2147483647  2147483648])
(2147483647 -2147483648 -1 0 1 2147483647 -2147483648)

Java 本身使用減法比較器來比較字串和字元等。這不會造成任何問題,因為將任意一對轉換為 int 的 16 位元字元相減的結果保證會符合 int 的範圍,而不會換行。如果您無法保證比較器會收到此類受限輸入,最好不要冒險。

可運作於不同類型之間的比較器

有時您可能希望依據某個鍵來排序一組值,但該鍵並非唯一。您希望具有相同鍵的值以某種可預測、可重複的順序排序,但您不太在意順序為何。

舉一個簡單的範例,您可能有一組向量,每個向量包含兩個元素,其中第一個元素永遠是字串,第二個元素永遠是數字。您希望依據數字值遞增排序,但您知道資料可能包含多個具有相同數字的向量。您希望以某種方式打破平手,並在多次排序中保持一致。

此案例很容易使用前面章節所述的多欄位比較器來實作。

(defn by-number-then-string [[a-str a-num] [b-str b-num]]
  (compare [a-num a-str]
           [b-num b-str]))

如果可以將整個向量值與 compare 進行比較,因為所有向量的長度都相等,而且每個對應元素的類型都可以使用 compare 進行比較,那麼您也可以執行此操作,使用整個向量值作為最終的平手終結者

(defn by-number-then-whatever [a-vec b-vec]
  (compare [(second a-vec) a-vec]
           [(second b-vec) b-vec]))

但是,如果向量的某些元素位置包含 compare 無法處理的不同類型,而且這些向量的第二個元素相同,則會擲回例外狀況

;; compare throws exception if you try to compare a string and a
;; keyword
user> (sort by-number-then-whatever [["a" 2] ["c" 3] [:b 2]])
Execution error (ClassCastException) at user/by-number-then-whatever (REPL:2).
class java.lang.String cannot be cast to class clojure.lang.Keyword

以下的 cc-cmp(「類別間比較」)在這種情況下可能很有用。它可以比較不同類型的值,並根據表示值類型的字串對其進行排序。它不只是 (class x),因為這樣數字(例如 IntegerLong)就不會以數字順序排序。函式庫 clj-arrangement 對您可能也有用。

;; comparison-class throws exceptions for some types that might be
;; useful to include.

(defn comparison-class [x]
  (cond (nil? x) ""
        ;; Lump all numbers together since Clojure's compare can
        ;; compare them all to each other sensibly.
        (number? x) "java.lang.Number"

        ;; sequential? includes lists, conses, vectors, and seqs of
        ;; just about any collection, although it is recommended not
        ;; to use this to compare seqs of unordered collections like
        ;; sets or maps (vectors should be OK).  This should be
        ;; everything we would want to compare using cmp-seq-lexi
        ;; below.  TBD: Does it leave anything out?  Include anything
        ;; it should not?
        (sequential? x) "clojure.lang.Sequential"

        (set? x) "clojure.lang.IPersistentSet"
        (map? x) "clojure.lang.IPersistentMap"
        (.isArray (class x)) "java.util.Arrays"

        ;; Comparable includes Boolean, Character, String, Clojure
        ;; refs, and many others.
        (instance? Comparable x) (.getName (class x))
        :else (throw
               (ex-info (format "cc-cmp does not implement comparison of values with class %s"
                                (.getName (class x)))
                        {:value x}))))

(defn cmp-seq-lexi
  [cmpf x y]
  (loop [x x
         y y]
    (if (seq x)
      (if (seq y)
        (let [c (cmpf (first x) (first y))]
          (if (zero? c)
            (recur (rest x) (rest y))
            c))
        ;; else we reached end of y first, so x > y
        1)
      (if (seq y)
        ;; we reached end of x first, so x < y
        -1
        ;; Sequences contain same elements.  x = y
        0))))

;; The same result can be obtained by calling cmp-seq-lexi on two
;; vectors, but cmp-vec-lexi should allocate less memory comparing
;; vectors.
(defn cmp-vec-lexi
  [cmpf x y]
  (let [x-len (count x)
        y-len (count y)
        len (min x-len y-len)]
    (loop [i 0]
      (if (== i len)
        ;; If all elements 0..(len-1) are same, shorter vector comes
        ;; first.
        (compare x-len y-len)
        (let [c (cmpf (x i) (y i))]
          (if (zero? c)
            (recur (inc i))
            c))))))

(defn cmp-array-lexi
  [cmpf x y]
  (let [x-len (alength x)
        y-len (alength y)
        len (min x-len y-len)]
    (loop [i 0]
      (if (== i len)
        ;; If all elements 0..(len-1) are same, shorter array comes
        ;; first.
        (compare x-len y-len)
        (let [c (cmpf (aget x i) (aget y i))]
          (if (zero? c)
            (recur (inc i))
            c))))))


(defn cc-cmp
  [x y]
  (let [x-cls (comparison-class x)
        y-cls (comparison-class y)
        c (compare x-cls y-cls)]
    (cond (not= c 0) c  ; different classes

          ;; Compare sets to each other as sequences, with elements in
          ;; sorted order.
          (= x-cls "clojure.lang.IPersistentSet")
          (cmp-seq-lexi cc-cmp (sort cc-cmp x) (sort cc-cmp y))

          ;; Compare maps to each other as sequences of [key val]
          ;; pairs, with pairs in order sorted by key.
          (= x-cls "clojure.lang.IPersistentMap")
          (cmp-seq-lexi cc-cmp
                        (sort-by key cc-cmp (seq x))
                        (sort-by key cc-cmp (seq y)))

          (= x-cls "java.util.Arrays")
          (cmp-array-lexi cc-cmp x y)

          ;; Make a special check for two vectors, since cmp-vec-lexi
          ;; should allocate less memory comparing them than
          ;; cmp-seq-lexi.  Both here and for comparing sequences, we
          ;; must use cc-cmp recursively on the elements, because if
          ;; we used compare we would lose the ability to compare
          ;; elements with different types.
          (and (vector? x) (vector? y)) (cmp-vec-lexi cc-cmp x y)

          ;; This will compare any two sequences, if they are not both
          ;; vectors, e.g. a vector and a list will be compared here.
          (= x-cls "clojure.lang.Sequential")
          (cmp-seq-lexi cc-cmp x y)

          :else (compare x y))))

以下是一個快速範例,說明 `cc-cmp` 比較不同類型值的運作方式。

user> (pprint (sort cc-cmp [true false nil Double/MAX_VALUE 10
                            Integer/MIN_VALUE :a "b" 'c (ref 5)
                            [5 4 3] '(5 4) (seq [5]) (cons 6 '(1))
                            #{1 2 3} #{2 1}
                            {:a 1, :b 2} {:a 1, :b -2}
                            (object-array [1 2 3 4])]))
(nil
 {:a 1, :b -2}
 {:a 1, :b 2}
 #{1 2}
 #{1 2 3}
 :a
 #<Ref@1493d9b3: 5>
 (5)
 (5 4)
 [5 4 3]
 (6 1)
 c
 false
 true
 -2147483648
 10
 1.7976931348623157E308
 "b"
 [1, 2, 3, 4])
nil

原始作者:Andy Fingerhut