범위가 주어졌을 때 랜덤 함수를 사용하지 않고, 20개의 수를 랜덤으로 추첨해주는 프로그램을 작성해 보세요. (Hint: 시간)
라는 주제로 자바 프로그램을 통해 구현해보려 한다
자바에서 난수를 얻어내는 방법은 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번 뽑은 결과는 다음과 같다.
| 번호 | 빈도 | 번호 | 빈도 | 번호 | 빈도 | 번호 | 빈도 | 번호 | 빈도 |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 2641 | 11 | 2575 | 21 | 2555 | 31 | 2621 | 41 | 2718 |
| 2 | 1486 | 12 | 1478 | 22 | 1546 | 32 | 1506 | 42 | 1613 |
| 3 | 2280 | 13 | 2228 | 23 | 2302 | 33 | 2400 | 43 | 2255 |
| 4 | 2076 | 14 | 1874 | 24 | 1953 | 34 | 1928 | 44 | 2022 |
| 5 | 1727 | 15 | 1844 | 25 | 1761 | 35 | 1812 | 45 | 1665 |
| 6 | 2027 | 16 | 1955 | 26 | 2101 | 36 | 1976 | 46 | 2037 |
| 7 | 1970 | 17 | 1923 | 27 | 1965 | 37 | 1978 | 47 | 2051 |
| 8 | 1798 | 18 | 1785 | 28 | 1717 | 38 | 1773 | 48 | 1771 |
| 9 | 2358 | 19 | 2438 | 29 | 2360 | 39 | 2388 | 49 | 2419 |
| 10 | 1546 | 20 | 1630 | 30 | 1595 | 40 | 1661 | 50 | 1584 |
| 번호 | 빈도 | 번호 | 빈도 | 번호 | 빈도 | 번호 | 빈도 | 번호 | 빈도 |
|---|---|---|---|---|---|---|---|---|---|
| 51 | 2731 | 61 | 2627 | 71 | 2708 | 81 | 2691 | 91 | 2713 |
| 52 | 1560 | 62 | 1532 | 72 | 1520 | 82 | 1597 | 92 | 1556 |
| 53 | 2422 | 63 | 2296 | 73 | 2329 | 83 | 2365 | 93 | 2291 |
| 54 | 1918 | 64 | 2045 | 74 | 1923 | 84 | 1956 | 94 | 1938 |
| 55 | 1814 | 65 | 1650 | 75 | 1837 | 85 | 1777 | 95 | 1807 |
| 56 | 2017 | 66 | 2013 | 76 | 1971 | 86 | 2097 | 96 | 1962 |
| 57 | 1922 | 67 | 1944 | 77 | 1885 | 87 | 2032 | 97 | 2041 |
| 58 | 1763 | 68 | 1758 | 78 | 1678 | 88 | 1794 | 98 | 1846 |
| 59 | 2381 | 69 | 2422 | 79 | 2369 | 89 | 2398 | 99 | 2388 |
| 60 | 1577 | 70 | 1651 | 80 | 1580 | 90 | 1624 | 100 | 1612 |
그리고 pickUniqueNumUsingTimeHash()를 통해, 시간과 해쉬함수를 통해 10,000번 뽑은 값은 다음과 같다.
| 번호 | 빈도 | 번호 | 빈도 | 번호 | 빈도 | 번호 | 빈도 | 번호 | 빈도 |
|---|---|---|---|---|---|---|---|---|---|
| 1 | 2010 | 11 | 1954 | 21 | 2051 | 31 | 1954 | 41 | 1971 |
| 2 | 2015 | 12 | 2036 | 22 | 1926 | 32 | 2000 | 42 | 1946 |
| 3 | 2025 | 13 | 2010 | 23 | 2031 | 33 | 1965 | 43 | 1926 |
| 4 | 2024 | 14 | 1981 | 24 | 1998 | 34 | 2004 | 44 | 2006 |
| 5 | 2033 | 15 | 2011 | 25 | 2011 | 35 | 2028 | 45 | 1999 |
| 6 | 2065 | 16 | 1935 | 26 | 2037 | 36 | 2004 | 46 | 2059 |
| 7 | 2035 | 17 | 1962 | 27 | 2007 | 37 | 1964 | 47 | 1987 |
| 8 | 1957 | 18 | 2009 | 28 | 1973 | 38 | 1999 | 48 | 1959 |
| 9 | 1979 | 19 | 2038 | 29 | 1934 | 39 | 1984 | 49 | 1914 |
| 10 | 2006 | 20 | 2046 | 30 | 2013 | 40 | 1976 | 50 | 1950 |
| 번호 | 빈도 | 번호 | 빈도 | 번호 | 빈도 | 번호 | 빈도 | 번호 | 빈도 |
|---|---|---|---|---|---|---|---|---|---|
| 51 | 2010 | 61 | 2039 | 71 | 2028 | 81 | 2049 | 91 | 1996 |
| 52 | 2011 | 62 | 2014 | 72 | 1990 | 82 | 1982 | 92 | 2086 |
| 53 | 2051 | 63 | 1975 | 73 | 2053 | 83 | 1993 | 93 | 2003 |
| 54 | 1995 | 64 | 1968 | 74 | 2009 | 84 | 2043 | 94 | 2023 |
| 55 | 2014 | 65 | 2037 | 75 | 2003 | 85 | 2017 | 95 | 1896 |
| 56 | 1969 | 66 | 2011 | 76 | 2068 | 86 | 2013 | 96 | 1982 |
| 57 | 2076 | 67 | 2061 | 77 | 1977 | 87 | 2025 | 97 | 1970 |
| 58 | 2027 | 68 | 2043 | 78 | 1969 | 88 | 1998 | 98 | 2048 |
| 59 | 2017 | 69 | 1878 | 79 | 1960 | 89 | 2025 | 99 | 1960 |
| 60 | 1973 | 70 | 1951 | 80 | 1950 | 90 | 2004 | 100 | 2053 |
확실히 시간과 해쉬함수 두개를 동시에 활용한 것이 더 고르게 뽑힌 것을 알 수 있다.