[ JavaScript 代码质量优化之路-2 | 青训营笔记]

143 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天

一、本堂课重点内容:

  • 代码实践 - 洗牌

二、详细知识点介绍:

1.代码实践 - 洗牌
错误写法:

使用sort方法洗牌,每次都通过Math.random获取随机数,如果得到的随机数大于0.5,就交换两张牌,否则不交换。

image.png

错误原因:结果中,原数组index越小的数,在洗牌结果中越有可能靠前,即洗牌后各位置等于0~9的每个数的概率不均,卡牌留在原位的概率较大。因为在运行1000000次后,将数组每一位的每次运行所得值相加,发现index越小的位,所得总值越小,而不是每个位置的数值都大致相等,可知原index越小的牌,在洗牌后也会更靠前,而不是每个位置概率均等。

image.png

正确写法:

循环抽取卡牌,循环次数为数组长度。每次循环根据Math.random取得的随机数,与当前所剩未遍历位数的乘积,抽取对应卡牌。

以图中数组为例,第1次循环,当前剩余位数为10,假设所得随机数为0.63,则Math.floor向下取整后,pIdx = 6,则数组中第七位的数将被交换到最后一位,随之剩余位数-1,进入下一次循环。

从统计值可以看出,每一位的运行所得总值几乎均等,说明每一张卡牌位于每个位置的概率相等。每个数有1/k的概率被抽到并交换,对于原位置上的数来说也有1/k的概率会被留在原位置上。

image.png

另一种写法:

将正确写法中的函数变成一个生成器,将抽取的卡牌使用yield关键字返回。

image.png

这种方法的不同之处在于可以控制洗出的卡牌数量,即生成所需要的张数的随机卡牌,在洗牌后调用next().value获取洗牌结果,就可以得到yield上一次的返回结果的value,即所有洗好的卡牌中的下一张卡牌,从而获取所需数量的卡牌而不是全部卡牌。

image.png

三、课后个人总结:

在实现随机洗牌的功能时,要注意其结果是否真的随机,通过测试可以发现,使用与相邻卡牌两两交换的方式实现洗牌,并非完全随机。

因此采用另一种方法,随机从牌组中取出一张牌,并放到牌组的最后。经测试可知该方法的结果是随机的,因此该方法正确。

另外,还可以使用生成器进行洗牌,实现在洗牌后不用返回整个牌组,而是返回少量靠前的随机卡牌。