简体   繁体   English

使用 Math rand 进行改组是如何工作的?

[英]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 Math.random()*(numbers.size() - i) might be equal to numbers.size() - i , even if Math.random() outputs a number that excludes 1. rounding error can cause the idiom 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.RandomnextInt方法)。 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 .看到这个问题

  1. You have a list of 1 to 50 ints.您有一个 1 到 50 个整数的列表。

  2. So get a random value from 0 to 49 inclusive to index it.所以得到一个从 0 到 49 的随机值来索引它。 say it is 30.说是30。

  3. Get item at index 30.获取索引 30 处的项目。

  4. Now replace item at index 30 with item at index 49.现在将索引 30 处的项目替换为索引 49 处的项目。

  5. 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,并且那里的号码占据了最后使用的号码的位置。

  6. 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)将生成一个介于0n-1之间的随机数,因为Math.random会生成一个介于01之间的随机数。

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实现的逻辑。

Analysis of your code:分析您的代码:

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

Your queries:您的疑问:

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]
  1. 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
  2. This point is valid, it can happen.这一点是有效的,它可能发生。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM