JavaScript中的排序:不应该';对于比较函数来说,返回布尔值还不够吗?
我总是这样成功地对数组进行排序(当我不需要标准的词典排序时): 现在,有人告诉我这是错误的,我需要JavaScript中的排序:不应该';对于比较函数来说,返回布尔值还不够吗?,javascript,sorting,comparison,Javascript,Sorting,Comparison,我总是这样成功地对数组进行排序(当我不需要标准的词典排序时): 现在,有人告诉我这是错误的,我需要返回a-b。这是真的吗?如果是,为什么?我已经测试了我的比较函数,它是有效的!还有,为什么我的解决方案出错了呢?TL;博士 我总是这样成功地对数组进行排序 不,你没有。我没有注意到。一个快速反例: > [1,1,0,2].sort(function(a, b){ return a>b }) Array [0, 1, 2, 1] // in Opera 12. Results may va
返回a-b
。这是真的吗?如果是,为什么?我已经测试了我的比较函数,它是有效的!还有,为什么我的解决方案出错了呢?TL;博士
我总是这样成功地对数组进行排序
不,你没有。我没有注意到。一个快速反例:
> [1,1,0,2].sort(function(a, b){ return a>b })
Array [0, 1, 2, 1]
// in Opera 12. Results may vary between sorting algorithm implementations
function perms(n, i, arr, cb) {
// calls callback with all possible arrays of length n
if (i >= n) return cb(arr);
for (var j=0; j<n; j++) {
arr[i] = j;
perms(n, i+1, arr, cb);
}
}
for (var i=2; ; i++) // infinite loop
perms(i, 0, [], function(a) {
if ( a.slice().sort(function(a,b){ return a>b }).toString()
!= a.slice().sort(function(a,b){ return a-b }).toString() )
// you can also console.log() all of them, but remove the loop!
throw a.toString();
});
为什么?
因为即使b
大于a
,比较函数也会返回false
(或0
,等效)。但是0
意味着这两个元素被认为是相等的——排序算法认为这一点
深入解释
JavaScript中的比较函数
比较函数是如何工作的
可以将可选的自定义比较函数作为其参数。该函数使用两个参数(通常称为a
和b
)进行比较,并应返回一个数字
当>0
被视为大于a
且应在其之后排序时b
当==0
被认为等于a
时,哪个先到并不重要b
当<0
被认为小于a
且应在其之前排序时b
-1
或0
或1
(尽管通常是这样)
一致排序
为了保持一致,比较函数需要满足以下等式
comp(a, b) == -1 * comp(b, a)
// or, if values other than -1, 0 and 1 are considered:
comp(a, b) * comp(b, a) <= 0
哎呀。这就是为什么排序算法在使用不一致的比较函数调用时会失败(在规范中,这是“依赖于实现的行为”-即不可预测的结果)
为什么错误的解决方案如此普遍
因为在许多其他语言中,有一些排序算法不需要一个,而只需要一个小于运算符的布尔值。这是一个很好的例子。如果需要确定相等性,只需使用交换的参数应用两次。诚然,这可以更高效,也不容易出错,但是如果操作符不能内联,则需要更多地调用比较函数
反例
我已经测试了我的比较函数,它是有效的
如果你尝试一些随机的例子,那纯粹是运气使然。或者因为您的测试套件有缺陷—不正确和/或不完整
下面是我用来查找上述最小反例的小脚本:
> [1,1,0,2].sort(function(a, b){ return a>b })
Array [0, 1, 2, 1]
// in Opera 12. Results may vary between sorting algorithm implementations
function perms(n, i, arr, cb) {
// calls callback with all possible arrays of length n
if (i >= n) return cb(arr);
for (var j=0; j<n; j++) {
arr[i] = j;
perms(n, i+1, arr, cb);
}
}
for (var i=2; ; i++) // infinite loop
perms(i, 0, [], function(a) {
if ( a.slice().sort(function(a,b){ return a>b }).toString()
!= a.slice().sort(function(a,b){ return a-b }).toString() )
// you can also console.log() all of them, but remove the loop!
throw a.toString();
});
如果要反向排序,只需选择适当的一个,并将a
与b
交换即可
如果要对复合类型(对象等)进行排序,请将每个a
和每个b
替换为相关属性的访问权限,或方法调用或任何您想要排序的内容。函数需要一个需要两个参数a
和b
的函数,并返回:
- 如果A在b之前,则为负数
- 如果A在b之后,则为正数
- 如果a和b的相对顺序无关紧要,则为零
返回a-b
将生成正确的返回值;例如:
abret
1 2 -1
3 2 1
2 2 0
另一方面,返回a>b
产生以下返回值:
a b ret隐含
1 2假0
3 2正确的1
2假0
在上面的示例中,排序函数被告知1和2是相同的(将1放在2之前或将2放在1之前并不重要)。这将产生不正确的结果,例如(在Chrome 49中):
console.log([5,8,7,1,2,3,4,6,9,10,11,12,13])。排序(函数(a,b){
返回a>b;
}));
//[4,5,3,1,2,6,7,8,9,10,11,12,13]
回答得不错;两件事。首先,减法不能产生NaN。其次,对于使用整数(而不是像JS那样使用浮点数)作为比较运算符的语言中的一些错误示例,请参见[1,1,0,2].sort(函数(a,b){return a>b})
不返回[0,1,2,1]
。或者你只是指歌剧?我还没有测试过。您的反例在Chromium 66中正确排序,用于查找最小反例的脚本在节点8上没有产生任何结果。11请注意,谓词形式可以直接转换为正确的比较器:const comparator=(isLt)=>(a,b)=>isLt(a,b) ? -1:isLt(b,a)?1:0
,类似于['foo'、'bar'、'BAZ'].排序(比较器((a,b)=>a.toLowerCase()
(或通过翻转符号使用
),在Internet Explorer中排序不起作用。
function(a, b) {
if (a > b) return 1;
if (a < b) return -1;
/* else */ return 0;
}
function(a, b) {
return a - b; // but make sure only numbers are passed (to avoid NaN)
}