PS, 언어 공부/JAVA

[JAVA] 가변 배열에서의 확률 뽑기

Emil :) 2021. 1. 27. 16:21
728x90
반응형
이 글은 Notion에서 작성 후 재편집한 포스트입니다.

목차


개요


업무를 진행하면서 뽑기 확률로 진행되는 이벤트에 관한 로직을 짜야했다.
고정적인 값으로 들어오는 확률로 뽑는건 너무 단순해서 구글링하면 많이 나오지만, 가변리스트의 경우는 생각보다 별로 없어서
그냥 나름대로 한번 만들어봤다. 짱구를 좀 굴려봤다.

확률 알고리즘이 대충 어떤느낌으로 돌아가는지만 알아보고 나머지는 직접해봤다.

혹시 더 좋은 방법이 있다면 댓글 부탁드립니다.

참고


yoonbumtae.com/?p=518

 

Java 예제: 랜덤박스 (Math.random 이용) - BGSMM

일본산 온라인 과금 게임같은 경우 랜덤박스로 카드를 뽑을 수 있는 시스템이 있습니다. 여기서 나오는 카드의 등급은 희소성을 기준으로 SSR(Super-super Rare), SR(Super Rare), R(Rare) 등 총 3개의 등급으

yoonbumtae.com

예제 GIT 주소


github.com/kkkapuq/JAVA_PS/blob/main/src/Ps_210127_1.java

 

kkkapuq/JAVA_PS

자바 알고리즘 공부. Contribute to kkkapuq/JAVA_PS development by creating an account on GitHub.

github.com

진행 과정


1. 로직 설계 및 구현


고정된 값 안에서의 확률 뽑기 (참고 링크도 그렇게 되있다.)는 굉장히 단순한 로직이라, 별로 어려울게 없다.
그리고 구글링해보면 대부분 예제의 확률이 int, 즉 정수로 설정해놓은 예제가 많더라.

물론 int만으로만 하면 문제는 없지만 소수점까지 가야 더 정확한 확률이 되지않을까? 해서 나름대로 구상을 좀 해봤다.

기본적인 로직은 다음과 같다.

1. 랜덤값을 뽑고, 소수 둘째자리까지 절삭한다.
2. 해당 랜덤값이 특정 수 사이에 있다면, 그 배열의 값을 출력해준다.
3. 만약, 랜덤값이 100.00 으로 나온다면, 가장 마지막 인덱스의 값으로 정해준다.

왜 이렇게 하냐면.. 코드로 보는게 더 설명이 잘 될것이다.

double tmpRandom = (Math.random() * 100);
double tmpRatePrev = 0, tmpRateNext = 0;
int result = 0;
//소수 둘째자리까지 절삭
tmpRandom = Math.round(tmpRandom * 100) / 100.0;

System.out.println("설정된 랜덤값" + tmpRandom);

HashMap<String, String> map = new HashMap<String, String>();
ArrayList<HashMap<String, String>> list = new ArrayList<HashMap<String,String>>();

랜덤값을 뽑아내고, 절삭하고, 로직구현에 필요한 재료들을 세팅해주는 구간이다.

확률과 그에 해당하는 값은 Hashmap<String, String> 형태로 구현했고, 추후 값이나 확률은 Double로 형변환을 해주도록 하자.

map.put("rate", "20");
map.put("value", "100");
list.add(map);
//map.clear();
//처음에 clear를 썼는데, list에 삽입된 map까지 clear 해버려서 에러가 났다. 새로 선언해줘야함!
map = new HashMap<String, String>();

map.put("rate", "20");
map.put("value", "200");
list.add(map);
map = new HashMap<String, String>();

map.put("rate", "20");
map.put("value", "300");
list.add(map);
map = new HashMap<String, String>();

map.put("rate", "10");
map.put("value", "500");
list.add(map);
map = new HashMap<String, String>();

map.put("rate", "10");
map.put("value", "1000");
list.add(map);
map = new HashMap<String, String>();

map.put("rate", "10");
map.put("value", "1500");
list.add(map);
map = new HashMap<String, String>();

map.put("rate", "5");
map.put("value", "2000");
list.add(map);
map = new HashMap<String, String>();

map.put("rate", "5");
map.put("value", "3000");
list.add(map);
map = new HashMap<String, String>();

map에 본인이 원하는 확률을 rate에, value에 값을 집어넣어 주도록 하자.
당연하게도, 확률이 100에 딱 맞게 설정해줘야 한다. 만약 아닐경우엔 예외처리를 따로 해주도록 하자.
본 포스팅에선 로직만 설명할거라 따로 예외처리는 안했다.

위에 주석에도 써놨지만, clear() 함수를 쓰면 모두 list에 집어넣었던 값들도 초기화가 되버린다.
따라서 new 를 써서 재정의 해주도록 하자.
에러가 자꾸 나길래 여기서 발생한 문제였다..

for (int i = 0; i < list.size(); i++) {
    if (tmpRandom == 100) {
        //만약 난수가 100이라면 가장 마지막에있는 list 인덱스에 있는 value 적용
        result = Integer.parseInt(list.get(list.size() - 1).get("value"));
        break;
    } else {
        double rate = Double.parseDouble(list.get(i).get("rate"));
        tmpRateNext = tmpRatePrev + rate;
        if (tmpRandom >= tmpRatePrev && tmpRandom < tmpRateNext) {
            result = Integer.parseInt(list.get(i).get("value"));
            break;
        } else {
            tmpRatePrev = tmpRateNext;
        }
    }
}

본 포스팅의 핵심 로직이다. 뭔가 복잡해보이는데, 막상 이해하고나면 별거 아니었다.

for (int i = 0; i < list.size(); i++) {
    if (tmpRandom == 100) {
        //만약 난수가 100이라면 가장 마지막에있는 list 인덱스에 있는 value 적용
        result = Integer.parseInt(list.get(list.size() - 1).get("value"));
        break;
    }

먼저 for문을 lise의 size만큼 돈다.
위에서 말했던 것처럼, 랜덤으로 설정한 수가 100.00 이 나온다면 가장 마지막 인덱스의 값이 출력된다.
왜냐하면, 지금 이 로직은 0부터 n%를 계속 더해서 100이 채워질 때까지 for문을 도는 방식이다.

따라서, 만약 100이라면 for문의 가장 마지막 횟수까지 갔을 것이므로, 100일 경우에만 이렇게 예외처리를 해주도록 한다. 100일 경우엔 아래에 n% 이상 n% 미만 이라는 로직을 타지 못하기 때문이다.

else {
        double rate = Double.parseDouble(list.get(i).get("rate"));
        tmpRateNext = tmpRatePrev + rate;
        if (tmpRandom >= tmpRatePrev && tmpRandom < tmpRateNext) {
            result = Integer.parseInt(list.get(i).get("value"));
            break;
        } else {
            tmpRatePrev = tmpRateNext;
        }
    }

이제 100이 아닌 다른 수가 나왔을 때를 처리해주자.

rate는 현재 for문을 돌고있는 i번째 list 인덱스의 rate키의 값을 가져와주도록 한다.
말이 어렵다면... 천천히 다시 읽어보자..

그다음, tmpRatePrev는 위에서 0으로 선언해놨고, tmpRateNext는 tmpRatePrev에 현재 rate를 더해준다.
그러면 이 코드를 기준으로, 위에서 다음과 같이 list에 add 해줬으므로,
현재 list엔 rate : 20, value : 100이 들어가있을거다.

map.put("rate", "20");
map.put("value", "100");
list.add(map);

그렇다면 정리해보자.

첫번째 for문에서는 0과 20 사이
두번째 for문에서는 20과 40 사이의 수가 당첨이 되는것이다.

이런식으로 100까지 채워나가면, 확률에 맞게 세팅이 되는 것이다.

그렇게 해서, 현재 랜덤으로 뽑은 tmpRandom이 tmpRatePrev와 tmpRateNext의 사이에 있다면, 해당 list 인덱스의 값을 뽑아주고 break 해주면 끝!

결과


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class Ps_210127_1 {
	public void ratePick() {
		double tmpRandom = (Math.random() * 100);
		double tmpRatePrev = 0, tmpRateNext = 0;
		int result = 0;
		//소수 둘째자리까지 절삭
		tmpRandom = Math.round(tmpRandom * 100) / 100.0;
		
		System.out.println("설정된 랜덤값" + tmpRandom);
		
		HashMap<String, String> map = new HashMap<String, String>();
		ArrayList<HashMap<String, String>> list = new ArrayList<HashMap<String,String>>();

		map.put("rate", "20");
		map.put("value", "100");
		list.add(map);
		//map.clear();
		//처음에 clear를 썼는데, list에 삽입된 map까지 clear 해버려서 에러가 났다. 새로 선언해줘야함!
		map = new HashMap<String, String>();

		map.put("rate", "20");
		map.put("value", "200");
		list.add(map);
		map = new HashMap<String, String>();
		
		map.put("rate", "20");
		map.put("value", "300");
		list.add(map);
		map = new HashMap<String, String>();
		
		map.put("rate", "10");
		map.put("value", "500");
		list.add(map);
		map = new HashMap<String, String>();
		
		map.put("rate", "10");
		map.put("value", "1000");
		list.add(map);
		map = new HashMap<String, String>();
		
		map.put("rate", "10");
		map.put("value", "1500");
		list.add(map);
		map = new HashMap<String, String>();
		
		map.put("rate", "5");
		map.put("value", "2000");
		list.add(map);
		map = new HashMap<String, String>();
		
		map.put("rate", "5");
		map.put("value", "3000");
		list.add(map);
		map.clear();
		map = new HashMap<String, String>();
		
		
		for(int i = 0; i < list.size(); i++) {
			if(tmpRandom == 100) {
				//만약 난수가 100이라면 가장 마지막에있는 list 인덱스에 있는 value 적용
				result = Integer.parseInt(list.get(list.size()-1).get("value"));
				break;
			} else {
				double rate = Double.parseDouble(list.get(i).get("rate"));
				tmpRateNext = tmpRatePrev + rate;
				if(tmpRandom >= tmpRatePrev && tmpRandom < tmpRateNext) {
					result = Integer.parseInt(list.get(i).get("value"));
					break;
				} else {
					tmpRatePrev = tmpRateNext;
				}
			}
		}
		
		System.out.println(result + " 원에 당첨되었습니다.");
		
	}
}

 

구독 및 하트는 정보 포스팅 제작에 큰 힘이됩니다♡

728x90
반응형

'PS, 언어 공부 > JAVA' 카테고리의 다른 글

[JAVA] JVM 구조 및 작동원리  (2) 2022.10.29
[JAVA] 자바의 직렬화(Serialization)  (0) 2022.10.22