稳定的排序
假设你有一个这样的数组:
['Kale', 'Kaleidoscope', 'Aardvark', 'Apple', 'Leicester', 'Lovely']
现在你只想对第一个字母排序:
usort($array, function($a, $b) {
return strcmp($a[0], $b[0]);
});
结果是:
['Apple', 'Aardvark', 'Kale', 'Kaleidoscope', 'Lovely', 'Leicester']
那种东西不稳定!
敏锐的观察者可能已经注意到,数组排序算法(快速排序)并没有产生稳定的结果,并且相同首字母的单词之间的原始顺序没有被保留。这种情况很简单,我们应该比较整个字符串,但是让我们假设您的用例更复杂,比如在不同的字段上连续进行两次排序,它们不应该相互抵消。
施瓦兹变换
Schwartzian变换,也称为装饰-排序-不装饰习语,它使用固有的不稳定排序算法来实现稳定排序。
首先,你用另一个数组装饰每个数组元素,这个数组包含一个主键(value)和一个辅助键(它的索引或位置):
array_walk($array, function(&$element, $index) {
$element = array($element, $index); // decorate
});
这将数组转换为:
[
['Kale', 0], ['Kaleidoscope', 1],
['Aardvark', 2], ['Apple', 3],
['Leicester', 4], ['Lovely', 5]
]
现在,我们调整比较步骤;我们再次比较第一个字母,但如果它们相同,则使用次键来保留原来的顺序:
usort($array, function($a, $b) {
// $a[0] and $b[0] contain the primary sort key
// $a[1] and $b[1] contain the secondary sort key
$tmp = strcmp($a[0][0], $b[0][0]);
if ($tmp != 0) {
return $tmp; // use primary key comparison results
}
return $a[1] - $b[1]; // use secondary key
});
之后,我们进行装饰:
array_walk($array, function(&$element) {
$element = $element[0];
});
最终结果:
['Aardvark', 'Apple', 'Kale', 'Kaleidoscope', 'Leicester', 'Lovely']
重用呢?
你必须重写比较函数来处理转换后的数组元素;你可能不想编辑你精致的比较函数,所以这里有一个比较函数的包装器:
function stablecmp($fn)
{
return function($a, $b) use ($fn) {
if (($tmp = call_user_func($fn, $a[0], $b[0])) != 0) {
return $tmp;
} else {
return $a[1] - $b[1];
}
};
}
让我们使用这个函数来编写排序步骤:
usort($array, stablecmp(function($a, $b) {
return strcmp($a[0], $b[0]);
}));
瞧!您的原始比较代码回来了。