跳转到内容

算法实现/排序/施瓦茨变换

来自维基教科书,开放的书籍,开放的世界

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

[编辑 | 编辑源代码]

在现代版本的 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

对于旧版浏览器,可以使用 兼容性地图原型函数


华夏公益教科书