1. 소개

이 빠른 사용방법(예제)에서는 핵심 Java 클래스를 사용하여 중복 없이 난수를 생성하는 방법을 배웁니다. 먼저 처음부터 몇 가지 솔루션을 구현한 다음 보다 확장 가능한 접근 방식을 위해 Java 8+ 기능을 활용합니다.

2. 작은 범위의 난수

필요한 숫자의 범위가 작은 경우 크기 n 에 도달할 때까지 List에 순차적인 숫자를 계속 추가할 수 있습니다 . 그런 다음 선형 시간 복잡도 를 갖는 Collections.shuffle() 을 호출 합니다. 그런 다음 고유 번호의 무작위 List으로 끝납니다. 이러한 숫자를 생성하고 사용하는 유틸리티 클래스를 만들어 보겠습니다.

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);
    }
}

 개체를 구성한 후 랜덤의 순서 로 1부터 크기 까지 숫자를 갖게 됩니다. Iterator 를 구현 하고 있으므로 next() 를 호출할 때마다 난수를 얻습니다 . 또한 hasNext() 로 숫자가 남아 있는지 확인할 수 있습니다 . 따라서 이를 무시해 보겠습니다 .

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

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

결과적으로 remove() 는 List에서 첫 번째로 제거된 항목을 반환합니다. 마찬가지로 컬렉션을 섞지 않았다면 임의 인덱스를 전달할 수 있습니다. 하지만 생성 시 셔플링을 하면 전체 시퀀스를 미리 알 수 있다는 장점이 있습니다.

2.1. 사용하기

이를 사용하려면 원하는 숫자의 수를 선택하고 소비합니다.

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

결과는 다음과 같습니다.

4 1 2 5 3

3. 큰 범위의 난수

숫자의 일부만 사용하여 더 광범위한 범위의 숫자를 원한다면 다른 전략이 필요합니다.  첫째, 중복을 생성할 수 있기 때문에 ArrayList 에 난수를 추가하는 것에 의존할 수 없습니다 . 따라서 고유 항목을 보장하기 때문에 세트 를 사용합니다. 그런 다음 삽입 순서를 유지하기 때문에 LinkedHashSet 구현을 사용합니다.

이번에는 크기 에 도달할 때까지 루프에서 세트에 요소를 추가합니다 . 또한 Random 을 사용하여 0에서 최대 까지 랜덤의 정수를 생성합니다 .

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);
        }
    }
}

add() 가 이 작업을 수행 하기 때문에 집합에 숫자가 이미 존재하는지 확인할 필요가 없습니다 . 이제 인덱스로 항목을 제거할 수 없으므로 next() 를 구현 하려면 Iterator 의 도움이 필요합니다 .

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

4. Java 8+ 기능 활용

사용자 지정 구현이 더 재사용 가능하지만 Stream s 만 사용하여 솔루션을 만들 수 있습니다 . Java 8부터 Random 에는 IntStream 을 반환하는 ints() 메서드가 있습니다. 우리는 그것을 스트리밍하고 범위 및 제한과 같은 이전과 동일한 요구 사항을 부과할 수 있습니다. 이러한 기능을 결합 하고 결과를 집합 으로 수집 해 보겠습니다 .

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

순회 세트는 다음과 같은 출력을 생성할 수 있습니다.

-5 13 9 -4 14

ints()를 사용 하면 음의 정수에서 시작하는 범위를 갖는 것이 훨씬 더 간단합니다. 그러나 예를 들어 limit() 를 호출하지 않으면 발생할 수 있는 무한 스트림으로 끝나지 않도록 주의해야 합니다 .

5. 결론

이 기사에서는 두 가지 시나리오에서 중복되지 않는 난수를 생성하는 몇 가지 솔루션을 작성했습니다. 첫째, 우리는 이러한 클래스를 반복 가능하게 만들어 쉽게 사용할 수 있도록 했습니다. 그런 다음 스트림을 사용하여 보다 유기적인 솔루션을 만들었습니다.

그리고 항상 그렇듯이 소스 코드는 GitHub에서 사용할 수 있습니다 .

Generic footer banner