·8분 분량

로또 번호 생성 알고리즘은 어떻게 작동하는가 (의사코드 포함)

무작위 난수부터 가중치 샘플링, 몬테카를로 필터링까지. 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개 번호를 전부 생성기가 뽑고, 반자동 모드는 사용자가 고른 번호를 고정한 채 나머지만 채운다.

🎱 번호 생성기 사용해보기

글에서 소개한 알고리즘으로 직접 번호를 생성해보세요