为了帮助弄清Array#sort
其及其比较器的行为,请考虑在入门编程课程中教授的这种天真的插入类型:
const sort = arr => {
for (let i = 1; i < arr.length; i++) {
for (let j = i; j && arr[j-1] > arr[j]; j--) {
[arr[j], arr[j-1]] = [arr[j-1], arr[j]];
}
}
};
const array = [3, 0, 4, 5, 2, 2, 2, 1, 2, 2, 0];
sort(array);
console.log("" + array);
忽略选择插入排序作为算法,将重点放在硬编码的比较器上:arr[j-1] > arr[j]
。这有两个与讨论有关的问题:
- 的
>
操作符被调用上对数组元素,但你可能要进行排序,例如对象不应对很多事情的>
一种合理的方式(如果我们使用了相同的情况是真实的-
)。
- 即使您正在处理数字,通常情况下,您也需要其他安排,而不是这里升序排列的升序排序。
我们可以通过添加comparefn
您熟悉的参数来解决这些问题:
const sort = (arr, comparefn) => {
for (let i = 1; i < arr.length; i++) {
for (let j = i; j && comparefn(arr[j-1], arr[j]) > 0; j--) {
[arr[j], arr[j-1]] = [arr[j-1], arr[j]];
}
}
};
const array = [3, 0, 4, 5, 2, 2, 2, 1, 2, 2, 0];
sort(array, (a, b) => a - b);
console.log("" + array);
sort(array, (a, b) => b - a);
console.log("" + array);
const objArray = [{id: "c"}, {id: "a"}, {id: "d"}, {id: "b"}];
sort(objArray, (a, b) => a.id.localeCompare(b.id));
console.log(JSON.stringify(objArray, null, 2));
现在,朴素的排序例程已通用化。您可以确切看到何时调用此回调,从而回答了您的第一组问题:
在排序过程中是否多次调用了数组排序回调函数?如果是这样,我想知道每次将两个数字传递给函数
运行下面的代码表明,是的,该函数已调用多次,您可以console.log
用来查看传入的数字:
const sort = (arr, comparefn) => {
for (let i = 1; i < arr.length; i++) {
for (let j = i; j && comparefn(arr[j-1], arr[j]) > 0; j--) {
[arr[j], arr[j-1]] = [arr[j-1], arr[j]];
}
}
};
console.log("on our version:");
const array = [3, 0, 4, 5];
sort(array, (a, b) => console.log(a, b) || (a - b));
console.log("" + array);
console.log("on the builtin:");
console.log("" +
[3, 0, 4, 5].sort((a, b) => console.log(a, b) || (a - b))
);
你问:
然后如何将两组数字彼此相对排序?
为了精确,术语,a
而b
不是套数字-数组中不着痕迹的对象(在你的榜样,他们的数字)。
事实是,它并不重要如何他们排序,因为它的实现依赖。如果我使用与插入排序不同的排序算法,则比较器可能会在不同的数字对上被调用,但是在排序调用结束时,对JS程序员而言重要的不变式是结果数组根据比较器,假设比较器返回的值符合您指定的合同(<0时a < b
,0时a === b
和> 0时a > b
)。
从同样的意义上讲,只要我不违反规范,我就可以自由更改排序的实现,ECMAScript的实现可以在语言规范的范围内自由选择排序实现,因此Array#sort
可能会产生不同的比较器调用在不同的引擎上。如果逻辑依赖于某些特定的比较序列,则不会编写代码(比较器首先也不应该产生副作用)。
例如,当数组大于某个预先计算的元素数量时,V8引擎(在编写本文时)会调用Timsort,并对小的数组块使用二进制插入排序。但是,它曾经使用quicksort不稳定的,可能会给比较器提供一系列不同的参数和调用。
由于不同的排序实现以不同的方式使用比较器函数的返回值,因此当比较器不遵守合同时,这可能导致令人惊讶的行为。有关示例,请参见此线程。