랜덤함수 없이 랜덤하게 뽑기

irenett·2025년 1월 13일
post-thumbnail

범위가 주어졌을 때 랜덤 함수를 사용하지 않고, 20개의 수를 랜덤으로 추첨해주는 프로그램을 작성해 보세요. (Hint: 시간)
라는 주제로 자바 프로그램을 통해 구현해보려 한다

Java에서 Random 구현 방법

자바에서 난수를 얻어내는 방법은 Random클래스를 활용하는 방법과 Math클래스를 활용하는 방법 2가지가 있다.

Random 클래스Math.random()
사용 방법1. java.util.Random 객체 생성
2. 제공 메서드(nextInt(), nextDouble() 등) 호출
Math.random() 메서드 직접 호출
대표 메서드- nextInt()
- nextInt(int bound)
- nextLong()
- nextDouble()
0.0 이상 1.0 미만의 double 반환
시드(Seed) 지정 여부O (new Random(시드값))X (내부적으로 Random 인스턴스를 사용하나 시드 지정 불가)
반환 범위메서드마다 다름0.0 이상 1.0 미만의 double 값만 반환
용도- 여러 번 난수를 얻어야 할 때
- 시드를 지정해 동일 난수 패턴을 재현할 때
- 정수, 실수, 논리형 등 다양한 형태 난수가 필요할 때
- 간단히 하나의 난수가 필요할 때
- 0.0 이상 1.0 미만 범위의 실수가 필요할 때
특징/차이점1. Random 객체를 여러 번 재활용 가능
2. 다양한 범위와 형태의 난수 생성 메서드를 제공
3. 시드값 제어 가능
1. 인스턴스 생성 없이 간단히 호출
2. double 타입 난수만 반환
3. 시드 지정 불가

실 구현

import java.util.HashSet;
import java.util.Set;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.nio.ByteBuffer;

public class Main {
    public static void main(String[] args) {
        
        int[] frequency = new int[101];
        for (int i = 0; i < 10000; i++) {
            //20번 넘게 뽑아서 
            //UniqueNum이 고르게 뽑히는지 검증
            pickUniqueNumUsingTimeHash(1, 100, 20).stream()
            .mapToInt(Integer::intValue)
            .forEach(num -> frequency[num - 1]++);
        }
        
        for (int i = 1; i <= 100; i++) {
            System.out.println(i + " => " + frequency[i-1]);
        }
    }
    
    public static Set<Integer> pickUniqueNumUsingOnlyTime(int min, int max, int count) {
        Set<Integer> result = new HashSet<>();

        while (result.size() < count) {
            // 현재 시간을 이용해 난수 비슷하게 생성
            long currentTime = System.nanoTime();
            
            // 범위에 맞게 연산 적용 
            // 음수를 막기 위해 절댓값 처리
            int number = (int)(Math.abs(currentTime) % (max - min + 1)) + min;
            
            result.add(number);
        }

        // System.out.println("추첨된 20개 숫자: " + result);
        return result;
    }
    
    public static Set<Integer> pickUniqueNumUsingTimeHash(int min, int max, int count) {
        Set<Integer> result = new HashSet<>();

        while (result.size() < count) {
            try {
                // 현재 시간 얻기
                long currentTime = System.nanoTime();

                // 얻은 시간 값을 바이트 배열로 변환
                byte[] inputBytes = ByteBuffer.allocate(Long.BYTES).putLong(currentTime).array();

                // SHA-256 해시 함수로 해싱
                MessageDigest md = MessageDigest.getInstance("SHA-256");
                byte[] hashBytes = md.digest(inputBytes);

                // 나온 해시(32바이트) 중 앞 4바이트를 int로 변환 (int 범위가 4바이트)
                // 범위 자체는 어디를 하든 상관없음음
                int hashedInt = ByteBuffer.wrap(hashBytes, 0, 4).getInt();

                // 범위에 맞게 뽑기
                int number = Math.abs(hashedInt) % (max - min + 1) + min;
                
                result.add(number);

                // 숫자가 너무 빠른 시간에 반복해서 생기는 것 방지용
                // Thread.sleep(1);

            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            }
        }

        // System.out.println("추첨된 20개 숫자: " + result);
        return result;
    }
}

pickUniqueNumUsingOnlyTime()인 함수로, 시간만을 사용해서 0,000번 뽑은 결과는 다음과 같다.

번호빈도번호빈도번호빈도번호빈도번호빈도
12641112575212555312621412718
21486121478221546321506421613
32280132228232302332400432255
42076141874241953341928442022
51727151844251761351812451665
62027161955262101361976462037
71970171923271965371978472051
81798181785281717381773481771
92358192438292360392388492419
101546201630301595401661501584
번호빈도번호빈도번호빈도번호빈도번호빈도
512731612627712708812691912713
521560621532721520821597921556
532422632296732329832365932291
541918642045741923841956941938
551814651650751837851777951807
562017662013761971862097961962
571922671944771885872032972041
581763681758781678881794981846
592381692422792369892398992388
6015777016518015809016241001612

그리고 pickUniqueNumUsingTimeHash()를 통해, 시간해쉬함수를 통해 10,000번 뽑은 값은 다음과 같다.

번호빈도번호빈도번호빈도번호빈도번호빈도
12010111954212051311954411971
22015122036221926322000421946
32025132010232031331965431926
42024141981241998342004442006
52033152011252011352028451999
62065161935262037362004462059
72035171962272007371964471987
81957182009281973381999481959
91979192038291934391984491914
102006202046302013401976501950
번호빈도번호빈도번호빈도번호빈도번호빈도
512010612039712028812049911996
522011622014721990821982922086
532051631975732053831993932003
541995641968742009842043942023
552014652037752003852017951896
561969662011762068862013961982
572076672061771977872025971970
582027682043781969881998982048
592017691878791960892025991960
6019737019518019509020041002053

확실히 시간과 해쉬함수 두개를 동시에 활용한 것이 더 고르게 뽑힌 것을 알 수 있다.

0개의 댓글