【问题标题】:Sorting in JavaScript: Should every compare function have a "return 0" statement?JavaScript 中的排序:每个比较函数都应该有一个“return 0”语句吗?
【发布时间】:2014-01-02 12:51:22
【问题描述】:

我最近阅读了很多关于在 JavaScript 中排序的答案,我经常偶然发现一个如下所示的比较函数:

array.sort(function(a,b){ a > b ? 1 : -1; });

所以它是一个比较函数,如果a 大于b,则返回1,如果a 小于或等于b,则返回-1。如 MDN (link) 所述,比较函数也可以返回零,以确保两个项目的相对位置保持不变:

如果 compareFunction(a, b) 返回 0,则保持 a 和 b 不变 相互尊重,但对所有不同的事物进行排序 元素。

所以官方的例子看起来更像这样:

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

事实上,通过添加return 0 语句,排序算法通常需要更少的迭代并且总体上运行得更快(JSPerf)。

所以我想知道省略return 0 语句是否有任何优势。

我意识到,在 MDN 上,它还说:

注意:ECMAscript 标准不保证这种行为,并且 因此并非所有浏览器(例如 Mozilla 版本至少可以追溯到 2003)尊重这一点。

指的是行为,如果返回 0,ab 应该保持不变。所以也许,通过返回 0,我们在不同的浏览器中得到一个稍微不同的排序数组?这可能是一个原因吗?还有什么其他理由根本不返回零?

【问题讨论】:

  • 它可能会影响你是否得到一个稳定的排序(即相同的元素总是以相同的顺序出现)
  • 它更短。就是这样。当您对 100 个以下的元素进行排序时 - 您不在乎。
  • @Emissary 你是说如果我没有return 0 声明,这实际上是一种不稳定的排序?
  • function(a,b){ a &gt; b ? 1 : -1; } 是完全安全的,前提是您知道您正在排序的数组不包含重复项 - 如果是这种情况,那么每次调用的比较操作会减少一次与包含 return 0 的版本相比,这对于大型数组可能很重要。
  • @basilikum 除了自己实现排序算法外,您无法做任何事情来保证稳定或不稳定的排序。不幸的是,实现稳定性留给您的脚本运行的browser version。比较运算符只影响速度,甚至这种影响也取决于浏览器/节点版本。

标签: javascript sorting


【解决方案1】:

所以我想知道省略 return 0 语句是否有任何优势。

要输入的字母更少。由于省略了比较,它可能会快一点。所有其他效果都是缺点

我意识到,在 MDN 上,它还说:

注意:ECMAscript 标准不保证这种行为,并且 因此并非所有浏览器(例如 Mozilla 版本至少可以追溯到 2003)尊重这一点。

指的是行为,如果为0,a和b应该保持不变 被退回。

ab 的位置可以保持不变只是stable sort 的要求。这不是一个特定的行为,一些浏览器已经实现了一种不稳定的排序算法。

但是,返回零的实际目的是,既不是a排在b之前(好像小于0),也不是b排在a之前(好像大于0)——基本上当a 等于b。这是比较的必备条件,所有排序算法都遵循它。

要产生有效的、可满足的排序(数学上:将项目划分为totally ordered 等价类),比较必须具有某些属性。它们在spec for sort 中被列为“一致的比较函数”的要求。

最突出的是自反性,要求项目a 等于a(本身)。另一种说法是:

compare(a, a) 必须始终返回 0

当比较函数不满足这一点时会发生什么(就像你偶然发现的那样)?

规范说

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

这基本上意味着:如果您提供无效的比较函数,则数组可能无法正确排序。它可能会随机排列,或者sort 调用甚至可能不会终止。

所以也许,通过返回 0,我们会得到一个稍微不同的 不同浏览器中的排序数组?这可能是一个原因吗?

不,通过返回 0,您会得到一个跨浏览器的 正确排序 数组(由于排序不稳定,可能会有所不同)。原因是,如果不返回 0,你会得到稍微不同的排列数组(如果有的话),甚至可能产生预期的结果,但通常以更复杂的方式。

那么,如果您不为等效项目返回 0,会发生什么情况?一些实现对此没有任何问题,因为它们从不将项目与自身进行比较(即使在数组中的多个位置很明显) - 当已经知道结果必须时,可以优化这一点并省略对比较函数的昂贵调用为 0。

另一个极端是永不终止的循环。假设您有两个相同的项目,您会将后者与前者进行比较,并意识到您必须交换它们。再次测试,后者仍然会比前者小,您必须再次交换它们。等等……

然而,一个有效的算法大多不会测试已经比较过的项目再次,所以通常执行会终止。尽管如此,它可能会或多或少地进行实际上不必要的交换,因此会比使用一致的比较函数花费更长的时间。

还有什么其他充分的理由根本不返回零?

懒惰并希望数组不包含重复项。

【讨论】:

  • 规范现在保证了稳定的排序,但它只在最近的环境中实现。
【解决方案2】:

比较方法应该遵守约定

Math.sign(compare(a, b)) = -Math.sign(compare(b, a))

如果您在 a==b 时返回非零答案,则违反了该约定。

【讨论】:

  • 好的,但是如果我不遵守这一点,究竟会失败什么呢?算法在某些情况下会崩溃吗?在 MDN 上,它只要求比较函数应始终为特定的一对项目返回相同的值,这是有保证的,即使如果两个项目相同,我返回 -1。
  • @basilikum:这取决于使用的算法 :-) 有关详细信息,请参阅我的答案。
猜你喜欢
  • 2012-06-22
  • 2010-09-07
  • 2010-09-26
  • 1970-01-01
  • 1970-01-01
  • 2010-10-24
相关资源
最近更新 更多