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上获得。