JavaScript 数组改组 - 为什么随机数必须向下移动 0.5 到负范围?

JavaScript array shuffling - why does the random number have to be shifted 0.5 down to a negative range?

我发现以下随机播放功能简短易懂。

    function shuffleArray(arr) {
      arr.sort(() => {
          r = Math.random() - 0.5 
          // console.log(r)
          return r
      });
    }
    let arr = [1, 2, 3, 4, 5];
    shuffleArray(arr);
    console.log(JSON.stringify(arr))

为什么包含的排序调用需要偏移 0.5。是的,我以前见过这个,但是不,我不能说服也不理解自己为什么...

事实上,为了确认,从随机范围移位计算中删除 - 0.5 会生成我们需要洗牌的完全相同的数组。

我是否缺少更深层次的东西?为什么生成的数字必须同时为负数和正数,为什么要特别偏移 0.5?

如果您想深入了解并通过正在使用的特定排序算法的镜头对此进行分析,您做不到。 ECMAscript 标准没有指定浏览器必须为 JavaScript 的排序算法使用哪种排序算法,因此 Firefox 可能会以一种方式使用,而 Chrome 则使用另一种方式。然后 Chrome 可能会更改以在以后以不同的方式执行。我们只需要接受这种抽象级别。

现在,我们 知道这些事情。 JavaScript 允许我们提供一个“比较”功能,以便在对数组进行排序时使用。您可以将两个参数传递给该比较函数和 return 一个结果来表示哪个值应该首先出现在结果数组中。

  • 返回负数表示第一个参数应该出现在排序数组的前面。
  • 返回正数表示第二个参数应该出现在排序数组的前面。
  • 返回零表明这两个值相同并且应该在排序后的数组中相邻出现。

这里有一个小片段来举例说明这些规则:

var arr = ["w", "l", "w"];
arr.sort(function(a, b){
    if(a === b) return 0; //they're the same
    if(a === "w") return -1; //w should appear earlier
    return 1; //l should appear later
});

console.log(arr);

我们也可以更改它以使用数字:

var arr = [1, 3, 1];
arr.sort(function(a, b){
    if(a === b) return 0; //they're the same
    if(a < b) return -1; //lower numbers should appear earlier
    return 1; //higher numbers should appear later
});

console.log(arr);

所以在这一点上,一个有趣的问题是“如果我总是 return 一个正数会怎样?”好吧,您这样做真正告诉排序算法的是 everything 应该出现在排序后的数组中。但是,没有更早的东西,什么是“后来”?!它会一直循环下去吗?我的电脑会爆炸吗?

答案是,这完全取决于浏览器选择使用的排序算法。您可以完全相信浏览器的排序算法会选择一个停止排序的位置并结束排序,但当时芯片所在的位置可能因浏览器而异。在 Chrome 和 Firefox 中尝试这个例子,你可能会得到不同的结果:

var arr = [8, 6, 7, 5, 3, 0, 9];
arr.sort((a, b) => 1);

console.log(arr);

并且因为 任何 正数 return 在比较函数中发出同样的信号,总是 returning Math.random() 将具有相同的效果(因为您几乎可以保证随机数将大于零):

var arr = [8, 6, 7, 5, 3, 0, 9];
arr.sort((a, b) => Math.random());

console.log(arr);

只有当您引入 return 负数的可能性时,您才开始告诉排序算法在排序数组中先显示某些值:

var arr = [8, 6, 7, 5, 3, 0, 9];
arr.sort((a, b) => Math.random() - .5);

console.log(arr);

至于为什么特意选择.5?好吧,Math.random 给你一个 0 到 1 的数字。大约 100% 的时间都是正数。如果我们从 Math.random 中减去 .1,我们开始得到从 -0.1 到 0.9 的结果。在大约 90% 的时间里,这是一个正数!它会起作用,但理想情况下,我们希望它更像是一次抛硬币,这样我们就可以对随机排序的公平性感到满意。这就是为什么我们专门减去 0.5 - 得到从 -0.5 到 0.5 的数字,大约 50% 的时间产生积极的结果,大约另外 50% 的时间产生消极的结果。

但是如果你关心结果的质量...

正如其他人所提到的,上述比较功能已经过全面测试,并且已知会偏向于某些数字而不是其他数字。最流行的正确洗牌称为 Fisher Yates 洗牌,您可以阅读它 here. It's definitely not as concise, and when used with Math.random, it's of course not cryptographically secure

如果您需要加密强度高的随机排序,但仍然想要一些简短而有趣的东西,我总是建议您看一下 rando.js。您可以像这样获得加密强度高的排序:

console.log(randoSequence([8, 6, 7, 5, 3, 0, 9]));
<script src="https://randojs.com/2.0.0.js"></script>

默认情况下它会跟踪原始索引,以防两个值相同但您仍然关心它们的来源。如果您不喜欢那样,您可以使用简单的地图将结果缩减为仅包含值:

console.log(randoSequence([8, 6, 7, 5, 3, 0, 9]).map((i) => i.value));
<script src="https://randojs.com/2.0.0.js"></script>