【问题标题】:Why does compareFunction has to consider negative?为什么 compareFunction 必须考虑负数?
【发布时间】:2017-08-19 15:46:53
【问题描述】:

Array.prototype.sort()

compareFunction(a, b)中,只有当我们需要交换a和b的位置时,才返回一个正值。

如果省略compareFunction 中的负数if-statementArray.prototype.sort() 仍然有效,那么开发人员为什么要编写返回负值的if-statement

var list = [4, 5, 3, 5, 6, 9, 1, 4, 2];
list = list.sort(function(a, b) {
  if (a > b) {
    return 1;
  }
});
console.log(list); // correct result

【问题讨论】:

  • 这个想法是,特别是对于数字(或字符的 ASCII 代码),不需要 if 语句。您只需返回a - b。比较数字是一种算术运算。如果差为负,那么我们知道第一个操作数小于第二个操作数。如果它为零,那么它们是相同的值。如果大于零,则第一个操作数大于第二个
  • 比较函数的思想是:{f, a, b, X | a∈X ∧ b∈X ∧ f(a,b)∈ℤ ∧ (a b ⇒ f(a,b)>0)}
  • 当您只定义a>b 的情况时,您只是部分定义了比较函数(因为您的输入集仅部分定义了订单关系)

标签: javascript arrays sorting


【解决方案1】:

这里的主要问题是您已经发明了自己对比较函数的定义,并以此为基础提出了问题:

在compareFunction(a, b)中,只有当我们需要交换a和b的位置时,才返回一个正值。

这是不正确的。 “当我们需要交换 a 和 b 的位置时”是一个实现细节,您将实现与接口混淆。

compareFunction 不负责指示何时交换两个元素。它负责准确传达两个元素的关系。排序算法对该信息的处理取决于实现者。如果您只在某些时候返回正确的值,那么您就不能一直期待正确的结果。

例如,排序实现者可以像这样实现排序(基于https://www.nczonline.net/blog/2012/09/17/computer-science-in-javascript-insertion-sort/ 的示例)。如果我使用有效的比较函数运行它,它会产生正确的结果:

function insertionSort(items, compare) {

  var len = items.length, // number of items in the array
    value, // the value currently being compared
    i, // index into unsorted section
    j; // index into sorted section

  for (i = 0; i < len; i++) {

    // store the current value because it may shift later
    value = items[i];

    for (j = i - 1; j > -1 && compare(value, items[j]) < 0; j--) {
      items[j + 1] = items[j];
    }

    items[j + 1] = value;
  }

  return items;
}

console.log(insertionSort([4,2,6,1,7,2], (l, r) => l - r));

如果我用你的比较函数运行它,它什么都不做:

function insertionSort(items, compare) {

  var len = items.length, // number of items in the array
    value, // the value currently being compared
    i, // index into unsorted section
    j; // index into sorted section

  for (i = 0; i < len; i++) {

    // store the current value because it may shift later
    value = items[i];

    for (j = i - 1; j > -1 && compare(value, items[j]) < 0; j--) {
      items[j + 1] = items[j];
    }

    items[j + 1] = value;
  }

  return items;
}

console.log(insertionSort([4,2,6,1,7,2], function(a, b) {
    if (a > b) {
        return 1;
    }
}));

【讨论】:

    【解决方案2】:

    这适用于您的情况,因为您没有测试所有可能性。但是,如果您查看the implementation 内部,您会发现引擎在短数组(即长度insertion sort 用于短数组,而QuickSort 用于长数组。

    由于您的实现必须定义哪个数字更高、低于或等于另一个,当涉及到更长的数组时,它会失败,因为您忘记实现“下”的情况(并暗示大小写相等,因为当b &gt;= a which will be interpreted0 时,您的函数将返回undefined,因此QuickSort 将无法正确排序您的数组,因为它无法知道一个数字何时小于另一个数字如果我理解正确,插入排序将起作用,这要归功于它的算法依赖于“超过”比较。

    请参阅下面的示例:

    var shortList = [9, 8, 7, 6, 5, 4, 3, 2, 1, 0],
        list = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
        
    console.log('Works : ', shortList.sort(function(a, b) {
      if (a > b) {
        return 1;
      }
    })); // You're being lucky on this one. Insertion sort.
    
    console.log('Doesnt work : ', list.sort(function(a, b) {
      if (a > b) {
        return 1;
      }
    })); // QuickSort
    
    console.log('Works : ', list.sort(function(a, b) {
      if (a > b) {
        return 1;
      } else if (a < b) {
        return -1;
      }
      
      return a - b; // Can be reduced to 'return a - b';
    })); // QuickSort

    【讨论】:

      【解决方案3】:

      拥有所有三种情况(a b)允许您拥有Total Ordering。但是如果你只指定一种情况——只指定一个 Weak Ordering。差异源于数学——但为了简化问题:使用前一种方案,您确实关心如何对相互关联的元素进行排序,而使用后一种方案,您不关心 一定在乎。

      var list = [
          { age: 65, name: 'Tony'},
          { age: 24, name: 'Joe'},
          { age: 24, name: 'Susan' } // Joe and Susan are tied,
          { age: 5, name: 'Alice'},
      ];
      

      假设我们按年龄对上述员工列表进行排序。总订单,我们保证有订单:Alice、Joe、Susan、Tony。因此,虽然 Joe 和 Susan 的年龄相同,但排序后仍保留了他们的相对顺序。然而,对于弱排序,我们将首先拥有 Alice,最后拥有 Tony,但 Joe 和 Susan 的顺序可供选择。这些员工的年龄相同,因此并列(平等)。这对于弱排序是不利的,因为弱排序没有指定如何在结果中排​​序关系——它是模棱两可的!因此,使用弱排序:我们最终可能会得到结果:Alice、Susan、Joe、Tony。当排序算法保留了关系的顺序时,我们说它是 stable sort

      如果您的排序功能类似于Arrays.prototype.sort() 并期望总排序,您应该始终提供所有三种情况!如果您不这样做:1. 已绑定的元素可能无法正确排序,2. 浏览器可能会感到困惑并且排序算法可能无法正确排序所有元素(即使没有任何绑定!)。

      // (*) Array.prototype.sort expects total ordering 
      // ... so three cases needed
      sort(list, function(a, b) { // (*)
          if (a.age < b.age) return -1;
          if (a.age > b.age) return  1;
          return 0;
      });
      

      如果您使用的排序功能预期弱排序,您将只提供一种情况。 C++ 标准库提供了一个期望弱排序的函数的完美示例。

      // (*) C++ STL uses weak orderings ...
      // ... so only one case needed
      struct Employee { int age; string name };
      vector<Employee> employees = {
          { 65, "Tony" },
          { 24, "Joe" },
          { 24, "Susan" },
          { 5, "Alice" }
      };
      struct Sorter {
          bool operator()(const Employee &e1, const Employee &e2) const {
              return e1.age < e2.age; // (*)
          }
      };
      sort(employees.begin(), employees.end(), Sorter());
      

      这里,你只需要在排序函数中提供一个 case a C++ Reference for sort:

      comp ... 返回的值指示作为第一个参数传递的元素是否被认为在其定义的特定严格弱排序中的第二个之前... 等效元素是 不保证保持原来的相对顺序

      事实证明,C++ 标准库的原始设计者 Alexander Stepanov,可能是 did not want 使用弱排序并且更喜欢使用全排序(比如 Javascript!)——可能是为了防止这些与关系的歧义.事实上,许多其他语言(包括JavaPython)使用总排序来进行排序功能。总排序很棒,因为它们消除了歧义,因此您应该提供所有三种情况的效果。

      【讨论】:

        【解决方案4】:

        如果您不遵循规范,那么您很可能会看到引擎之间的不一致,因为浏览器(例如)不知道如何处理它。 Chrome、Firefox 和 Node.js 似乎可以按您的预期对数组进行排序,但 Safari 并没有对它进行排序,例如:

        [4, 5, 3, 5, 6, 9, 1, 4, 2]
        

        我希望所有这些浏览器在不满足规范时都失败,例如“错误:RTM”。

        【讨论】:

          猜你喜欢
          • 2010-12-30
          • 1970-01-01
          • 2017-02-21
          • 1970-01-01
          • 2017-04-26
          • 2018-05-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多