排序
输出是基于提供的比较器对输入进行排序的副本
排序运算符最简单的应用是简单的调用,没有参数。
[3, 1, 4, 2].sort # => [1, 2, 3, 4]
(sort [3 1 4 2]) ;; => (1 2 3 4)
像这样没有参数的排序适用于知道如何排序的东西,并使用预定义的排序顺序,在这些情况下为升序。
更改排序顺序最简单的方法是将排序与反转组合。
[3, 1, 4, 2].sort.reverse # => [4, 3, 2, 1]
(reverse(sort [3 1 4 2])) ;; => (4 3 2 1)
但通常情况下,要么没有标准方法来排序您需要的内容,要么您希望以与标准不同的方式排序。最基本的方法是将比较器函数传递到排序中。比较器函数接受两个参数并指示排序顺序。
["10", "8", "300"].sort {|a,b| a.to_i <=> b.to_i} # => ["8", "10", "300"]
(sort #(< (read-string %1) (read-string %2)) ["8" "10" "300"]) ;; => ("8" "10" "300")
这里有一个明显的重复。几乎总是当您想要排序某样东西时,您都会在两个参数上调用相同的函数以获取排序键。在上面的示例中,此函数是字符串到数字的转换。因此,仅指定一次该函数是有意义的。
["10", "8", "300"].sort_by {|a| a.to_i} # => ["8", "10", "300"]
(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"]
但这显然很麻烦。更好的方法是将键提取函数列表传递给排序方法,它可以依次尝试它们。
["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"]
(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")