Sorting in JavaScript: Shouldn't returning a boolean be enough for a comparison function?
我总是这样成功地对数组进行排序(当我不希望使用标准的词典排序时):
1 2 3 4 | var arr = […] // some numbers or so arr.sort(function(a, b) { return a > b; }); |
现在,有人告诉我这是错误的,我需要用
DR
I have always successfully sorted my arrays like this
Ok.
号
不,你没有。却没有注意到。一个快速的反例:好的。
1 2 3 | > [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 |
why?
Ok.
号
因为即使当
How do comparison functions work?
Ok.
号
- 当
a 被认为大于b 时,> 0 应在它之后排序。 == 0 当a 被认为等于b 时,谁先来并不重要。- 当
a 被认为小于b 时,< 0 应在它之前排序。
如果它不返回一个数字,结果将被转换为一个数字(对于布尔值来说很方便)。返回的数字不一定是
为了保持一致,比较函数需要满足这个方程。好的。
1 2 3 | 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 |
号
如果该需求被破坏,排序将表现为未定义。好的。
引用
如果
comparefn 不是此数组元素的一致比较函数[…],则将定义排序行为。好的。如果集合
S 中的所有值a 、b 和c (可能是相同的值),则函数comparefn 是一组值S 的一致比较函数;表示a 表示 comparefn(a,b) < 0 ;a =CF b 表示comparefn(a,b) = 0 ER符号);而a >CF b 表示comparefn(a,b) > 0 。好的。当给定一对特定的值
a 和b 作为它的两个参数时,调用comparefn(a,b) 总是返回相同的值v 。另外,Type(v) 是数字,v 不是NaN 。注意,这意味着对于给定的一对a 和b ,a 、 a =CF b 和a >CF b 中的一个是正确的。好的。
- 调用
comparefn(a,b) 不会修改此对象。a =CF a (自反性)- 如果是
a =CF b ,那么b =CF a (对称)- 如果
a =CF b 和b =CF c ,那么a =CF c (=CF 的传递性)- 如果
a 和 b ,那么 a ( 的传递性) - 如果
a >CF b 和b >CF c ,那么a >CF c (>CF 的传递性)注:上述条件是必要和充分的,以确保
comparefn 将设定的S 划分为等价类,并且这些等价类是完全有序的。好的。好的。
Uh, what does this mean? Why should I care?
Ok.
号
排序算法需要比较数组中的项。要做一个好的和有效的工作,它不需要把每一个项目相互比较,而是需要能够对他们的订购进行推理。为了使其工作良好,自定义比较函数需要遵守一些规则。一个微不足道的问题是,一个项目
a 等于它本身(compare(a, a) == 0 )——这是上面列表中的第一个项目(自反性)。是的,这有点数学化,但报酬很高。好的。最重要的是传递性。它表示,当算法比较了
a 和b 两个值,以及b 和c 两个值时,通过应用a = b 和b < c 等比较函数,可以得出a < c 也成立。这似乎只是逻辑上的,并且对于一个定义明确、一致的顺序是必需的。好的。但是你的比较函数并没有达到这个目的。让我们看看这个例子:好的。
1
2
3
4 function compare(a, b) { return Number(a > b); }
compare(0, 2) == 0 // ah, 2 and 0 are equal
compare(1, 0) == 1 // ah, 1 is larger than 0
// let's conclude: 1 is also larger than 2哎呀。这就是为什么当使用不一致的比较函数调用排序算法时,它会失败(在规范中,这是"依赖于实现的行为"——即不可预测的结果)。好的。
Why is the wrong solution so common?
Ok.
号
因为在许多其他语言中,有一些排序算法不希望进行三向比较,而只是一个小于运算符的布尔值。C++ EDCOX1〔27〕是一个很好的例子。如果需要确定相等性,只需使用交换参数应用两次。诚然,这样做效率更高且不易出错,但如果无法内联运算符,则需要对比较函数进行更多调用。好的。反例
I have tested my comparison function, and it works!
Ok.
号
只有靠运气,如果你尝试一些随机的例子。或者因为您的测试套件有缺陷-不正确和/或不完整。好的。
下面是我用来查找上述最小反例的小脚本:好的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 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();
});号什么比较函数是正确的?
当您需要词典排序时,完全不使用比较函数。如果需要,将对数组中的项进行字符串化。好的。
与关系运算符类似的通用比较函数可以实现为好的。
1
2
3
4
5 function(a, b) {
if (a > b) return 1;
if (a < b) return -1;
/* else */ return 0;
}通过一些技巧,可以将其缩小到等效的
function(a,b){return +(a>b)||-(a。好的。 对于数字,您可以简单地返回它们的差异,这符合上述所有法律:好的。
1
2
3 function(a, b) {
return a - b; // but make sure only numbers are passed (to avoid NaN)
}号
如果你想逆向排序,只需取适当的一个,用
b 交换a 。好的。如果要对复合类型(对象等)进行排序,请将每个
a 和每个b 替换为对相关属性的访问、方法调用或要排序的任何内容。好的。好啊。
sort 函数需要一个函数,该函数需要两个参数a 和b 并返回:
- 如果a在b之前,则为负数。
- 如果a在b之后,则为正数。
- 如果A和B的相对顺序无关紧要,则为零
为了按升序对数字排序,
return a - b 将生成正确的返回值;例如:
1
2
3
4 a b ret
1 2 -1
3 2 1
2 2 0另一方面,
return a > b 产生以下返回值:
1
2
3
4 a b ret implied
1 2 false 0
3 2 true 1
2 2 false 0号
在上面的例子中,sort函数被告知1和2是相同的(将1放在2之前或2放在1之前并不重要)。这将产生不正确的结果,例如(在Chrome 49中):
1
2
3
4 [5, 8, 7, 1, 2, 3, 4, 6, 9, 10, 11, 12, 13].sort(function(a, b) {
return a > b;
});
// [4, 5, 3, 1, 2, 6, 7, 8, 9, 10, 11, 12, 13]