[英]How does this shuffling with Math rand work?
I saw this code to shuffle a list:我看到这段代码来打乱一个列表:
public static void shuffle(List<Integer> numbers) {
if(numbers == null || numbers.isEmpty()) return;
for(int i = 0; i < numbers.size(); ++i) {
int index = (int) (i + Math.random()*(numbers.size() - i));
swap(numbers, i, index);
}
}
The code seem to work but I don't understand this snippet:该代码似乎有效,但我不明白这个片段:
int index = (int) (i + Math.random()*(numbers.size() - i));
Basically it is i + R*(ni)
but how does this ensure that: i) we won't get an out of bounds index or ii) I won't be changing the same element's ie index == i
and the shuffle would not be that random?基本上它是i + R*(ni)
但这如何确保:i)我们不会得到超出范围的索引或 ii)我不会更改相同元素的index == i
并且洗牌会不是那么随意吗?
Math.random()
returns a uniform random number in the interval [0, 1)
, and numbers.size() - i
, ideally, scales that number to the interval [0, numbers.size() - i)
. Math.random()
返回区间[0, 1)
中的统一随机数,并且numbers.size() - i
理想情况下,将该数字缩放到区间[0, numbers.size() - i)
。 For example, if i
is 2 and the size of the list is 5, a random number in the interval [0, 3)
is chosen this way, in the ideal case.例如,如果i
为 2,列表的大小为 5,则在理想情况下,以这种方式选择区间[0, 3)
中的随机数。 Finally, i
is added to the number and the (int)
cast discards the number's fractional part.最后,将i
添加到数字中,并且(int)
强制转换丢弃数字的小数部分。 Thus, in this example, a random integer in [2, 5)
(that is, either 2, 3, or 4) is generated at random, so that at each iteration, the number at index X swaps with itself or a number that follows it.因此,在此示例中,随机生成[2, 5)
(即 2、3 或 4)中的随机 integer,因此在每次迭代中,索引 X 处的数字与自身或与跟随它。
However, there is an important subtlety here.然而,这里有一个重要的微妙之处。 Due to the nature of floating-point numbers and rounding error when scaling the number, in extremely rare cases the output of rounding error can cause the idiom Math.random()*(numbers.size() - i)
might be equal to numbers.size() - i
, even if Math.random()
outputs a number that excludes 1.Math.random()*(numbers.size() - i)
to bias some results over others.由于浮点数的性质和缩放数字时的舍入误差,在极少数情况下, 舍入误差会导致成语Math.random()*(numbers.size() - i)
的 output 可能等于numbers.size() - i
,即使Math.random()
输出一个不包括 1 的数字。Math.random()*(numbers.size() - i)
使某些结果偏向于其他结果。 For example, this happens whenever 2^53 is not divisible by numbers.size() - i
, since Math.random()
uses java.util.Random
under the hood, and its algorithm generates numbers with 53 bits of precision.例如,只要 2^53 不能被numbers.size() - i
整除,就会发生这种情况,因为Math.random()
在后台使用java.util.Random
,其算法生成的数字精度为 53 位。 Because of this, Math.random()
is not the best way to write this code, and the code could have used a method specially made for generating random integers instead (such as the nextInt
method of java.util.Random
).因此, Math.random()
不是编写此代码的最佳方式,代码可以使用专门用于生成随机整数的方法(例如java.util.Random
的nextInt
方法)。 See also this question and this question .另见这个问题和这个问题。
EDIT: As it turns out, the Math.random() * integer
idiom does not produce the issue that it may return integer
, at least when integer
is any positive int
and the round-to-nearest rounding mode is used as in Java. EDIT: As it turns out, the Math.random() * integer
idiom does not produce the issue that it may return integer
, at least when integer
is any positive int
and the round-to-nearest rounding mode is used as in Java. See this question .看到这个问题。
You have a list of 1 to 50 ints.您有一个 1 到 50 个整数的列表。
So get a random value from 0 to 49 inclusive to index it.所以得到一个从 0 到 49 的随机值来索引它。 say it is 30.说是30。
Get item at index 30.获取索引 30 处的项目。
Now replace item at index 30 with item at index 49.现在将索引 30 处的项目替换为索引 49 处的项目。
Next time generate a number between 0 and 48 inclusive.下次生成一个介于 0 到 48 之间的数字。 49 will never be reached and the number that was there occupies the slot of the last number used.永远不会达到 49,并且那里的号码占据了最后使用的号码的位置。
Continue this process until you've exhausted the list.继续此过程,直到您用完列表。
Note: that the expression (int)(Math.random() * n)
will generate a random number between 0
and n-1
inclusive because Math.random
generates a number between 0
and 1
exclusive.注意:表达式(int)(Math.random() * n)
将生成一个介于0
和n-1
之间的随机数,因为Math.random
会生成一个介于0
和1
之间的随机数。
Math.random() always returns a floating-point number between 0 (inclusive) and 1 (exclusive). Math.random() 总是返回一个介于 0(包括)和 1(不包括)之间的浮点数。 So when you do Math.random()*(numbers.size() - i)
, the result will always be between 0 (inclusive) and ni
(exclusive).因此,当您执行Math.random()*(numbers.size() - i)
时,结果将始终介于 0(含)和ni
(不含)之间。
Then you add i to it in i + Math.random()*(numbers.size() - i)
.然后在i + Math.random()*(numbers.size() - i)
中添加 i 。
Now the result, as you can see, will be between i (inclusive) and n (exclusive).现在,如您所见,结果将介于 i(包括)和 n(不包括)之间。
After that, you are casting it to an int.之后,您将其转换为 int。 When you cast a double to an int, you truncate it, so now the value of index will somewhere from ``i to
n - 1``` (inclusive for both).当你将一个 double 转换为一个 int 时,你会截断它,所以现在 index 的值将介于“i to
n - 1”之间(包括两者)。
Therefore, you will not have an ArrayIndexOutOfBoundsException, since it will always be at least 1 less than the size of the array.因此,您不会有 ArrayIndexOutOfBoundsException,因为它总是至少比数组的大小小 1。
However, the value of index could be equal to i, so yes, you are right in that a number could be swapped with itself and stay right there.然而,index 的值可能等于 i,所以是的,你是对的,一个数字可以与自身交换并保持在那里。 That's perfectly fine.这完全没问题。
Instead of using such a custom method, I recommend you use OOTB Collections.shuffle .我建议您不要使用这种自定义方法,而是使用 OOTB Collections.shuffle 。 Check this to understand the logic implemented for Collections.shuffle
.检查此项以了解为Collections.shuffle
实现的逻辑。
Math.random() returns a double
value with a positive sign, greater than or equal to 0.0
and less than 1.0
. Math.random()返回一个带正号的double
精度值,大于或等于0.0
且小于1.0
。
Now, let's assume numbers.size() = 5
and dry run the for
loop:现在,让我们假设numbers.size() = 5
并空运行for
循环:
When i = 0, index = (int) (0 + Math.random()*(5 - 0)) = (int) (0 + 4.x) = 4
When i = 1, index = (int) (1 + Math.random()*(5 - 1)) = (int) (1 + 3.x) = 4
When i = 2, index = (int) (2 + Math.random()*(5 - 2)) = (int) (2 + 2.x) = 4
When i = 3, index = (int) (3 + Math.random()*(5 - 3)) = (int) (3 + 1.x) = 4
When i = 4, index = (int) (4 + Math.random()*(5 - 4)) = (int) (4 + 0.x) = 4
As you can see, the value of index
will remain 4
in each iteration when numbers.size() = 5
.如您所见,当numbers.size() = 5
时,每次迭代中index
的值都将保持为4
。
how does this ensure that: i) we won't get an out of bounds index这如何确保:i) 我们不会得到越界索引
As already explained above using the dry run, it will never go out of bounds.正如上面已经使用试运行所解释的,它永远不会超出 go。
or ii) I won't be changing the same element's ie index == i and the shuffle would not be that random?或 ii) 我不会更改相同元素的 ie index == i 并且随机播放不会那么随机?
swap(numbers, i, index);
is swapping the element at index, i
with the element at index, 4
each time when numbers.size() = 5
.当numbers.size() = 5
时,每次将索引i
处的元素与索引处的元素4
交换。 This is illustrated with the following example:以下示例说明了这一点:
Let's say numbers
= [1, 2, 3, 4, 5]假设numbers
= [1, 2, 3, 4, 5]
When i = 0, numbers will become [5, 2, 3, 4, 1]
When i = 1, numbers will become [5, 1, 3, 4, 2]
When i = 2, numbers will become [5, 1, 2, 4, 3]
When i = 3, numbers will become [5, 1, 2, 3, 4]
When i = 4, numbers will become [5, 1, 2, 3, 4]
int index = (int) (i + Math.random()*(numbers.size() - i));
- it is important to note that Math.random() will generate a number which belongs to <0;1). - 重要的是要注意 Math.random() 将生成一个属于 <0;1) 的数字。 So it will never exceed the boundry as exclusive max will be: i + 1*(number.size() -i) = number.size所以它永远不会超过边界,因为独占最大值将是: i + 1*(number.size() -i) = number.size
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.