로또 번호 생성 알고리즘은 어떻게 작동하는가 (의사코드 포함)
무작위 난수부터 가중치 샘플링, 몬테카를로 필터링까지. munber6가 실제로 사용하는 번호 생성 알고리즘을 의사코드와 함께 단계별로 공개한다.
번호 생성기, 생각보다 복잡하다
"어차피 랜덤이잖아?"라고 흔히들 말한다. 맞다. 최종적으로는 랜덤이다. 하지만 좋은 번호 생성기는 단순 랜덤과 통계적 필터 사이에서 균형을 잡는다. 이 글에서는 munber6가 내부적으로 사용하는 생성 파이프라인을 네 단계로 나눠 공개한다.
1단계: 가중치 테이블 구축
먼저 1~45번 각각에 대한 가중치를 계산한다. 가중치는 세 요소의 선형 결합이다.
weight[n] = 0.4 * freqScore[n] // 최근 52주 출현 빈도
+ 0.3 * gapScore[n] // 현재 미출현 기간
+ 0.3 * coOccurScore[n] // 동반 출현 기대치
freqScore는 최근 52주 등장 횟수를 정규화한 값이다. gapScore는 평균 갭 대비 현재 미출현 기간의 비율이며, 오래 안 나온 번호일수록 점수가 높다. coOccurScore는 이미 선택된 번호들과의 동반 관계를 반영하므로 런타임에 동적으로 갱신된다.
2단계: 가중치 샘플링
가중치 테이블이 준비되면 "가중치 랜덤 샘플링(weighted random sampling)" 기법으로 6개 번호를 뽑는다. 전체 가중치 합을 S라 하면, 0~S 사이의 난수 r을 뽑고 누적합이 r을 처음 넘는 번호를 선택한다. 선택된 번호는 후보 풀에서 제거되고, 다음 뽑기에서는 남은 44개 중에서 다시 누적합을 계산한다. 이 과정을 6번 반복하면 한 세트가 완성된다.
function weightedSample(weights, k):
selected = []
for i in 1..k:
total = sum(weights)
r = random(0, total)
pick = findCumulative(weights, r)
selected.push(pick)
weights[pick] = 0 // 중복 제거
updateCoOccur(weights, pick) // 동반 점수 갱신
return selected
3단계: 통계 필터링
샘플링으로 나온 한 세트가 통계적으로 "자연스러운지" 검증한다. 검증 조건은 다섯 가지다.
- 합계 범위: 120 ≤ sum ≤ 180 (벗어나면 재샘플)
- AC값: 6 ≤ AC ≤ 10
- 홀짝 비율: 1:5, 2:4, 3:3, 4:2, 5:1 중 하나
- 고저 분포: 모두 1~22 또는 모두 23~45가 아닐 것
- 연속 수열: 4개 이상 연속 숫자 금지 (예: 7-8-9-10)
다섯 조건 중 하나라도 위배되면 그 세트는 버리고 2단계로 돌아간다. 평균적으로 1세트를 얻기까지 1.8회 정도의 재샘플이 발생한다.
4단계: 다양성 보장
여러 세트를 동시에 생성할 때는 세트 간 유사도가 너무 높으면 사용자 경험이 나빠진다. munber6는 자카드 유사도(Jaccard similarity) 0.5 이상인 세트가 연속 3개 이상 나오면 가중치 테이블을 약간 흔들어(noise injection) 다양성을 확보한다.
function generateSets(n):
results = []
while len(results) < n:
set = weightedSample(weights, 6)
if not passFilters(set):
continue
if tooSimilar(set, results):
perturbWeights()
continue
results.push(set)
return results
왜 이렇게까지 하나
결과적으로 번호 당첨 확률을 바꿀 수는 없다. 하지만 "극단적이거나 통계적으로 어색한" 조합을 줄이고, 사용자에게 매번 다양한 번호를 제공하는 데는 확실히 도움이 된다. 같은 번호를 두 번 추천받으면 사용자는 서비스를 떠난다. 당첨 확률은 못 바꿔도 체감 품질은 바꿀 수 있다. 이 파이프라인은 약 5ms 이내에 1세트를 생성하며, 1,000세트 자동 생성도 1초 미만으로 끝난다.
오픈소스와의 비교
시중에 공개된 로또 생성기 대부분은 단순 Math.random() 호출로 끝난다. 통계 필터링을 적용한 생성기는 극소수이고, 가중치 샘플링 + 몬테카를로 필터링 + 다양성 보장을 전부 갖춘 오픈소스는 찾기 어렵다. munber6의 장점은 이 모든 단계가 클라이언트가 아닌 서버에서 일관된 기준으로 실행된다는 점, 그리고 매주 최신 당첨번호가 반영되어 가중치가 자동 갱신된다는 점이다.
직접 써보기
메인 페이지에서 "번호 생성하기" 버튼을 누르면 위 파이프라인이 그대로 실행된 결과를 받는다. 자동 모드는 6개 번호를 전부 생성기가 뽑고, 반자동 모드는 사용자가 고른 번호를 고정한 채 나머지만 채운다.