Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
## 기능 요구 사항
- [X] 입력 기능
- [X] 로또 구입 금액 입력
- [X] 당첨 번호 입력. 번호는 쉽표(',')를 기준으로 구분
- [X] 보너스 번호 입력

- [ ] 출력 기능
- [X] 구입 금액 입력 요구 문구 "구입금액을 입력해 주세요."
- [X] 발행한 로또 수량 출력
- [X] 발행한 로또 수량만큼 번호 출력. 번호는 오름차순으로 정렬하여 출력
- [ ] 당첨 내역 출력
- [ ] 수익률 출력, 수익률은 소수점 둘째 자리에서 반올림
- [ ] 예외 상황 시 에러 문구 출력. 에러 문구는 "[ERROR]"로 시작

- [ ] 컴퓨터 기능
- [X] 구입 금액에 해당하는 만큼 로또 발행
- [X] 로또는 6자리이며, 범위는 1 ~ 45 사이 정수
- [X] 사용자가 구매한 로또 번호와 당첨 번호 비교
- [ ] 당첨 내역 및 수익률 계산

- [ ] 당첨 기준 및 금액
- [ ] 1등: 6개 번호 일치 / 2,000,000,000원
- [ ] 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
- [ ] 3등: 5개 번호 일치 / 1,500,000원
- [ ] 4등: 4개 번호 일치 / 50,000원
- [ ] 5등: 3개 번호 일치 / 5,000원

- [X] 예외 처리 기능
- [X] 사용자가 잘못된 값을 입력할 경우 ValueError를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받음
- [X] 로또 구입 금액 입력 예외 사항
- [X] int형이 아닌 경우
- [X] 양의 정수가 아닌 경우
- [X] 1,000원 단위가 아닌 경우
- [X] 당첨 번호 입력 예외 사항
- [X] 정수가 아닌 것이 포함된 경우
- [X] 당첨 번호 길이가 6자리가 아닌 경우
- [X] 번호 범위가 1 ~ 45를 벗어날 경우
- [X] 당첨 번호가 중복될 경우
- [X] 보너스 번호 입력 예외사항
- [X] 정수가 아닌 경우
- [X] 번호 범위가 1 ~ 45를 벗어날 경우
- [X] 당첨 번호와 중복될 경우

## 기능 추가
1. is_number()
> 입력 값이 정수인지 검증하는 함수
2. validate_input_prchase_amount()
> 구매 금액 입력에 대한 검증 함수
3. input_purchase_amount()
> 구매 금액을 입력받는 함수
4. generate_lotto_quantity()
> 구매 가능한 로또 개수 계산 함수
5. Lotto.issuance_lotto()
> 로또 발행 함수
6. convert_to_list()
> 입력 값을 list 형식으로 변환하는 함수
7. input_winning_numbers()
> 당첨 번호를 입력받는 함수
8. Lotto._validate()
> 당첨 번호를 검증하는 함수
9. input_bonus_number()
> 보너스 번호를 입력받는 함수
10. Lotto.validate_bonus_number()
> 보너스 번호를 검증하는 함수
11. Lotto.compare_winning_number()
> 당첨 번호와 발행 번호를 비교하는 함수
12. Lotto.compare_bonus_number()
> 보너스 번호와 발행 번호를 비교하는 함수
13. Lotto.calculate_result()
> 발행한 로또를 순서대로 당첨 번호, 보너스 번호 비교 함수를 실행하는 함수


## 오류 수정

## 기능 수정
5 changes: 3 additions & 2 deletions src/lotto/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# src/lotto/__init__.py

# 📌 이 패키지는 로또 관련 기능을 제공하는 모듈입니다.
# 외부에서 `from lotto import Lotto`와 같은 방식으로 사용할 수 있도록
# 외부에서 `from lotto import Lotto`와 같은 방식으로 사용할 수 있도록
# 필요한 모듈을 여기에 등록하세요.
#
# ✅ 새로운 모듈을 추가할 경우:
Expand All @@ -10,9 +10,10 @@
# - `flake8`의 F401 경고(`imported but unused`)가 발생하는 경우, `__all__`을 활용해 해결하세요.

from .lotto import Lotto # 🎲 로또 번호 생성 및 검증을 위한 클래스
from .lotto import Score

# 패키지 외부에서 `from lotto import *` 사용 시 제공할 모듈을 명시적으로 정의합니다.
__all__ = ["Lotto"]
__all__ = ["Lotto", "Score"]

# 💡 예시: 새로운 모듈을 추가할 때
# from .other_module import OtherClass # 🆕 예: 새로운 클래스 추가 시
Expand Down
95 changes: 89 additions & 6 deletions src/lotto/lotto.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,95 @@
from typing import List
from enum import Enum
import random


LOTTO_SIZE = 6 # 로또 길이 매직넘버상수
LOTTO_NUMBER_RANGE = range(1, 46) # 로또 숫자 범위 매직넘버상수


class Lotto:
def __init__(self, numbers: List[int]):
def __init__(self, numbers: list[int] = None):
if numbers is None: # numbers가 주어지지 않으면 자동으로 생성
numbers = self.issuance_lotto()

Check warning on line 12 in src/lotto/lotto.py

View check run for this annotation

Codecov / codecov/patch

src/lotto/lotto.py#L12

Added line #L12 was not covered by tests
self._validate(numbers)
self._numbers = numbers
self.bonus_number = int
self.numbers_list = list[list]
self.result_list = list[int]
self.statistics_list = list[list]

def _validate(self, numbers: list[int]):
if len(numbers) != LOTTO_SIZE:
raise ValueError("[ERROR] 당첨 번호는 6자리입니다.")
elif not all(num in LOTTO_NUMBER_RANGE for num in numbers):
raise ValueError("[ERROR] 당첨 번호는 1 ~ 45 사이여야 합니다.")
elif not all(numbers.count(num) == 1 for num in numbers):
raise ValueError("[ERROR] 당첨 번호는 중복될 수 없습니다.")

def validate_bonus_number(self, number: int):
self.bonus_number = number
if self.bonus_number in self._numbers:
raise ValueError("[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다.")
elif self.bonus_number not in LOTTO_NUMBER_RANGE:
raise ValueError("[ERROR] 보너스 번호는 1 ~ 45 사이여야 합니다.")

@staticmethod
def issuance_lotto():
"""랜덤한 6자리 로또 번호 생성 후 정렬하여 반환"""
value = sorted(random.sample(LOTTO_NUMBER_RANGE, LOTTO_SIZE))
return value

def __str__(self):
"""str 형식으로 변환하여 반환"""
return str(self._numbers)

Check warning on line 43 in src/lotto/lotto.py

View check run for this annotation

Codecov / codecov/patch

src/lotto/lotto.py#L43

Added line #L43 was not covered by tests

def get_numbers(self):
"""로또 번호 리스트 반환"""
return self._numbers

Check warning on line 47 in src/lotto/lotto.py

View check run for this annotation

Codecov / codecov/patch

src/lotto/lotto.py#L47

Added line #L47 was not covered by tests

def compare_winning_number(self, numbers: list[int]):
"""당첨 번호와 발행 번호를 비교"""
count = 0
for i in numbers:
if i in self._numbers:
count += 1
return count

def compare_bonus_number(self, numbers: list[int]):
"""보너스 번호와 발행 번호를 비교"""
if self.bonus_number in numbers:
return 1
return 0

def calculate_result(self, numbers_list: list[list]):
"""발행한 로또를 순서대로 당첨 번호, 보너스 번호와 비교"""
self.numbers_list = numbers_list
self.result_list = [0 for _ in range(len(numbers_list))]
for i in range(len(self.result_list)):
count_winning = self.compare_winning_number(self.numbers_list[i])
count_bonus = self.compare_bonus_number(self.numbers_list[i])
self.result_list[i] = [count_winning, count_bonus]
return self.result_list


class Score(Enum):
FIRST = (6, 0, 2000000000) # 6개 일치, 보너스 X, 1등
SECOND = (5, 1, 30000000) # 5개 일치, 보너스 O, 2등
THIRD = (5, 0, 1500000) # 5개 일치, 보너스 X, 3등
FOURTH = (4, 0, 50000) # 4개 일치, 보너스 X, 4등
FIFTH = (3, 0, 5000) # 3개 일치, 보너스 X, 5등
NONE = (0, 0, 0) # 당첨되지 않음

def _validate(self, numbers: List[int]):
if len(numbers) != 6:
raise ValueError
def __init__(self, m_count, b_match, prize):
self.m_count = m_count # 맞춘 숫자 개수
self.b_match = b_match # 보너스 번호 일치 여부
self.prize = prize # 상금

# TODO: 추가 기능 구현
@classmethod
def get_score(cls, m_count, b_match):
"""
당첨 번호 개수와 보너스 번호 여부를 받아 해당하는 Score 반환
"""
for score in cls:
if score.m_count == m_count and score.b_match == b_match:
return score
return cls.NONE # 당첨되지 않은 경우
116 changes: 114 additions & 2 deletions src/lotto/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,118 @@
from lotto.lotto import Lotto
from lotto.lotto import Score


LOTTO_EACH_PRICE = 1000 # 로또 구입 금액 단위 매직넘버상수


# data가 정수인지 검증 함수
def is_number(data):
try:
return int(data)
except ValueError as e:
raise ValueError("[ERROR] 정수만 입력해주세요.") from e


# 구입 금액 입력 검증 함수
def validate_purchase_amount(data):
purchase_amount = is_number(data) # 입력 값이 정수인지 검증
if purchase_amount <= 0: # 입력 값이 양의 정수인지 검증
raise ValueError("[ERROR] 구입 금액은 양의 정수 입니다.")

Check warning on line 20 in src/lotto/main.py

View check run for this annotation

Codecov / codecov/patch

src/lotto/main.py#L20

Added line #L20 was not covered by tests
elif purchase_amount % LOTTO_EACH_PRICE != 0: # 입력 값이 1,000원 단위인지 검증
raise ValueError("[ERROR] 구입 금액을 1,000원 단위로 입력해 주세요.")

Check warning on line 22 in src/lotto/main.py

View check run for this annotation

Codecov / codecov/patch

src/lotto/main.py#L22

Added line #L22 was not covered by tests
return purchase_amount # 검증 통과


def input_purchase_amount(): # 구입 금액 입력 함수
print("구입금액을 입력해 주세요.")
purchase_amount = input()
purchase_amount = validate_purchase_amount(purchase_amount) # 구입 금액 검증
return purchase_amount


# 구매 가능한 로또 개수 계산
def generate_lotto_quantity(purcahse_amount):
lotto_quantity = purcahse_amount // LOTTO_EACH_PRICE
print("\n{0}개를 구매했습니다.".format(lotto_quantity))
return lotto_quantity

Comment on lines +33 to +38
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

로또 개수 생성 함수
purcahse_amount 파라미터의 철자가 오타로 보이며, 변수 명을 purchase_amount로 수정하는 것을 권장합니다.

-def generate_lotto_quantity(purcahse_amount):
+def generate_lotto_quantity(purchase_amount):
    lotto_quantity = purchase_amount // LOTTO_EACH_PRICE
    ...
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 구매 가능한 로또 개수 계산
def generate_lotto_quantity(purcahse_amount):
lotto_quantity = purcahse_amount // LOTTO_EACH_PRICE
print("\n{0}개를 구매했습니다.".format(lotto_quantity))
return lotto_quantity
# 구매 가능한 로또 개수 계산
def generate_lotto_quantity(purchase_amount):
lotto_quantity = purchase_amount // LOTTO_EACH_PRICE
print("\n{0}개를 구매했습니다.".format(lotto_quantity))
return lotto_quantity


# 입력 값을 list 형식으로 변환해주는 함수
def convert_to_list(data):
try:
data = list(map(int, data.replace(" ", "").split(",")))
return data
except ValueError as e:
raise ValueError("[ERROR] 번호는 정수로 이루어져 있어야 합니다.") from e

Check warning on line 46 in src/lotto/main.py

View check run for this annotation

Codecov / codecov/patch

src/lotto/main.py#L45-L46

Added lines #L45 - L46 were not covered by tests

Comment on lines +40 to +47
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

입력값 검증 강화 및 테스트 추가가 필요합니다.

  1. 예외 처리 로직에 대한 테스트가 누락되어 있습니다.
  2. 입력값이 비어있는 경우에 대한 처리가 필요합니다.
 def convert_to_list(data):
     try:
+        if not data.strip():
+            raise ValueError("[ERROR] 빈 입력값은 허용되지 않습니다.")
         data = list(map(int, data.replace(" ", "").split(",")))
         return data
     except ValueError as e:
         raise ValueError("[ERROR] 번호는 정수로 이루어져 있어야 합니다.") from e
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# 입력 값을 list 형식으로 변환해주는 함수
def convert_to_list(data):
try:
data = list(map(int, data.replace(" ", "").split(",")))
return data
except ValueError as e:
raise ValueError("[ERROR] 번호는 정수로 이루어져 있어야 합니다.") from e
# 입력 값을 list 형식으로 변환해주는 함수
def convert_to_list(data):
try:
if not data.strip():
raise ValueError("[ERROR] 빈 입력값은 허용되지 않습니다.")
data = list(map(int, data.replace(" ", "").split(",")))
return data
except ValueError as e:
raise ValueError("[ERROR] 번호는 정수로 이루어져 있어야 합니다.") from e
🧰 Tools
🪛 GitHub Check: codecov/patch

[warning] 45-46: src/lotto/main.py#L45-L46
Added lines #L45 - L46 were not covered by tests


# 당첨 번호를 입력받고 검증하는 함수
def input_winning_numbers():
print("\n당첨 번호를 입력해 주세요.")
winning_numbers = input() # 당첨 번호 입력
winning_numbers = convert_to_list(winning_numbers) # 당첨 번호 list형으로 변경
lotto = Lotto(winning_numbers) # 당첨 번호 검증
return lotto


# 보너스 번호를 입력받고 검증하는 함수
def input_bonus_number(lotto):
print("\n보너스 번호를 입력해 주세요.")
bouns_number = input() # 보너스 번호 입력
bouns_number = is_number(bouns_number) # 보너스 번호를 int형으로 변환
lotto.validate_bonus_number(bouns_number) # 보너스 번호 검증증


def just_print(Score, score_count):
# 결과 출력
for score in Score:
if score == Score.NONE:
continue # NONE 등급(낙첨)은 출력하지 않음
description = f"{score.m_count}개 일치"
if score.b_match == 1:
description += ", 보너스 볼 일치"
print(f"{description} ({format(score.prize, ',d')}원)", end="")
print(f" - {score_count[score]}개")


def print_result(result_list, purchase_amount):
print("\n당첨 통계\n---")

# 등수별 당첨 개수 저장 딕셔너리 초기화
score_count = {score: 0 for score in Score if score != Score.NONE}

# 당첨 개수 세기
for match_count, bonus_match in result_list:
score = Score.get_score(match_count, bonus_match)
if score != Score.NONE:
score_count[score] += 1

just_print(Score, score_count)

# 총 당첨 금액 계산
total_prize = sum(score.prize * cnt for score, cnt in score_count.items())

# 수익률 계산 및 출력
revenue_rate = (total_prize / purchase_amount) * 100
print(f"총 수익률은 {revenue_rate:.1f}%입니다.")


def main():
# TODO: 프로그램 구현
pass
purchase_amount = input_purchase_amount() # 구입 금액 입력
l_quantity = generate_lotto_quantity(purchase_amount) # 로또 수량 계산산

issu_l_list = [Lotto.issuance_lotto() for _ in range(l_quantity)] # 로또 발행
for lotto in issu_l_list: # 발행된 로또 출력
print(lotto)

lotto = input_winning_numbers() # 당첨 번호 입력

input_bonus_number(lotto) # 보너스 번호 입력

result_list = lotto.calculate_result(issu_l_list)

print_result(result_list, purchase_amount)


if __name__ == "__main__":
main()
61 changes: 60 additions & 1 deletion tests/lotto/test_lotto.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,63 @@ def test_create_lotto_by_duplicated_number():
Lotto([1, 2, 3, 4, 5, 5])


# 추가 테스트 작성 가능
# 로또 번호 범위 초과 예외 테스트 (예: 46이 포함된 경우)
@pytest.mark.custom_name("로또 번호가 1~45 범위를 벗어나면 예외가 발생한다.")
def test_create_lotto_by_out_of_range():
with pytest.raises(ValueError):
Lotto([0, 1, 2, 3, 4, 5]) # 0 포함
with pytest.raises(ValueError):
Lotto([1, 2, 3, 4, 5, 46]) # 46 포함


# 보너스 번호 중복 예외 테스트 (보너스 번호가 당첨 번호와 중복)
@pytest.mark.custom_name("보너스 번호가 당첨 번호와 중복되면 예외가 발생한다.")
def test_validate_bonus_number_by_duplicate():
lotto = Lotto([1, 2, 3, 4, 5, 6])
with pytest.raises(ValueError):
lotto.validate_bonus_number(5) # 기존 당첨 번호와 중복된 보너스 번호


# 보너스 번호 범위 초과 예외 테스트
@pytest.mark.custom_name("보너스 번호가 1~45 범위를 벗어나면 예외가 발생한다.")
def test_validate_bonus_number_by_out_of_range():
lotto = Lotto([1, 2, 3, 4, 5, 6])
with pytest.raises(ValueError):
lotto.validate_bonus_number(0) # 0 포함
with pytest.raises(ValueError):
lotto.validate_bonus_number(46) # 46 포함


# 로또 자동 발행 시, 6개의 숫자가 포함되어 있는지 확인
@pytest.mark.custom_name("로또 자동 발행 시, 6개의 숫자가 포함되어야 한다.")
def test_issuance_lotto_size():
numbers = Lotto.issuance_lotto()
assert len(numbers) == 6


# 로또 자동 발행 시, 숫자가 정렬되어 있는지 확인
@pytest.mark.custom_name("로또 자동 발행 시, 번호가 오름차순 정렬되어야 한다.")
def test_issuance_lotto_sorted():
numbers = Lotto.issuance_lotto()
assert numbers == sorted(numbers)


# 로또 번호 비교 테스트 (당첨 개수 확인)
@pytest.mark.custom_name("발행된 로또 번호와 당첨 번호를 비교하여 일치 개수를 확인한다.")
def test_compare_winning_number():
winning_lotto = Lotto([1, 2, 3, 4, 5, 6])
issued_numbers = [1, 2, 3, 10, 20, 30] # 3개 일치
assert winning_lotto.compare_winning_number(issued_numbers) == 3


# 보너스 번호 비교 테스트
@pytest.mark.custom_name("발행된 로또 번호와 보너스 번호를 비교하여 일치 여부를 확인한다.")
def test_compare_bonus_number():
winning_lotto = Lotto([1, 2, 3, 4, 5, 6])
winning_lotto.validate_bonus_number(7)

issued_numbers = [7, 8, 9, 10, 11, 12] # 보너스 번호(7) 포함
assert winning_lotto.compare_bonus_number(issued_numbers) == 1

issued_numbers = [1, 2, 3, 4, 5, 6] # 보너스 번호 미포함
assert winning_lotto.compare_bonus_number(issued_numbers) == 0
Loading