算法实现/排序/施瓦茨变换
Perl 的紧凑列表操作函数可以在单个语句中执行整个变换,尽管它必须反向编写
@sorted_array =
map { $_->[0] } # extract original list elements
sort { $a->[1] <=> $b->[1] } # sort list by keys
map { [$_, -M $_] } # pair up list elements with keys
@files_array;
这是一个完整的施瓦茨变换,展示了基于大小和修改时间记忆键的多级排序。
my @sorted_files =
map $_->[0], # extract original name
sort {
$a->[1] <=> $b->[1] # sort first numerically by size (smallest first)
or $b->[2] <=> $a->[2] # then numerically descending by modtime age (oldest first)
or $a->[0] cmp $b->[0] # then stringwise by original name
}
map [$_, -s $_, -M $_], # compute tuples of name, size, modtime
glob "*"; # of all files in the directory
Python 程序员在排序操作可能很昂贵的排序中使用变换。
在这个例子中,f(x) 返回一个适合用于排序的键。例如,它可能返回 x 的长度,或者它可能根据 x 的值执行数据库查找。
# python2.4
new_list = sorted(old_list, key=f)
如果需要二级和更高级的排序键,键函数可以返回一个元组值,利用元组首先按第一个键排序,然后按第二个键排序(如果第一个键相同),依此类推。如果键的默认排序不够,可以使用一个比较函数,并使用一个额外的关键字参数 cmp,例如
# python2.4; removed in python3.0
new_list = sorted(old_list, key=f, cmp=lambda a,b:cmp(a[0],b[0]) or cmp(b[1],a[1]))
注意,键函数 f 应该执行昂贵的操作,因为它只对每个键调用一次,而比较函数在排序算法需要比较两个元素时被调用。在 Python 3.0 中不能再使用比较函数;只可以用键。函数 sorted 是 Python 2.4 中的新功能,在此之前,只有方法 sort 可用,需要多个语句。键函数也是 Python 2.4 中的新功能,因此在 Python 2.3 中,必须以类似以下方式编写变换,利用元组的第一个元素将首先用于比较
# python2.3
new_list = [(f(x), x) for x in old_list]
new_list.sort()
new_list = [x[1] for x in new_list]
如果需要,也可以在此处为排序提供比较函数(Python 2.4;在 Python 3.0 中删除)。
以下是与 Perl 习惯用法类似的版本(除了结尾处的额外括号),再次利用元组的排序,将函数结果放在首位
new_list = map(lambda x: x[1],
sorted(
map(lambda x: (f(x), x),
old_list)))
zip
内置函数可以用来代替内联 lambda 函数,给出
new_list = zip(*
sorted(
zip(map(f, old_list),
old_list)))[1]
Ruby 程序员有自己的快捷方式。
new_list = old_list.sort_by {|file| file.mtime }
然而,这不是完整的施瓦茨变换,因为记忆键的比较是固定的。(虽然有一个解决方法。)
真正的施瓦茨变换还详细说明了如何比较各种记忆键(作为字符串,作为数字),包括惰性逻辑,它可以避免在二级和三级键中进行比较,如果主要键的区分就足够了。
sorted_files = Dir["*"]. # Get all files
collect{|f| [f, test(?s, f), test(?M, f)]}. # compute tuples of name, size, modtime
sort {|a, b| # sort
a[1] <=> b[1] or # -- by increasing size
b[2] <=> a[2] or # -- by age descending
a[0] <=> b[0] # -- by name
}.collect{|a| a[0]} # extract original name
注意,由于 collect、sort 等都是方法调用,因此操作从左到右读取,而不是像原始 Perl 中那样从右到左读取。
现在,让我们来看一下使快捷方法能够实现完整变换的所有功能的技巧。这个技巧是,在 Ruby 中,对象知道如何排序自身,并且数组按字典顺序排序(即通过比较第一个值、然后比较第二个值等等)。这意味着可以构造一些值,这些值将通过相当复杂的逻辑进行排序自身。
# Here is the helper class.
class InReverse
include Comparable
attr :value
def initialize (value)
@value = value
end
def <=> (other)
other.value <=> @value
end
end
# And now we can do the complex transform using sort_by.
sorted_files = Dir["*"].sort_by{|f| [test(?s, f), InReverse.new(test(?M, f)), f]}
这是 Haskell 中按元素的变换版本进行比较的惯用方式
import Data.List (sortBy)
import Data.Ord (comparing)
sortOn f = sortBy (comparing f)
这里,“比较”函数接受一个函数,并返回一个基于比较函数应用结果的比较函数。它是这样定义的
comparing f x y = compare (f x) (f y)
这里有一个完全模仿传统 Perl 习惯用法的版本
import Data.List (sortBy)
import Data.Ord (comparing)
sortOn' f = map fst . sortBy (comparing snd) . map (\x -> (x, f x))
但是,我们可以利用元组首先按第一个元素排序,然后按第二个元素排序,依此类推,将函数结果放在第一个元素位置,然后使用默认比较器
import Data.List (sort)
sortOn'' f = map snd . sort . map (\x -> (f x, x))
由于元组比较要求两种元素类型都是Ord的实例,然而,最后这个实现有一个微妙的不同类型
sortOn, sortOn' :: (Ord b) => (a -> b) -> [a] -> [a]
sortOn'' :: (Ord a, Ord b) => (a -> b) -> [a] -> [a]
这可能是 OCaml 中最简单的方法
let comparing f x y = compare (f x) (f y)
let newList = List.sort (comparing f) oldList
这里有一个完全模仿传统 Perl 习惯用法的版本(除了额外的括号)
let comparing f x y = compare (f x) (f y)
let newList = List.map fst (
List.sort (comparing snd) (
List.map (fun x -> x, f x)
oldList))
但是,我们可以利用元组首先按第一个元素排序,然后按第二个元素排序,依此类推,将函数结果放在第一个元素位置,然后使用默认比较器
let newList = List.map snd (
List.sort compare (
List.map (fun x -> f x, x)
oldList))
在现代版本的 Javascript 中(Firefox 1.5、Internet Explorer 9、Opera 9.6、Chrome、Safari 3 等;在 ECMAScript 第 5 版中标准化),数组在其原型中有一个 map 函数,允许人们轻松地编写施瓦茨变换
function sortOn(arr, fun) {
return arr.map(function (x) {
return [ x, fun(x) ];
}).sort(function (a, b) {
// If fun(a) always returns a number, this can be replaced by: return a[1] - b[1];
return a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0;
}).map(function (x) {
return x[0];
});
}
示例用法
js> print(sortOn([ 'Charlie', 'alice', 'BOB' ], function (s) { return s.toUpperCase(); }));
alice,BOB,Charlie
对于旧版浏览器,可以使用 兼容性地图原型函数。