在JavaScript中排序:不应该返回一个布尔值足够的比较函数?

时间:2014-06-06 11:29:20

标签: javascript sorting comparison

我总是这样成功地对我的数组进行排序(当我不想要标准的词典排序时):

var arr = […] // some numbers or so
arr.sort(function(a, b) {
    return a > b;
});

现在,有人告诉我这是错的,我需要return a-b代替。这是真的,如果是的话为什么?我测试了我的比较功能,它有效!另外,为什么我的解决方案be so common何时出错?

2 个答案:

答案 0 :(得分:94)

TL; DR

  

我总是像这样成功地对我的数组进行排序

不,你没有。并没有注意到它。一个快速的反例:

> [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
  

为什么?

因为即使false大于0,您的比较函数也会返回b(或a,等效)。但是0意味着两个元素被认为是相等的 - 排序算法认为。

深入解释

JavaScript中的比较函数

  

比较功能如何运作?

Array::sort method可以使用可选的自定义比较函数作为其参数。该函数有两个参数(通常称为ab),它应该比较,并且应该返回数字

    {li> > 0a被视为大于b且应在其后排序时
  • == 0a被视为等于b并且首先出现时并不重要
  • {li> < 0a被认为小于b且应在其之前排序时

如果它没有返回数字,结果将被转换为数字(这对于布尔值来说很方便)。返回的数字不必完全是-101(尽管通常是这样)。

一致的排序

为了保持一致,比较函数需要满足等式

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

如果该要求被破坏,排序将表现为未定义。

引用ES5.1 spec on sortES6 spec中的相同内容):

  

如果comparefn不是此数组元素的一致比较函数,则sort的行为是实现定义的。

     

如果所有值comparefnS满足以下所有要求,则函数a是一组值b的一致比较函数和c中的S(可能是相同的值):符号a <CF b表示comparefn(a,b) < 0; a =CF b表示comparefn(a,b) = 0(任一标志); a >CF b表示comparefn(a,b) > 0

     

当给定一对特定值comparefn(a,b)v作为其两个参数时,调用a始终返回相同的值b。此外,Type(v)是数字,v不是NaN。请注意,这意味着对于给定的a <CF ba =CF b对,a >CF bab中只有一个为真。

     
      
  • 调用comparefn(a,b)不会修改此对象。
  •   
  • a =CF areflexivity
  •   
  • 如果a =CF b,则b =CF asymmetry
  •   
  • 如果a =CF bb =CF c,则a =CF c=CF的{​​{3}})
  •   
  • 如果a <CF bb <CF c,则a <CF c<CF的传递性
  •   
  • 如果a >CF bb >CF c,则a >CF c>CF的传递性
  •   
     

注意:上述条件是必要且足以确保comparefn将集合S划分为等价类,并且这些等价类是完全有序的。

  呃,这是什么意思?我为什么要关心?

排序算法需要将数组的项目相互比较。要做好工作和高效工作,不必将每个项目相互比较,但需要能够推断他们的订购。为了更好地工作,自定义比较功能需要遵守一些规则。一个简单的问题是项a等于它自己(compare(a, a) == 0) - 这是上面列表中的第一项(反身性)。是的,这有点数学,但收入很好。

最重要的是传递性。它表示当算法比较了两个值ab,以及bc时,并通过应用比较函数找到了它。 a = bb < c,然后可以预期a < c也可以。这似乎是合乎逻辑的,并且是明确定义的一致排序所必需的。

但是你的比较功能确实失败了。让我们看一下这个例子:

 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

糟糕!这就是为什么排序算法会失败(在规范中,这是&#34; 依赖于实现的行为&#34; - 即不可预测的结果)当使用比较函数调用它时不一致。

  

为什么错误的解决方案如此普遍?

因为在许多其他语言中,有些排序算法不期望transitivity,而只是布尔小于运算符。 three-way comparison就是一个很好的例子。如果需要确定相等性,它将仅使用交换参数应用两次。不可否认,如果无法内联运算符,这可能更有效且不易出错,但需要更多调用到比较函数。

的反

  

我已经测试了我的比较功能,它有效!

只有纯粹的运气,如果你尝试了一些随机的例子。或者因为您的测试套件存在缺陷 - 不正确和/或不完整。

这是我用来找到上述最小反例的小脚本:

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

什么比较功能正确?

当您需要词典排序时,根本不使用比较功能。如有必要,数组中的项目将被字符串化。

与关系运算符类似的通用比较函数可以实现为

function(a, b) {
    if (a > b) return 1;
    if (a < b) return -1;
    /* else */ return 0;
}

通过一些技巧,可以将其缩小为等效function(a,b){return +(a>b)||-(a<b)}

C++ std::sort,您可以简单地返回他们的差异,这符合上述所有法律:

function(a, b) {
    return a - b; // but make sure only numbers are passed (to avoid NaN)
}

如果您想要反向排序,只需选择合适的排序并与a交换b

如果要对复合类型(对象等)进行排序,请将每个a和每个b替换为相关属性的访问权限,或方法调用或您要排序的任何内容。

答案 1 :(得分:12)

sort函数需要一个需要两个参数ab的函数,并返回:

  • 如果来自 b
  • 之前的,则为负数
  • 如果在 b
  • 之后,则为正数
  • 如果a和b的相对顺序无关紧要为零

为了按升序排序数字return a - b将产生正确的返回值;例如:

a    b    ret
1    2    -1
3    2     1
2    2     0

另一方面,return a > b产生以下返回值:

a    b    ret      implied
1    2    false    0
3    2    true     1
2    2    false    0

在上面的示例中,sort函数被告知1和2 相同(并且在1之前将1放在1或2之前无关紧要)。这将产生不正确的结果,例如(在Chrome 49中):

[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]