18 랜덤 라이브러리

김민영·2023년 1월 19일
0

C# 기초 프로그래밍

목록 보기
12/18

☃️ 랜덤 라이브러리

C# 코드에서 무작위의 수를 사용하기 위한 Random 라이브러리를 알아보자

1. 인스턴스 생성

1) Random 또한 클래스기에 인스턴스를 생성해 내부의 메소드들을 사용합니다.

Random random = new Random();

2. 무작위 정수 생성

▶ .Next() 메소드

1) .Next() 메소드를 이용합니다.

2) [0, Int32.MaxValue - 1]
: 따로 범위를 지정하지 않는 경우 0부터 int가 표현할 수 있는 최대 수 - 1까지의 범위 중 하나의 수를 반환합니다.

▶ 범위 지정하기

1) 최대값을 지정하여 무작위 정수 생성

  • 하나의 인수를 전달하는 경우, 그 수를 최대 범위로 하여 무작위의 수를 반환합니다.
// 0부터 100까지의 범위에서 무작위 정수를 반환하는 예시
int maxValue = 100;
int randomInteger2 = random.Next(maxValue + 1);

→ 마지막 값 - 1 까지 범위에 포함되기 때문에 의도대로 100을 범위 내에 포함하려면 의도한 최대 수 + 1을 인자로 넣어줘야 합니다.

2) 최소값과 최대값을 지정하여 무작위 정수 생성

  • 두개의 인수를 전달하는 경우, 첫번째 인자를 최소값 / 두번째 인자를 최대값으로 제한한 범위 내에서 무작위 정수를 반환합니다.
// 10부터 99까지의 범위에서 무작위 정수를 반환하는 예시
int minValue = 10;
int maxValue = 100;
int randomValue = random.Next(minValue, maxValue);
// [minValue, maxValue - 1] 범위에서 무작위 정수 생성

3. 무작위 실수 생성

▶ .NextDouble() 메소드

1) .NextDouble() 메소드를 이용합니다.

2) [0.0, 1.0)
: 따로 범위를 지정하지 않는 경우 0.0 이상 10.0 미만의 범위에서 무작위의 실수를 반환합니다.

▶ 범위 지정하기

예제 1) [0.0, 10.0) 중 무작위의 실수 구하기

Random random = new Random();

double randomNumber = random.NextDouble();
randomNumber *= 10;

예제 2) [-5.0, 3.0) 중 무작위의 실수 구하기

Random random = new Random();

double minValue = -5.0;
double maxValue = 3.0;
double startNumber = minValue;
double range = maxValue - minvalue;

double randomNumber = startNumber + random.NextDouble() * range;

→ 현재 예제의 경우 -5.0 ~ 3.0 사이의 범위에서 값을 구하는 것으로, 수를 구하려는 범위의 크기가 8.0입니다. 따라서 우선 Next.Double을 이용해 구할 수 있는 값의 범위를 늘리기 위해 예제 1)의 경우와 같이 range를 곱합니다.

→ 그런 다음 최소값을 더해주어 범위의 첫번째 수를 조절합니다. startNumber를 더해준 것이 위 코드에서 이에 해당하는 부분입니다.


☃️ 가챠 시스템 만들기

Random을 통해 얻는 무작위의 수는 각각 등장 확률이 비슷합니다. 하지만 게임 속 가챠의 경우 아이템, 캐릭터 등의 등급에 따라 등장 확률이 달라집니다. 이를 가중치 랜덤이라고 합니다. 가중치 랜덤 방식을 통해 가챠 시스템을 만들어보겠습니다.

1. Item 클래스 만들기

class Item
{
    public int Id;
    public string Name;
    public int Weight;  // 가중치 (Portion, 비율 혹은 Ratio 라고도 함)
}
  • 가챠를 통해 뽑을 Item 클래스를 간단히 만들었습니다.
  • Weight라는 멤버에 각 아이템의 가중치(비율)을 저장해 사용하겠습니다.

2. Item table 생성

Student[] itemTable =
{
   new Item{ Id = 1, Name = "A", Weight = 1},
   new Item{ Id = 2, Name = "B", Weight = 2 },
   new Item{ Id = 3, Name = "C", Weight = 3 },
   new Item{ Id = 4, Name = "D", Weight = 4 }
};
  • 전체 가중치 (total weight): 모든 아이템들의 가중치를 합산한 값
  • 각 아이템의 등장 확률(Probability): Weight / total weight
  • 즉 가중치가 4인 아이템의 등장 확률은 4 / 10 입니다.

▶ 가중치를 이용해 등장할 아이템을 선정하는 방법

  • 각 아이템은 가중치만큼의 숫자(Number)들을 가지게 됩니다.
    → 예를들어 가중치가 2인 B는 2와 3, 총 2개의 수를 가지며 가중치가 3인 C는 4, 5, 6 총 3개의 수를 가집니다.

  • 이후 구현할 과정에서 Random 클래스를 통해 전체 가중치 범위 내의 수를 무작위로 선정합니다.
    그 수(SelectedNumber)가 어떤 아이템의 숫자인지 (어떤 아이템의 범위에 속하는지) 판단하여 해당하는 아이템을 반환하는 방식으로 아이템을 선정하는 방법을 구현할 것입니다.

3. 전체 가중치 구하기

int totalWeight = 0;
for(int i = 0; i < itemTable.Length; ++i)
{
   totalWeight += itemTable[i].Weight;
}
  • 반복문을 통해 itemTable을 순회하며, 모든 Item 객체들의 Weight를 합산합니다.

4. 전체 가중치 범위 내 무작위의 수 선정하기

  • [1, totalWeight] 범위 중 임의의 수를 선정합니다.
  • 정수가 아닌 실수를 사용하며, 소수점 아래의 수는 반올림해 사용할 것입니다.
Random random = new Random();
int selectedNumber = (int)(1.0 + random.NextDouble() * (totalWeight - 1) + 0.5);
  • totalWeight 에서 1을 뺀 수를 곱하는 이유는 최대값(totalWeight) - 최소값(1)을 곱해 범위를 설정하기 위함입니다.
  • 1.0을 더하는 이유는 무작위 수를 선정하는 범위가 1부터 시작하기 때문입니다.
  • 0.5를 더하고 (int)를 통해 타입을 변환하여 반올림 한 정수를 구합니다.
    → 소수점 첫째자리를 기준으로 반올림하기 위해서는 첫째자리가 5 이상일 경우 올림 / 미만일 경우 내려야 합니다.
    → 소수점 이하 첫째자리가 5 이상인 경우 0.5를 더했을 때 1의자리 수가 증가하고, 5 미만인 경우 0.5를 더했을 때 1의자리 수가 증가하지 않습니다. 그 상태에서 실수를 int 타입으로 변환할 시 소수점 아래가 버려지기 때문에 의도한 반올림을 구현할 수 있습니다.

5. 어떤 아이템이 뽑힐지 판정하는 함수

Item Gacha(int selectedNumber, Item[] itemTable)
{
	Item item = null;
    int weight = 0;
    
    for(int i = 0; i < itemTable.Length; ++i)
    {
    	weight += itemTable[i].Weight;
        if(selectedNumber <= weight)
        {
        	student = itemTable[i];
            break;
        }
    }
    return item;
}
            
  • 반복문을 통해 itemTable을 순회하며 weight를 누적합니다.

  • selectedNumber와 weight를 비교하여 selectedNumber가 해당하는 범위를 판단합니다.
    → Random 클래스를 통해 선정된 selectedNumber가 해당 루프에 누적된 weight보다 작은 경우, 그 범위에 해당하는 아이템을 반환합니다.
  • 이미 이전 루프를 지나왔다면, 더 작은 누적 weight의 범위에는 당연히 포함되지 않으므로 if문의 조건식에 selectedNumber가 무엇보다 커야하는가에 대한 조건은 없어도 되는 것입니다.

아이템의 가중치가 작다면 그 아이템에 해당하는 Number의 개수도 적어집니다. 따라서 그 아이템에 해당하는 Number가 Random을 통해 선정될 가능성이 적어지는 것입니다.

: 여기서 저는 예시 속 가중치가 가장 적은 아이템에 해당하는 Number가 1이라는 것과, 반복문을 통해 누적된 weight보다 작은지 비교한다는 점에서 혼동이 와 그럼 1이 무조건 등장하는 것이 아닌가하는 생각을 했습니다.

하지만 이 반복문은 1부터 시작하여 모든 수를 weight와 비교하는 것이 아니라, 무작위로 선정된 selectedNumber를 weight와 비교하는 것이며 가중치가 적은 아이템의 Number는 그 수 자체가 적어 selectedNumber로 선정될 가능성이 적다는 것을 이해해야 합니다!

▶ 함수의 사용

1) Item 객체 하나

Item item = Gacha(selectedNumber, itemTable);
  • 알맞은 인자를 전달하면, Item 타입의 객체가 반환됩니다.

2) 실제로 각 아이템들이 선정된 확률 확인하기

int[] counts = new int[5];  // 각 아이템 별 뽑힌 횟수를 저장할 배열
							// Id가 1부터 시작하고, Id를 인덱스로 사용해 값을 저장할 것이기 때문에
                            // 실제 아이템 종류 수인 4보다 1 크게 배열을 생성하고,
                            // 0번 인덱스는 사용하지 않을 것

for(int i = 0; i < 1000; ++i)
{
   int selectedNumber = (int)(1.0 + random.NextDouble() * (totalWeight - 1) + 0.5);
   Item item = Gacha(selectedNumber, itemTable);
   counts[item.Id] += 1;  // 각 student마다 뽑힌 횟수를 저장
}
  • 아무리 가챠 횟수를 늘려도 결과를 확인했을 때 등장 확률이 설정한 확률과 완벽히 같아지지 않습니다.
    그 이유는 컴퓨터는 진정한 의미의 난수를 생성할 수 없기 때문입니다.

  • 의사난수: 컴퓨터가 생성하는 난수로, 난수처럼 보이게하는 패턴을 구현하여 생성

  • 최대한 설정한 확률과 비슷하게 하려면 의사난수 알고리즘을 균등분포 알고리즘으로 변경하여 사용할 수 있습니다. (하지만 C#에선 어려움)

+) 예제에서 사용한 방식은 구간확률 방식이며, 그 외에도 낙차확률, 보정확률, 묶음확률이 있습니다.

0개의 댓글