From 997ed8296df8fef8ff911fb1c77b3f872b9dbc7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EA=B7=9C?= Date: Fri, 14 Feb 2025 01:22:56 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/check-no-external-libs.yml | 2 +- docs/README.md | 24 +++++ pytest.ini | 2 + src/lotto/__init__.py | 3 + src/lotto/lotto.py | 47 ++++++++-- src/lotto/main.py | 94 +++++++++++++++++++- 6 files changed, 163 insertions(+), 9 deletions(-) diff --git a/.github/workflows/check-no-external-libs.yml b/.github/workflows/check-no-external-libs.yml index cd8918d..a1a6f83 100644 --- a/.github/workflows/check-no-external-libs.yml +++ b/.github/workflows/check-no-external-libs.yml @@ -20,7 +20,7 @@ jobs: import os import ast - allowed_modules = {'sys', 'os', 'math', 'random', 'datetime', 're'} + allowed_modules = {'sys', 'os', 'math', 'random', 'datetime', 're' 'enum'} def check_imports(file_path): with open(file_path, 'r', encoding='utf-8') as f: diff --git a/docs/README.md b/docs/README.md index e69de29..533abf2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,24 @@ +# 기능 목록 정리 +1. 입력 처리 +- 로또 구입 금액 입력 (1000원 단위인지 확인) +- 당첨 번호 입력 (1~45 범위, 6개 중복 없음) +- 보너스 번호 입력 (1~45 범위, 당첨 번호와 중복 없음) + +2. 로또 발행 +- 입력한 금액만큼 로또 자동 생성 (중복 없는 6개 번호) +- 로또 번호는 오름차순 정렬 + +3. 당첨 결과 계산 +- 각 로또 번호와 당첨 번호 비교 +- 보너스 번호 포함 여부 확인 +- 등수 판별 (1등~5등) +- 당첨 내역 카운트 + +4. 출력 처리 +- 구매한 로또 출력 +- 당첨 내역 출력 +- 총 수익률 계산 및 출력 (소수점 둘째 자리 반올림) + +5.예외 처리 +- 잘못된 입력 ([ERROR] 메시지 출력 후 재입력) +- ValueError 발생 시 처리 \ No newline at end of file diff --git a/pytest.ini b/pytest.ini index fcccae1..8359d12 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,4 @@ [pytest] pythonpath = src +markers = + custom_name: 로또 번호 테스트에 사용되는 Custom Mark \ No newline at end of file diff --git a/src/lotto/__init__.py b/src/lotto/__init__.py index e69de29..2dd63a8 100644 --- a/src/lotto/__init__.py +++ b/src/lotto/__init__.py @@ -0,0 +1,3 @@ +from .lotto import Lotto + +__all__ = ["Lotto"] diff --git a/src/lotto/lotto.py b/src/lotto/lotto.py index 9c8c935..b0675e0 100644 --- a/src/lotto/lotto.py +++ b/src/lotto/lotto.py @@ -1,12 +1,47 @@ -from typing import List +from enum import Enum +import random + class Lotto: - def __init__(self, numbers: List[int]): + def __init__(self, numbers: list[int]): self._validate(numbers) - self._numbers = numbers + self._numbers = sorted(numbers) - def _validate(self, numbers: List[int]): + def _validate(self, numbers: list[int]): if len(numbers) != 6: - raise ValueError + raise ValueError("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.") + if len(set(numbers)) < 6: + raise ValueError("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.") + if max(numbers) > 45 or min(numbers) < 1: + raise ValueError("로또 번호의 숫자 범위는 1~45까지입니다.") + + @classmethod + def generate_num(clss): + return clss(random.sample(range(1, 46), 6)) + + def get_numbers(self): + return self._numbers + + def __str__(self): + return str(self._numbers) + + +class Rank(Enum): + FIFTH = (3, False, 5_000) + FOURTH = (4, False, 50_000) + THIRD = (5, False, 1_500_000) + SECOND = (5, True, 30_000_000) + FIRST = (6, False, 2_000_000_000) + NONE = (0, False, 0) + + def __init__(self, match_cnt, bonus_match, prize): + self.match_cnt = match_cnt + self.bonus_match = bonus_match + self.prize = prize - # TODO: 추가 기능 구현 + @classmethod + def get_rank(clss, match_cnt, bonus): + for rank in clss: + if rank.match_cnt == match_cnt and rank.bonus_match == bonus: + return rank + return clss.NONE diff --git a/src/lotto/main.py b/src/lotto/main.py index 5f270aa..f13e512 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -1,6 +1,96 @@ +from .lotto import Rank, Lotto + +def check_amount(input_amount): + if not input_amount.isdigit(): + raise ValueError("[ERROR] 숫자를 입력해 주세요.") + if int(input_amount) % 1000 != 0: + raise ValueError("구입 금액은 1,000원으로 나누어 떨어져야 합니다.") + if int(input_amount) < 1000: + raise ValueError("구입 금액은 1,000원 이상이어야 합니다.") + return int(input_amount) // 1000 + +def prompt_purchase_amount(): + while True: + try: + print("구입금액을 입력해 주세요.") + amount = input() + return check_amount(amount) + except ValueError as error: + print(f"[ERROR] {error}") + raise + +def print_lotto_tickets(tickets): + print(f"\n{len(tickets)}개를 구매했습니다.") + for ticket in tickets: + print(ticket) + +def prompt_winning_numbers(): + while True: + print("\n당첨 번호를 입력해 주세요.") + try: + winning_numbers = list(map(int, input().split(","))) + return Lotto(winning_numbers).get_numbers() + except ValueError as error: + print(f"[ERROR] {error}") + +def check_bonus_number(bonus_num, winning_numbers): + if not bonus_num.isdigit(): + raise ValueError("숫자를 입력해 주세요.") + if int(bonus_num) in winning_numbers: + raise ValueError("보너스 숫자와 입력한 당첨 번호는 중복되지 않아야 합니다.") + if int(bonus_num) > 45 or int(bonus_num) < 1: + raise ValueError("로또 번호의 숫자 범위는 1~45까지입니다.") + return int(bonus_num) + +def prompt_bonus_number(winning_numbers): + while True: + try: + bonus_num = input("\n보너스 번호를 입력해 주세요.\n") + return check_bonus_number(bonus_num, winning_numbers) + except ValueError as error: + print(f"[ERROR] {error}") + +def evaluate_tickets(tickets, winning_numbers, bonus_num): + results = {rank: 0 for rank in Rank} + total_prize = 0 + + for ticket in tickets: + ticket_numbers = ticket.get_numbers() + match_count = len(set(winning_numbers) & set(ticket_numbers)) + bonus = bonus_num in ticket_numbers + + rank = Rank.get_rank(match_count, bonus) + results[rank] += 1 + total_prize += rank.prize + + return results, total_prize + +def print_results(results, total_prize, amount): + profit_percentage = round((total_prize / (amount * 1000)) * 100, 2) + + print("\n당첨 통계") + print("---") + for rank in Rank: + if rank == Rank.SECOND: + print(f"{rank.match_cnt}개 일치, 보너스 볼 일치 ({rank.prize:,}원) - " + f"{results[rank]}개") + + if rank != Rank.NONE and rank != Rank.SECOND: + print(f"{rank.match_cnt}개 일치 ({rank.prize:,}원) - " + f"{results[rank]}개") + + print(f"총 수익률은 {profit_percentage}%입니다.") + def main(): - # TODO: 프로그램 구현 - pass + amount = prompt_purchase_amount() + tickets = [Lotto.generate_num() for _ in range(amount)] + print_lotto_tickets(tickets) + + winning_numbers = prompt_winning_numbers() + bonus_num = prompt_bonus_number(winning_numbers) + + results, total_prize = evaluate_tickets(tickets, winning_numbers, bonus_num) + print_results(results, total_prize, amount) if __name__ == "__main__": main()