此页面描述了集合管道模式中的一个操作。有关更多上下文,请阅读

排序

输出是基于提供的比较器对输入进行排序的副本

排序运算符最简单的应用是简单的调用,没有参数。

ruby…
[3, 1, 4, 2].sort
# => [1, 2, 3, 4]
clojure…
(sort [3 1 4 2])
;; => (1 2 3 4)

像这样没有参数的排序适用于知道如何排序的东西,并使用预定义的排序顺序,在这些情况下为升序。

更改排序顺序最简单的方法是将排序与反转组合。

ruby…
[3, 1, 4, 2].sort.reverse
# => [4, 3, 2, 1]
clojure…
(reverse(sort [3 1 4 2]))
;; => (4 3 2 1)

但通常情况下,要么没有标准方法来排序您需要的内容,要么您希望以与标准不同的方式排序。最基本的方法是将比较器函数传递到排序中。比较器函数接受两个参数并指示排序顺序。

ruby…
["10", "8", "300"].sort {|a,b| a.to_i <=> b.to_i}
# => ["8", "10", "300"]
clojure…
(sort #(< (read-string %1) (read-string %2)) ["8" "10" "300"])
;; => ("8" "10" "300")

这里有一个明显的重复。几乎总是当您想要排序某样东西时,您都会在两个参数上调用相同的函数以获取排序键。在上面的示例中,此函数是字符串到数字的转换。因此,仅指定一次该函数是有意义的。

ruby…
["10", "8", "300"].sort_by {|a| a.to_i}
# => ["8", "10", "300"]
clojure…
(sort-by read-string ["8" "10" "300"])
;; => ("8" "10" "300")

为了更深入地思考这一点,我认为在指定排序时需要两个函数。一个函数是**比较器**,它比较两个值并产生排序顺序的指示,另一个函数是**键提取**函数,它在要排序的集合的每个元素上运行并提取传递给比较器的值。

比较器有两种风格。第一种是布尔比较器,例如“<”,它告诉您第一个参数是否小于第二个参数。为了获得排序,这种比较器可能需要在每个比较中都以两种方向使用,这样您就可以判断测试的成对值的之间的关系是小于、大于还是等于。(这就是为什么您不能使用“<=”的原因。)

第二种形式的比较器返回一个整数,其中负数表示小于,零表示等于,正数表示大于。许多语言使用的“星舰”运算符“<=>”就是一个很好的例子。这种三值比较器只需要对每个测试的成对值使用一次。

大多数平台都将有一个默认的比较器,它适用于各种基本类型,例如数字和字符串。

如果您想对其他东西进行排序,例如“S”、“M”、“L”和“XL”的 T 恤尺寸怎么办?您有两个途径,要么在 T 恤尺寸类型上实现默认的比较器,要么使用一个键提取函数,它将 T 恤尺寸转换为可以比较的东西(例如数字)。

当您需要按多个键排序时,粗略的方法是将所有键比较合并到一个比较器 lambda 中。

ruby…
["J.C. Bach", "J.S Bach", "C.P.E. Bach", "T Albinoni"].sort do |a,b|
   first = a.split.last <=> b.split.last
   (0 != first) ? first : a <=> b
end
# => ["T Albinoni", "C.P.E. Bach", "J.C. Bach", "J.S Bach"]

但这显然很麻烦。更好的方法是将键提取函数列表传递给排序方法,它可以依次尝试它们。

ruby…
["J.C. Bach", "J.S Bach", 
   "C.P.E. Bach", "T Albinoni"].
   sort_by{|s| [s.split.last, s]}
# => ["T Albinoni", "C.P.E. Bach", "J.C. Bach", "J.S Bach"]
clojure…
(sort-by 
   (juxt #(last (clojure.string/split % #"\s+")) identity) 
   ["J.C. Bach" "J.S Bach" "C.P.E. Bach" "T Albinoni"])
;; => ("T Albinoni" "C.P.E. Bach" "J.C. Bach" "J.S Bach")