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)
}