Creating Random Numbers With No Duplicates in Java – 在Java中创建无重复的随机数

最后修改: 2022年 9月 18日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.绪论

In this quick tutorial, we’ll learn how to generate random numbers with no duplicates using core Java classes. First, we’ll implement a couple of solutions from scratch, then take advantage of Java 8+ features for a more extensible approach.

在这个快速教程中,我们将学习如何使用核心Java类生成无重复的随机数。首先,我们将从头开始实现几个解决方案,然后利用Java 8+的功能来获得更多的扩展性。

2. Random Numbers From a Small Range

2.小范围内的随机数

If the range of numbers we need is small, we can keep adding sequential numbers to a list until we reach size n. Then, we call Collections.shuffle(), which has linear time complexity. After that, we’ll end up with a randomized list of unique numbers. Let’s create a utility class to generate and use those numbers:

如果我们需要的数字范围很小,我们可以不断向列表中添加连续的数字,直到我们达到大小n然后,我们调用Collections.shuffle(),它具有线性时间复杂性。之后,我们最终会得到一个随机的唯一数字列表。让我们创建一个实用类来生成和使用这些数字。

public class UniqueRng implements Iterator<Integer> {
    private List<Integer> numbers = new ArrayList<>();

    public UniqueRng(int n) {
        for (int i = 1; i <= n; i++) {
            numbers.add(i);
        }

        Collections.shuffle(numbers);
    }
}

After constructing our object, we’ll have numbers from one to size in random order. Notice we’re implementing Iterator, so we’ll get a random number every time we call next(). Also, we can check if we have numbers left with hasNext(). So, let’s override them:

构建完我们的对象后,我们会有从1到size的数字,顺序随机。注意我们正在实现Iterator,所以我们每次调用next()时都会得到一个随机数字。另外,我们可以用hasNext()检查我们是否还有数字。所以,让我们来覆盖它们。

@Override
public Integer next() {
    if (!hasNext()) {
        throw new NoSuchElementException();
    }
    return numbers.remove(0);
}

@Override
public boolean hasNext() {
    return !numbers.isEmpty();
}

Consequently, remove() returns the first removed item from the list. Similarly, if we hadn’t shuffled our collection, we could pass it a random index. But, shuffling at construction time has the advantage of letting us know the whole sequence in advance.

因此,remove()返回列表中第一个被移除的项目。类似地,如果我们没有对我们的集合进行洗牌,我们可以传递给它一个随机索引。但是,在构造时进行洗牌的好处是让我们提前知道整个序列。

2.1. Putting It to Use

2.1.将其投入使用

To use it, we just choose how many numbers we want and consume them:

要使用它,我们只需选择我们想要的多少个数字,然后消耗它们。

UniqueRng rng = new UniqueRng(5);
while (rng.hasNext()) {
    System.out.print(rng.next() + " ");
}

This could result in output like:

这可能会导致像这样的输出。

4 1 2 5 3

3. Random Numbers From a Big Range

3.大范围内的随机数

We need a different strategy if we want a more extensive range of numbers, only using a few of them. First, we cannot rely on adding random numbers to an ArrayList because that could generate duplicates. So, we’ll use a Set because it guarantees unique items. Then, we’ll use the LinkedHashSet implementation because it maintains insertion order.

如果我们想要一个更广泛的数字范围,只使用其中的几个,我们需要一个不同的策略。首先,我们不能依靠向ArrayList添加随机数字,因为这可能会产生重复的数字。因此,我们将使用Set,因为它可以保证唯一的项目。然后,我们将使用LinkedHashSet实现,因为它可以保持插入的顺序。

This time, we’ll add elements to our set in a loop until we reach size. Also, we’ll use Random to generate random integers from zero to max:

这一次,我们将在一个循环中向我们的集合添加元素,直到我们达到size。另外,我们将使用Random来生成从零到max的随机整数:

public class BigUniqueRng implements Iterator<Integer> {
    private Random random = new Random();
    private Set<Integer> generated = new LinkedHashSet<>();

    public BigUniqueRng(int size, int max) {
        while (generated.size() < size) {
            Integer next = random.nextInt(max);
            generated.add(next);
        }
    }
}

Note we don’t need to check if a number already exists in our set because add() does this. Now, since we can’t remove items by index, we need the help of an Iterator to implement next():

请注意,我们不需要检查一个数字是否已经存在于我们的集合中,因为add()会这样做。现在,由于我们不能按索引删除项目,我们需要一个迭代器的帮助来实现next()

public Integer next() {
    Iterator<Integer> iterator = generated.iterator();
    Integer next = iterator.next();
    iterator.remove();
    return next;
}

4. Taking Advantage of Java 8+ Features

4.利用Java 8+功能的优势

While custom implementations are more reusable, we can create a solution using only Streams. Starting with Java 8, Random has an ints() method that returns an IntStream. We can stream it and impose the same requisites from earlier, like a range and a limit. Let’s combine these features and collect the results into a Set:

虽然自定义的实现更具可重用性,但我们可以只使用Streams创建一个解决方案。从Java 8开始,Random有一个ints()方法,可以返回一个IntStream。我们可以将其流化,并施加与先前相同的必要条件,如范围和限制。让我们结合这些功能,并将结果收集到Set

Set<Integer> set = new Random().ints(-5, 15)
  .distinct()
  .limit(5)
  .boxed()
  .collect(Collectors.toSet());

The traversed set could yield output like:

遍历的集合可以产生类似的输出。

-5 13 9 -4 14

With ints(), it’s even simpler to have a range starting from a negative integer. But, we must be careful not to end up with an infinite stream, which would happen if we didn’t call limit(), for example.

使用ints(),从一个负整数开始的范围就更简单了。但是,我们必须小心,不要最后出现一个无限的流,如果我们不调用limit(),就会出现这种情况。

5. Conclusion

5.总结

In this article, we wrote a couple of solutions to generate random numbers with no duplicates in two scenarios. First, we’ve made those classes iterable so we could easily consume them. Then, we created a more organic solution using streams.

在这篇文章中,我们写了几个解决方案,在两种情况下生成没有重复的随机数。首先,我们把这些类做成可迭代的,这样我们就可以很容易地消耗它们。然后,我们使用流创建了一个更有机的解决方案。

And as always, the source code is available over on GitHub.

一如既往,源代码可在GitHub上获得。