这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天
一、本堂课重点内容:
- 代码实践 - 洗牌
二、详细知识点介绍:
1.代码实践 - 洗牌
错误写法:
使用sort方法洗牌,每次都通过Math.random获取随机数,如果得到的随机数大于0.5,就交换两张牌,否则不交换。
错误原因:结果中,原数组index越小的数,在洗牌结果中越有可能靠前,即洗牌后各位置等于0~9的每个数的概率不均,卡牌留在原位的概率较大。因为在运行1000000次后,将数组每一位的每次运行所得值相加,发现index越小的位,所得总值越小,而不是每个位置的数值都大致相等,可知原index越小的牌,在洗牌后也会更靠前,而不是每个位置概率均等。
正确写法:
循环抽取卡牌,循环次数为数组长度。每次循环根据Math.random取得的随机数,与当前所剩未遍历位数的乘积,抽取对应卡牌。
以图中数组为例,第1次循环,当前剩余位数为10,假设所得随机数为0.63,则Math.floor向下取整后,pIdx = 6,则数组中第七位的数将被交换到最后一位,随之剩余位数-1,进入下一次循环。
从统计值可以看出,每一位的运行所得总值几乎均等,说明每一张卡牌位于每个位置的概率相等。每个数有1/k的概率被抽到并交换,对于原位置上的数来说也有1/k的概率会被留在原位置上。
另一种写法:
将正确写法中的函数变成一个生成器,将抽取的卡牌使用yield关键字返回。
这种方法的不同之处在于可以控制洗出的卡牌数量,即生成所需要的张数的随机卡牌,在洗牌后调用next().value获取洗牌结果,就可以得到yield上一次的返回结果的value,即所有洗好的卡牌中的下一张卡牌,从而获取所需数量的卡牌而不是全部卡牌。
三、课后个人总结:
在实现随机洗牌的功能时,要注意其结果是否真的随机,通过测试可以发现,使用与相邻卡牌两两交换的方式实现洗牌,并非完全随机。
因此采用另一种方法,随机从牌组中取出一张牌,并放到牌组的最后。经测试可知该方法的结果是随机的,因此该方法正确。
另外,还可以使用生成器进行洗牌,实现在洗牌后不用返回整个牌组,而是返回少量靠前的随机卡牌。