Midnight Coder's Lounge

[Java] Random으로 Hello World 출력하기 본문

Language

[Java] Random으로 Hello World 출력하기

AtomicLiquors 2022. 10. 14. 18:50

 개요 

public static void main(String[] args){

    System.out.println(
        randomString(-229985452) + " " + randomString(-147909649)
    );
    
}


public static String randomString(int i)
{
    Random ran = new Random(i);
    StringBuilder sb = new StringBuilder();
    while (true)
    {
        int k = ran.nextInt(27);
        if (k == 0)
            break;

        sb.append((char)('`' + k));
    }

    return sb.toString();
}

 

이 코드를 실행하면 항상 "hello world"가 출력이 된다는 걸 알고 계신가요?

 

 

 

 

[출력 결과]

hello world

 

 

 

 

(이 글은 2013년 5월 3일 Stackoverflow에 등록된 질의내용을 바탕으로 작성하였습니다.)

https://stackoverflow.com/questions/15182496/why-does-this-code-using-random-strings-print-hello-world

 

Why does this code using random strings print "hello world"?

The following print statement would print "hello world". Could anyone explain this? System.out.println(randomString(-229985452) + " " + randomString(-147909649)); And randomString() looks like th...

stackoverflow.com

 

 

 


 

 상세 

Random은 사실 완벽한 random이 아니다.

일반적으로 컴퓨터 프로그래밍으로 만들어지는 난수는 “의사 난수 Pseudo-Random Number ”입니다.

특정한 숫자 값을 알고리즘에 투입하면, 그 결과로 숫자들이 일정한 순서로 생성되는 원리입니다.

이 때, 알고리즘에 투입한 숫자를 ‘시드 seed ’라고 합니다.

 

똑같은 시드를 투입하면, 결과도 똑같이 정해져 있기 때문에 사실상 완전한 랜덤이라고 보기는 어렵죠.

그냥 난수가 아니라 ‘의사’ 난수라고 부르는 이유는 이 때문입니다.

 

이는 Java도 마찬가지인데요,
java.util.Random 클래스는 인스턴스를 생성할 때 시드 역할을 할 long형 정수를 매개변수로 받도록 설계되어 있습니다.

 

public Random(long seed) {
        if (getClass() == Random.class)
            this.seed = new AtomicLong(initialScramble(seed));
        else {
            // subclass might have overridden setSeed
            this.seed = new AtomicLong();
            setSeed(seed);
        }
    }

< long형 매개변수 seed를 갖는 Random 클래스의 생성자 >

 

 


 

"If two instances of Random are created with the same seed, and the same sequence of method calls is made for each, they will generate and return identical sequences of numbers. In order to guarantee this property, particular algorithms are specified for the class Random."

 

서로 다른 Random 클래스의 인스턴스가 똑같은 시드 값을 갖고, 똑같은 절차로 메서드 호출이 이루어졌다면,
서로 같은 숫자들을 순서대로 만들고 반환합니다. 이와 같은 특성을 갖도록 하기 위하여,
Random 클래스에는 특정한 알고리즘들이 적용되어 있습니다.

 

< Java 공식 문서의 Random 클래스 설명 >

http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Random.html

 

 

 

 

public static String randomString(int i)
{
    Random ran = new Random(i);
		...
}

public static void main(String[] args){

    System.out.println(
        randomString(-229985452) + " " + randomString(-147909649)
    );

}

앞서 살펴 본 코드에서는 사용자 정의 메서드 randomString()을 선언하였습니다.
잘 살펴보면 이 randomString() 역시 정수 -229985452, -147909649를 매개변수로 받아서,
이것을 Random 클래스의 인스턴스를 생성하는 시드 값으로 사용하는 것을 확인할 수 있습니다.

 

조금 전에 말씀드린 것처럼,
특정한 시드를 Random의 매개변수로 넣어주면 항상 동일한 값들이 출력됩니다.

 

실제로 그럴까요? Random(-229985452)가 어떤 값을 갖는지 확인해보도록 합시다.

Random 클래스의 .nextInt() 메서드를 이용하면, 어떤 숫자들이 만들어지는지 순서대로 확인할 수 있습니다.

Random의 인스턴스 r이 만들어낸 숫자를 정수형 변수 k에 저장하고, 이를 출력하는 코드를 실행하겠습니다.

10번 정도만 반복 실행해 봅시다.

 

 

[테스트 코드]

Random r = new Random(-229985452);

for(int i = 0; i < 10; i++){
	int k = r.nextInt();
	System.out.println(k);
}

 

출력 결과는 다음과 같습니다.

 

 

[출력 결과]

-755142161
-1073255141
-369383326
1592674620
-1524828502
-1401688822
319683896
-1514795721
-1353069796
-1206847205

한 번 프로그램을 종료하고 다시 실행해 보세요.
시드 값을 바꾸지 않는 이상 똑같이 이 숫자들이 다시 출력될 것입니다.

 

 

 

 의문점 

Q. 우리가 실제로 Random()을 이용할 때는 아무런 매개변수를 넣지 않습니다.
그럼 아무런 시드 값이 없을텐데, 그럴 때는 어떻게 작동하는 건가요?

Random r = new Random();

 

 

A. "현재 시간”을 이용하여 시드가 만들어집니다.

public Random() {
    this(seedUniquifier() ^ System.nanoTime());
}

 

Random 클래스의 인스턴스가 아무런 매개변수 없이 생성된다면,

Random 클래스의 생성자는 System.nanoTime()을 이용해 JVM으로부터 현재 시간을 받아옵니다.

이 때, 이 시간 값은 십억 분의 1초(nanosecond) 단위까지 세분화된 값입니다.

추가적으로 seedUniquifier()라는 메서드에 내장된 알고리즘을 작동시키고, 그 값에 현재 시간 값을 제곱합니다.

 

이렇게 하면 Random 클래스가 여러 차례 호출되더라도
웬만해서는 똑같은 시드 값이 생성되지 못할 것이고, 
그 결과로 매번 서로 다른 숫자들이 생성될 것이기 때문에,
프로그램 사용자가 보기에 '무작위적'이라고 생각될 만한 결과가 나타나게 되겠지요.

 

 

 

Q. Random()이 아니라 Math.random() 써도 그런가요?

 

A. Math.random()도 똑같은 원리로 작동됩니다.
메서드 정의를 들여다보면, Math.random()도 결국 이 Random 클래스를 이용하고 있다는 것을 알 수 있습니다.

 

 

public static double random() {
    return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}

<Math 클래스 내부의 random() 메서드 정의>

'RandomNumberGeneratorHolder'라는 클래스의 'randomNumberGenerator' 속성을 호출합니다.

 

private static final class RandomNumberGeneratorHolder {
    static final RandomrandomNumberGenerator= new Random();
}

<RandomNumberGeneratorHolder 클래스 내부의 randomNumberGenerator 속성 정의>

'RandomrandomNumberGenerator' 역시 Random 클래스의  인스턴스라는 것을 알 수 있습니다.

 


 

 

 Random으로 Hello World 출력하기 

[시드를 넣고 숫자 생성하기]

Random r = new Random(-229985452);

for(int i = 0; i < 10; i++){
	int k = r.nextInt();
	System.out.println(k);
}
-755142161
-1073255141
-369383326
1592674620
-1524828502
-1401688822
319683896
-1514795721
-1353069796
-1206847205

이제 Random 클래스가 특정한 시드 값에 대해 항상 동일한 결과를 출력한다는 것을 알게 되었습니다.

 

다시 Hello World를 출력하는 과정을 따라가 봅시다.
앞서 Random(-229985452)의 실행 결과를 확인하였습니다.
제법 큰 숫자들이 나왔는데요,

 

.nextInt(27);

코드를 조금 수정하여, nextInt() 메서드에 정수 매개변수를 넣겠습니다.
그렇게 하면 Random 클래스가 만들어내는 값의 최대 크기를 제한할 수 있습니다.
위와 같이 매개변수로 27을 넣어주면, 생성되는 난수의 범위는 0 ≤ k < 27 이 됩니다.

 

 

 

 

[테스트 코드]

Random r = new Random(-229985452);

for(int i = 0; i < 10; i++){
	int k = r.nextInt(27);
	System.out.println(k);
}

 

[출력 결과]

8
5
12
12
15
0
4
21
9
2

수정 결과 실제로 0~20 안팎의 작은 수들이 일정 순서대로 출력되었습니다.

같은 코드를 Random(-147909649)에도 적용해 보도록 합시다.

 

 

[테스트 코드]

Random r = new Random(-147909649);

for(int i = 0; i < 10; i++){
    int k = r.nextInt(27);
    System.out.println(k);	
}

 

[출력 결과]

23
15
18
12
4
0
16
6
5
25

마찬가지로 일련의 0~20 내외의 숫자들이 출력되었습니다.

 

 

뒷 내용을 보면 아시겠지만, 이 숫자들 중에서 필요한 숫자는 0 이전에 나온 숫자들 뿐입니다.

반복문을 수정해 주겠습니다. 10회 반복되는 for문을, k의 값이 0이 되면 종료되는 while문으로 수정해 줍시다.

 

 

[테스트 코드]

Random r = new Random(-147909649);

while(true){
    int k = r.nextInt(27);
    if (k == 0)
    	break;
    System.out.println(k);
}

 

[출력 결과]

23
15
18
12
4

 

 

 

[숫자를 문자로 변환하기]

프로그래밍 언어에서, 정수형 숫자는 각각 특정한 알파벳이나 특수 기호와 짝을 이루고 있습니다.

각 숫자가 어떤 기호에 해당하는지는 ‘아스키 코드’라는 표준을 따르고 있습니다.

https://www.ascii-codes.com/

*American Standard Code for Information Interchange (ASCII)

1963년부터 미국에서 통신용으로 사용되어 온 문자 인코딩 시스템입니다.

 

 

'`' + k  

백틱 문자(’)의 아스키 코드는 96입니다.

여기에 앞서 우리가 Random으로 얻어낸 숫자 k를 더하는 연산을 실행할 것입니다.

Java 컴파일러에 의해 백틱 문자는 정수형으로 변환되고,

연산의 결과는 k에 96을 더한 값이 됩니다.

 

 

[테스트 코드]

Random r = new Random(-229985452);

for(int i = 0; i < 10; i++){
    int k = r.nextInt(27);
    System.out.println('`' + k );
}

 

[출력 결과]

104 // 8  + 96 
101 // 5  + 96 
108 // 12 + 96
108 // 12 + 96
111 // 15 + 96

 

 

 

(char)('`' + k)

이 숫자들을 문자형 char로 변환하면,

이번에는 각 숫자의 아스키 코드에 해당하는 문자가 출력이 될 것입니다.

 

 

[테스트 코드]

Random r = new Random(-229985452);

while(true){
      int k = r.nextInt(27);
      if (k == 0)
          break;
      System.out.println((char)('`' + k));
  }

 

[출력 결과]

h //ASCII 코드 104번
e //ASCII 코드 101번
l //ASCII 코드 108번
l //ASCII 코드 108번
o //ASCII 코드 111번

hello’의 철자들이 나왔네요!

 

 

Random(-147909649)에도 같은 코드를 실행해 봅시다.
역시 ‘world’의 철자들이 출력될 것입니다.

 

 

[테스트 코드]

Random r = new Random(-147909649);

while(true){
      int k = r.nextInt(27);
      if (k == 0)
          break;
      System.out.println((char)('`' + k));
  }

 

[출력 결과]

w  //ASCII 코드 119번
o  //ASCII 코드 111번
r  //ASCII 코드 114번
l  //ASCII 코드 108번
d  //ASCII 코드 100번

 

 

 

StringBuilder sb = new StringBuilder();
sb.append((char)('`' + k));

return sb.toString();

이제 지금까지 작성한 테스트 코드를 바탕으로,

메서드 randomString을 만들어 주겠습니다.

StringBuilder를 생성하여 문자들을 저장한 다음,

StringBuilder에 저장된 문자들을 return값으로 반환하게 될 것입니다.

 

 

[메서드 randomString]

public static String randomString(int i)
{
    Random ran = new Random(i);
    StringBuilder sb = new StringBuilder();
    while (true)
    {
        int k = ran.nextInt(27);
        if (k == 0)
            break;

        sb.append((char)('`' + k));
    }

    return sb.toString();
}

 

[randomString 호출]

System.out.println(randomString(-229985452) + " " + randomString(-147909649));

 

[출력 결과]

hello world

 

hello world가 출력되었습니다!

 

 


 

 

 마치며 

이상으로 Random에 특정 시드를 대입하여 hello world를 출력하는 코드를 살펴보았습니다.

그렇다면 Random을 이용하여, hello world 외에도 다른 문자를 출력할 수 있을까요?

특정 문자열을 만들어내는 Random 시드를 찾아내는 코드를 소개하며 마무리하도록 하겠습니다.

 

코드 제공 :

Denis Tulskiy @StackOverflow, 2013.05.03

import java.util.*;

public class SeedGen {

    public static void main(String[] args) {
        long time = System.currentTimeMillis();
        generate("stack");
        generate("over");
        generate("flow");
        generate("rulez");

        System.out.println("Took " + (System.currentTimeMillis() - time) + " ms");
    }

    private static void generate(String goal) {
        long[] seed = generateSeed(goal, Long.MIN_VALUE, Long.MAX_VALUE);
        System.out.println(seed[0]);
        System.out.println(randomString(seed[0], (char) seed[1]));
    }

    public static long[] generateSeed(String goal, long start, long finish) {
        char[] input = goal.toCharArray();
        char[] pool = new char[input.length];
        label:
        for (long seed = start; seed < finish; seed++) {
            Random random = new Random(seed);

            for (int i = 0; i < input.length; i++)
                pool[i] = (char) random.nextInt(27);

            if (random.nextInt(27) == 0) {
                int base = input[0] - pool[0];
                for (int i = 1; i < input.length; i++) {
                    if (input[i] - pool[i] != base)
                        continue label;
                }
                return new long[]{seed, base};
            }

        }

        throw new NoSuchElementException("Sorry :/");
    }

    public static String randomString(long i, char base) {
        System.out.println("Using base: '" + base + "'");
        Random ran = new Random(i);
        StringBuilder sb = new StringBuilder();
        for (int n = 0; ; n++) {
            int k = ran.nextInt(27);
            if (k == 0)
                break;

            sb.append((char) (base + k));
        }

        return sb.toString();
    }
}

 

[출력 결과]

-9223372036808280701
Using base: 'Z'
stack
-9223372036853943469
Using base: 'b'
over
-9223372036852834412
Using base: 'e'
flow
-9223372036838149518
Using base: 'd'
rulez
Took 3945 ms

 

참고사항 :

이 코드에 작성된 RandomString() 메서드는 앞서 보았던 RandomString() 메서드와는 조금 다른 점이 있습니다.

실제 코드를 테스트해 보실 분들은 참고하시기 바랍니다.

  • 매개변수로 long형 시드 번호를 받습니다.
  • 원하는 결과를 얻기 위해, 백틱(`) 외에도 다양한 문자를 더할 수 있도록 변형되어 있습니다.

 

 

[참고자료]

- https://stackoverflow.com/questions/15182496/why-does-this-code-using-random-strings-print-hello-world

 

Why does this code using random strings print "hello world"?

The following print statement would print "hello world". Could anyone explain this? System.out.println(randomString(-229985452) + " " + randomString(-147909649)); And randomString() looks like th...

stackoverflow.com

- https://mathbits.com/JavaBitsNotebook/LibraryMethods/RandomGeneration.html

 

Java Random Generation - JavaBitsNotebook.com

Computers can be used to simulate the generation of random numbers. This random generation is referred to as a pseudo-random generation.  These created values are not truly "random" because a mathematical formula is used to generate the values. The "rando

mathbits.com

- http://mwultong.blogspot.com/2007/01/java-nextint.html

 

Java/자바] nextInt() 메소드 사용법

Random 클래스의 nextInt() 메소드에 파라미터를 입력하지 않으면 int형 표현 범위(-2147483648 ~ 2147483647)의 모든 영역에서 랜덤한 숫자가 나옵니다. 그런데 int형 파라미터를 입력하면, 0에서 그 파라미

mwultong.blogspot.com

 

FThompson, jpmc26, Eng.Fouad @ Stackoverflow

Comments