在Java中生成唯一的随机数

109

我想要获取0到100之间的随机数,但是我希望它们是唯一的,在序列中不会重复。例如,如果我获得了5个数字,它们应该是82、12、53、64、32,而不是82、12、53、12、32。我使用了以下代码,但它生成的数字在序列中是相同的。

Random rand = new Random();
selected = rand.nextInt(100);

5
你可以创建一个 1..100 范围内的 随机排列(有著名的算法可实现),但在确定前 n 个元素后停止。 - Kerrek SB
可能是使用Java生成范围内的随机数的重复问题。 - user289086
相关内容:http://stackoverflow.com/questions/28990820/iterator-to-produce-unique-random-order - Nikos M.
这个独特的随机ID生成器可能会很有用。 - Erfan Ahmed
21个回答

168
  • 将范围内的每个数字按顺序添加到列表结构中。
  • 洗牌它。
  • 取前'n'个。

这是一个简单的实现。这将从1-10范围内打印3个唯一的随机数。

import java.util.ArrayList;
import java.util.Collections;

public class UniqueRandomNumbers {
    
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        for (int i=1; i<11; i++) list.add(i);
        Collections.shuffle(list);
        for (int i=0; i<3; i++) System.out.println(list.get(i));
    }
}

根据原始做法,如Mark Byers在一篇已删除的答案中指出的那样,修复的第一部分是只使用一个Random实例。

这就是导致数字相同的原因。 Random实例以毫秒为单位设置种子值。对于特定的种子值,'随机'实例将返回完全相同的伪随机数序列


3
感谢指出单个随机实例并回答问题,点个赞 :) - Mark Byers
4
如果你想要 n 个不重复的数字,就不需要洗牌整个范围。只需使用 Fisher-Yates 洗牌算法洗牌前 n 个位置即可。这在处理大列表和小 n 的情况下可能会有所帮助。 - rossum
如何打乱一个数组 https://www.digitalocean.com/community/tutorials/shuffle-array-java - Ola Ström

73

使用Java 8+,你可以使用Randomints方法获取随机值的IntStream,然后使用distinctlimit将流减少到一定数量的唯一随机值。

ThreadLocalRandom.current().ints(0, 100).distinct().limit(5).forEach(System.out::println);

Random还有一些方法可以创建LongStreamDoubleStream,如果你需要的话。

如果你想要在一个范围内以随机顺序输出所有(或大量)数字,可能更有效的方法是将所有数字添加到列表中,然后打乱它们并取前n个数字。因为上面的示例是通过在请求的范围内生成随机数并将其传递到集合中来实现的(类似于Rob Kielty的答案),这可能需要生成比限制数量更多的随机数,因为发现新的唯一数字的概率会随着每个数字的发现而减少。以下是另一种方法的示例:

List<Integer> range = IntStream.range(0, 100).boxed()
        .collect(Collectors.toCollection(ArrayList::new));
Collections.shuffle(range);
range.subList(0, 99).forEach(System.out::println);

我需要这个来测试一些代码,而Arrays#setAll()比流略快。所以:Integer [] indices = new Integer [n]; Arrays.setAll(indices,i-> i); Collections.shuffle(Arrays.asList(indices)); return Arrays.stream(indices).mapToInt(Integer :: intValue).toArray(); - AbuNassar

17
  1. 创建一个由100个数字组成的数组,然后随机打乱它们的顺序。
  2. 设计一个范围为100的伪随机数生成器。
  3. 创建一个由100个元素组成的布尔数组,当你选择该数字时将其元素设置为true。在选择下一个数字时,检查该数组并如果该数组元素已设置,则再次尝试。 (您可以使用long数组创建易于清除的布尔数组,其中您可以通过移位和掩码来访问各个位。)

2
+1 对于另一种方法; pick() 是一个例子。 - trashgod
1
你可以使用 HashSet 代替布尔数组,将已经生成的数字存储在其中,并使用 contains 方法来测试是否已经生成了该数字。HashSet 可能比布尔数组稍微慢一些,但占用的内存更少。 - Rory O'Kane
1
@RoryO'Kane -- 我非常确定,如果实现为long[2]的数组布尔数组将占用更少的空间。绝对不可能使HashSet那么小。 - Hot Licks
最后一种方法有点丑陋,因为它没有明确定义的步骤来生成整个序列。而且你不需要重新发明轮子 - BitSet - Pavel Horal

15

使用 Collections.shuffle() 对所有100个数字进行洗牌,然后选择前五个,可以参考这里和下面的示例。

控制台:

59 9 68 24 82

代码:

private static final Random rnd = new Random();
private static final int N = 100;
private static final int K = 5;
private static final List<Integer> S = new ArrayList<>(N);

public static void main(String[] args) {
    for (int i = 0; i < N; i++) {
        S.add(i + 1);
    }
    Collections.shuffle(S, rnd);
    for (int i = 0; i < K; i++) {
        System.out.print(S.get(i) + " ");
    }
    System.out.println();
}

13

我觉得这种方法值得一提。

   private static final Random RANDOM = new Random();    
   /**
     * Pick n numbers between 0 (inclusive) and k (inclusive)
     * While there are very deterministic ways to do this,
     * for large k and small n, this could be easier than creating
     * an large array and sorting, i.e. k = 10,000
     */
    public Set<Integer> pickRandom(int n, int k) {
        final Set<Integer> picked = new HashSet<>();
        while (picked.size() < n) {
            picked.add(RANDOM.nextInt(k + 1));
        }
        return picked;
    }

9

我重新改进了Anand的答案,不仅利用了Set的独特属性,而且还使用了在集合添加失败时set.add()返回的布尔值false。

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class randomUniqueNumberGenerator {

    public static final int SET_SIZE_REQUIRED = 10;
    public static final int NUMBER_RANGE = 100;

    public static void main(String[] args) {
        Random random = new Random();

        Set set = new HashSet<Integer>(SET_SIZE_REQUIRED);

        while(set.size()< SET_SIZE_REQUIRED) {
            while (set.add(random.nextInt(NUMBER_RANGE)) != true)
                ;
        }
        assert set.size() == SET_SIZE_REQUIRED;
        System.out.println(set);
    }
}

1
不错的想法。但有一个重要的标记 - 如果 SET_SIZE_REQUIRED 足够大(比如说,超过了 NUMBER_RANGE / 2),那么你将得到更长的预期运行时间。 - noamgot
1
不要使用 != true 的比较方式。只需使用 while(set.add(random.nextInt(NUMBER_RANGE)) ; 并且始终使用泛型。不要创建一个 HashSet<Integer> 并将其分配给原始的 Set - Holger
1
当然,while(! set.add(random.nextInt(NUMBER_RANGE)) ; - Holger

5
我已经按照此方式完成了这个项目。
    Random random = new Random();
    ArrayList<Integer> arrayList = new ArrayList<Integer>();

    while (arrayList.size() < 6) { // how many numbers u need - it will 6
        int a = random.nextInt(49)+1; // this will give numbers between 1 and 50.

        if (!arrayList.contains(a)) {
            arrayList.add(a);
        }
    }

4

以下方法可用于生成唯一的随机数:

import java.util.HashSet;
import java.util.Random;

public class RandomExample {

    public static void main(String[] args) {
        Random rand = new Random();
        int e;
        int i;
        int g = 10;
        HashSet<Integer> randomNumbers = new HashSet<Integer>();

        for (i = 0; i < g; i++) {
            e = rand.nextInt(20);
            randomNumbers.add(e);
            if (randomNumbers.size() <= 10) {
                if (randomNumbers.size() == 10) {
                    g = 10;
                }
                g++;
                randomNumbers.add(e);
            }
        }
        System.out.println("Ten Unique random numbers from 1 to 20 are  : " + randomNumbers);
    }
}

3

一种聪明的方法是使用模中原根的指数。

例如,2是模101的一个原根,这意味着2的幂模101不重复地生成从1到100的所有数字:

2^0 mod 101 = 1
2^1 mod 101 = 2
2^2 mod 101 = 4
...
2^50 mod 101 = 100
2^51 mod 101 = 99
2^52 mod 101 = 97
...
2^100 mod 101 = 1

在Java代码中,你需要编写如下内容:
void randInts() {
int num=1;
for (int ii=0; ii<101; ii++) {
    System.out.println(num);
    num= (num*2) % 101;
    }
}

在特定模数下找到一个原根可能会很棘手,但是Maple的“primroot”函数可以帮助您完成这项任务。

有趣的是,但我们如何确保生成的序列是随机的呢?它似乎不是。在序列开头有1、2、4、8、16等数字似乎非常确定性。 - h4nek
这不是随机的...它是伪随机的。没有人知道如何生成真正的随机数。如果你不喜欢初始模式,可以使用更大的基数作为原根。 - A T - student
伪随机数是可以的。但是,在给定的“范围”内,原根数量和因此唯一序列的数量是有限的,特别是对于较小的范围。因此,似乎存在一个问题,例如始终具有根的幂的子序列。除非我们应用更多的花招,否则不会在多次运行中获得(可能)非常不同的序列。我想这取决于用例。无论如何,改变基数都是一个不错的升级,尽管它只是“移动”模式。 - h4nek

2

我来自另一个问题,该问题已被重复提交到这个问题(在 Java 中生成唯一的随机数)

  1. 将1至100的数字存储在数组中。

  2. 生成1至100之间的随机数作为位置,并返回array[position-1]以获取值

  3. 一旦使用了数组中的数字,请将该值标记为-1(无需维护另一个数组来检查此数字是否已使用)

  4. 如果数组中的值为-1,请再次获取随机数字以获取数组中的新位置。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接