From e701b83ee6a9e9bb6fe3e45967f91dd00bcdb01d Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 19:22:30 +0900 Subject: [PATCH 01/32] =?UTF-8?q?docs:=20=EA=B5=AC=ED=98=84=ED=95=A0=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EB=AA=A9=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 250 ++++++++++-------------------------------------------- 1 file changed, 46 insertions(+), 204 deletions(-) diff --git a/README.md b/README.md index 867338a..654e834 100644 --- a/README.md +++ b/README.md @@ -1,204 +1,46 @@ -# 미션 - 로또 - -> [!NOTE] -> 이 코드는 원래 [java-lotto-6](https://github.com/woowacourse-precourse/java-lotto-6)에서 제공된 **Java 기반의 로또 게임**을 **Python**에 맞게 변환한 과제입니다. 프로젝트 구조, 요구 사항, 기능 구현 방식은 원본 저장소를 바탕으로 Python 환경에 맞추어 수정하였습니다. - ---- - -## 🔍 진행 방식 - -- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있습니다. -- 세 가지 요구 사항을 만족하기 위해 노력해야 하며, 특히 기능을 구현하기 전에 기능 목록을 작성하고 기능 단위로 커밋하는 방식으로 진행해야 합니다. -- 기능 요구 사항에 명시되지 않은 부분은 스스로 판단하여 구현할 수 있습니다. - -## 📮 미션 제출 방법 - -- 미션 구현을 완료한 후 GitHub을 통해 제출해야 합니다. - - **Pull Request로 최종 제출** - -## 🚨 과제 제출 전 체크 리스트 - 0점 방지 - -- 기능을 모두 정상적으로 구현했더라도 **요구 사항에 명시된 출력값 형식을 지키지 않을 경우 0점 처리**됩니다. -- 기능 구현을 완료한 뒤 아래 가이드에 따라 테스트를 실행하고 모든 테스트가 성공하는지 확인합니다. -- **테스트가 실패할 경우 0점 처리**되므로, 반드시 확인 후 제출해야 합니다. - - - -### 테스트 실행 가이드 - -- 터미널에서 `python --version`을 실행하여 Python 버전이 3.9 이상인지 확인합니다. -- 프로젝트의 의존성 패키지를 설치하기 위해 `pip install -r requirements.txt` 명령어를 실행합니다. -- 아래 명령어를 실행하여 모든 테스트가 성공하는지 확인합니다. - -```bash -pytest -``` - -테스트 실행 시 출력 예시는 다음과 같습니다. - -```bash -=========================== test session starts ============================ -platform darwin -- Python 3.9.x, pytest-6.2.5 -rootdir: /path/to/project -collected 5 items - -tests/lotto/test_main.py .... [ 80%] -tests/lotto/test_lotto.py .. [100%] - -============================ 100% passing in Xs ============================= -``` - ---- - -## 🚀 기능 요구 사항 - -로또 게임 기능을 구현해야 합니다. 로또 게임은 아래와 같은 규칙으로 진행됩니다. - -``` -- 로또 번호의 숫자 범위는 1~45까지입니다. -- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑습니다. -- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑습니다. -- 당첨은 1등부터 5등까지 있습니다. 당첨 기준과 금액은 아래와 같습니다. - - 1등: 6개 번호 일치 / 2,000,000,000원 - - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 - - 3등: 5개 번호 일치 / 1,500,000원 - - 4등: 4개 번호 일치 / 50,000원 - - 5등: 3개 번호 일치 / 5,000원 -``` - -- 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 합니다. -- 로또 1장의 가격은 1,000원입니다. -- 당첨 번호와 보너스 번호를 입력받습니다. -- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료합니다. -- 사용자가 잘못된 값을 입력할 경우 `ValueError`를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받습니다. - -### 입출력 요구 사항 - -#### 입력 - -- 로또 구입 금액을 입력받습니다. 구입 금액은 1,000원 단위로 입력받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리합니다. - -``` -14000 -``` - -- 당첨 번호를 입력받습니다. 번호는 쉼표(,)를 기준으로 구분합니다. - -``` -1,2,3,4,5,6 -``` - -- 보너스 번호를 입력받습니다. - -``` -7 -``` - -#### 출력 - -- 발행한 로또 수량 및 번호를 출력합니다. 로또 번호는 오름차순으로 정렬하여 보여줍니다. - -``` -8개를 구매했습니다. -[8, 21, 23, 41, 42, 43] -[3, 5, 11, 16, 32, 38] -[7, 11, 16, 35, 36, 44] -[1, 8, 11, 31, 41, 42] -[13, 14, 16, 38, 42, 45] -[7, 11, 30, 40, 42, 43] -[2, 13, 22, 32, 38, 45] -[1, 3, 5, 14, 22, 45] -``` - -- 당첨 내역을 출력합니다. - -``` -3개 일치 (5,000원) - 1개 -4개 일치 (50,000원) - 0개 -5개 일치 (1,500,000원) - 0개 -5개 일치, 보너스 볼 일치 (30,000,000원) - 0개 -6개 일치 (2,000,000,000원) - 0개 -``` - -- 수익률은 소수점 둘째 자리에서 반올림합니다. (ex. 100.0%, 51.5%, 1,000,000.0%) - -``` -총 수익률은 62.5%입니다. -``` - -- 예외 상황 시 에러 문구를 출력해야 합니다. 단, 에러 문구는 "[ERROR]"로 시작해야 합니다. - -``` -[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다. -``` - -#### 실행 결과 예시 - -``` -구입금액을 입력해 주세요. -8000 - -8개를 구매했습니다. -[8, 21, 23, 41, 42, 43] -[3, 5, 11, 16, 32, 38] -[7, 11, 16, 35, 36, 44] -[1, 8, 11, 31, 41, 42] -[13, 14, 16, 38, 42, 45] -[7, 11, 30, 40, 42, 43] -[2, 13, 22, 32, 38, 45] -[1, 3, 5, 14, 22, 45] - -당첨 번호를 입력해 주세요. -1,2,3,4,5,6 - -보너스 번호를 입력해 주세요. -7 - -당첨 통계 ---- -3개 일치 (5,000원) - 1개 -4개 일치 (50,000원) - 0개 -5개 일치 (1,500,000원) - 0개 -5개 일치, 보너스 볼 일치 (30,000,000원) - 0개 -6개 일치 (2,000,000,000원) - 0개 -총 수익률은 62.5%입니다. -``` - ---- - -## 🎯 프로그래밍 요구 사항 - -- Python 3.9 이상에서 실행 가능해야 합니다. **정상적으로 동작하지 않을 경우 0점 처리**됩니다. -- 프로그램 실행의 시작점은 `src/lotto/main.py`의 `main()` 함수입니다. -- 외부 라이브러리를 사용하지 않습니다. -- [Python 코드 스타일 가이드(Python PEP8)](https://peps.python.org/pep-0008/)을 준수하며 프로그래밍합니다. -- 프로그램 종료 시 `sys.exit()`를 호출하지 않습니다. -- 프로그램 구현이 완료되면 `tests/lotto/test_main.py`의 모든 테스트가 성공해야 합니다. **테스트가 실패할 경우 0점 처리**됩니다. -- 프로그래밍 요구 사항에서 별도로 명시하지 않는 한 파일과 패키지 이름을 수정하거나 이동하지 않습니다. -- 들여쓰기(인덴트) 깊이를 3을 넘지 않도록 구현합니다. 2까지만 허용합니다. - - 예를 들어, while문 안에 if문이 있으면 들여쓰기는 2입니다. - - 함수나 메서드를 분리해 들여쓰기 깊이를 줄이는 것이 좋습니다. -- 삼항 연산자를 사용하지 않습니다. -- 함수나 메서드는 한 가지 일만 하도록 작게 작성해야 합니다. -- Pytest와 Assert를 이용해 본인이 정리한 기능 목록이 정상 동작하는지 테스트 코드로 확인합니다. - -### 추가된 요구 사항 - -- 함수나 메서드의 길이는 15라인을 넘지 않도록 구현합니다. -- else 예약어를 사용하지 않습니다. - - 힌트: if 조건절에서 값을 반환하면 else를 생략할 수 있습니다. -- Python의 Enum을 적용합니다. -- 도메인 로직에 단위 테스트를 구현해야 합니다. 단, UI(System.out, input) 로직은 제외합니다. - - 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리합니다. - - src/lotto/lotto.py: 핵심 로직을 구현 - - src/lotto/main.py: UI(입출력)를 담당하는 로직 구현 - - 단위 테스트 작성이 익숙하지 않다면 `tests/lotto/test_lotto.py`를 참고하여 학습한 후 테스트를 구현합니다. - ---- - -## ✏️ 과제 진행 요구 사항 - -- 미션은 [python-lotto](https://github.com/swthewhite/python-lotto) 저장소를 Fork & Clone하여 시작합니다. -- **기능을 구현하기 전 `docs/README.md`에 구현할 기능 목록을 정리**해 추가합니다. -- **Git의 커밋 단위는 앞 단계에서 `docs/README.md`에 정리한 기능 목록 단위**로 추가합니다. - - [커밋 메시지 컨벤션](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 가이드를 참고해 커밋 메시지를 작성합니다. \ No newline at end of file +구현 기능 목록 + +입력 처리 + 구입 금액 입력 받기 + 1,000원 단위로 입력 확인 + 잘못된 입력 처리 (ValueError 발생 및 [ERROR] 메시지 출력) + + 당첨 번호 입력 받기 + 쉼표(,)로 구분된 6개의 숫자 입력 + 숫자는 1~45 범위 내에 있어야 함 + 중복되지 않도록 검사 + 잘못된 입력 처리 (ValueError 발생 및 [ERROR] 메시지 출력) + +보너스 번호 입력 받기 + 숫자는 1~45 범위 내에 있어야 함 + 당첨 번호와 중복되지 않도록 검사 + 잘못된 입력 처리 (ValueError 발생 및 [ERROR] 메시지 출력) + + +로또 발행 + 입력된 금액에 따라 로또 발행 + 1,000원당 1개씩 생성 + 각 로또는 1~45 범위의 숫자 6개 (중복 없이) 랜덤 생성 + 생성된 로또 번호는 오름차순 정렬 + 발행된 로또 번호 출력 + + 당첨 결과 계산 + 각 로또 번호와 당첨 번호 비교 + 일치하는 번호 개수 계산 + 보너스 번호 일치 여부 확인 + + 당첨 내역 계산 + 각 당첨 등수 개수 출력 + +수익률 계산 + 총 당첨 금액 계산 + 수익률 계산 ((총 당첨 금액 / 구입 금액) * 100) + 소수점 둘째 자리에서 반올림하여 출력 + +예외 처리 + 입력값이 숫자가 아닐 경우 예외 처리 + 구입 금액이 1,000원 단위가 아닐 경우 예외 처리 + 당첨 번호가 6개가 아닐 경우 예외 처리 + 당첨 번호가 1~45 범위를 벗어날 경우 예외 처리 + 보너스 번호가 1~45 범위를 벗어날 경우 예외 처리 + 보너스 번호가 당첨 번호와 중복될 경우 예외 처리 \ No newline at end of file From a241007a8cf62d37661cfa77a252e4009c94a748 Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 19:29:39 +0900 Subject: [PATCH 02/32] =?UTF-8?q?feat:=20=EA=B5=AC=EC=9E=85=20=EA=B8=88?= =?UTF-8?q?=EC=95=A1=20=EC=9E=85=EB=A0=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/main.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lotto/main.py b/src/lotto/main.py index 5f270aa..5aee62f 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -1,6 +1,9 @@ -def main(): - # TODO: 프로그램 구현 - pass +def purchase_price_Input () : + purchase_price=int(input("구입금액을 입력해 주세요.")) + return purchase_price +def main(): + a=purchase_price_Input() + print(a) if __name__ == "__main__": main() From a9cd6868bfb8ea8a60e9e0b400cc8a0044faf419 Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 19:34:41 +0900 Subject: [PATCH 03/32] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/main.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/lotto/main.py b/src/lotto/main.py index 5aee62f..daf49e9 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -1,9 +1,17 @@ def purchase_price_Input () : - purchase_price=int(input("구입금액을 입력해 주세요.")) + purchase_price=input("구입금액을 입력해 주세요.") + if not purchase_price.isdigit(): + raise ValueError("[ERROR] 숫자를 입력해야 합니다.") + + purchase_price=int(purchase_price) + + if purchase_price %1000 !=0: + raise ValueError("[ERROR] 1000원 단위로 입력해야 합니다.") + return purchase_price def main(): - a=purchase_price_Input() - print(a) + purchase_price_Input() + if __name__ == "__main__": main() From 38464369cc214922ed48870b439d51cab88d7aa3 Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 20:15:42 +0900 Subject: [PATCH 04/32] =?UTF-8?q?feat:=20=EB=B3=B4=EB=84=88=EC=8A=A4=20?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EC=9E=85=EB=A0=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/main.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/lotto/main.py b/src/lotto/main.py index daf49e9..739776b 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -1,15 +1,36 @@ +import random + + def purchase_price_Input () : purchase_price=input("구입금액을 입력해 주세요.") if not purchase_price.isdigit(): raise ValueError("[ERROR] 숫자를 입력해야 합니다.") purchase_price=int(purchase_price) - + if purchase_price %1000 !=0: raise ValueError("[ERROR] 1000원 단위로 입력해야 합니다.") return purchase_price + +def winnernumber_Input () : + winnernumber=input("당첨 번호를 입력해 주세요.").split(',') + winnernumber=[int(i) for i in winnernumber] + + return winnernumber + + +def bonusnumber_Input(): + bonusnumber=input("보너스 번호를 입력해 주세요.") + if not bonusnumber.isdigit(): + raise ValueError("[ERROR] 숫자를 입력해야 합니다.") + + bonusnumber=int(bonusnumber) + + return bonusnumber + + def main(): purchase_price_Input() From e87c55e1e2dbb899160560804474a8d6be702854 Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 20:24:01 +0900 Subject: [PATCH 05/32] =?UTF-8?q?feat:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=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 --- src/lotto/main.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/lotto/main.py b/src/lotto/main.py index 739776b..088a0f5 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -1,6 +1,19 @@ import random +def Exception_handling(x): + if len(x) != 6: + raise ValueError("[ERROR] 당첨 번호는 6개여야 합니다.") + x=[int(i) for i in x] + + if any(n < 1 or n > 45 for n in x): + raise ValueError("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.") + if len(set(x)) != 6: + raise ValueError("[ERROR] 중복되지 않은 6개의 숫자를 입력해야 합니다.") + + return x + + def purchase_price_Input () : purchase_price=input("구입금액을 입력해 주세요.") if not purchase_price.isdigit(): @@ -16,8 +29,8 @@ def purchase_price_Input () : def winnernumber_Input () : winnernumber=input("당첨 번호를 입력해 주세요.").split(',') - winnernumber=[int(i) for i in winnernumber] - + winnernumber=Exception_handling(winnernumber) + return winnernumber @@ -32,7 +45,6 @@ def bonusnumber_Input(): def main(): - purchase_price_Input() if __name__ == "__main__": main() From a3b6564a4104a8e6c97e02d8e47306bddb824cad Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 20:29:28 +0900 Subject: [PATCH 06/32] =?UTF-8?q?feat:=20=EB=A1=9C=EB=98=90=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=83=9D=EC=84=B1=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/main.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/lotto/main.py b/src/lotto/main.py index 088a0f5..889b86d 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -44,7 +44,17 @@ def bonusnumber_Input(): return bonusnumber +def lottonumber(): + lotto=[] + while len(lotto)<=6: + a=random.randint(1,45) + lotto.append(a) + + return lotto + + def main(): - + pass + if __name__ == "__main__": main() From f7819e1eb4ce0113180c234b6d18f142cda93387 Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 20:40:13 +0900 Subject: [PATCH 07/32] =?UTF-8?q?feat:=20=EB=A1=9C=EB=98=90=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/main.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/lotto/main.py b/src/lotto/main.py index 889b86d..7b7646c 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -23,7 +23,8 @@ def purchase_price_Input () : if purchase_price %1000 !=0: raise ValueError("[ERROR] 1000원 단위로 입력해야 합니다.") - + purchase_price=purchase_price/1000 + print("%d개를 구입했습니다." %purchase_price) return purchase_price @@ -49,11 +50,20 @@ def lottonumber(): while len(lotto)<=6: a=random.randint(1,45) lotto.append(a) - + lotto.sort() + return lotto def main(): + lottocount=purchase_price_Input() + count=1 + + while count<=lottocount : + a=lottonumber() + print(a) + + count+=1 pass if __name__ == "__main__": From 15ac1d9b21b5985405bd59b34f40917674231fc0 Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 20:44:32 +0900 Subject: [PATCH 08/32] =?UTF-8?q?feat:=20=EB=A1=9C=EB=98=90=20=EB=8B=B9?= =?UTF-8?q?=EC=B2=A8=20=EB=B2=88=ED=98=B8=20=EB=B9=84=EA=B5=90=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/main.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/lotto/main.py b/src/lotto/main.py index 7b7646c..1a01925 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -51,10 +51,22 @@ def lottonumber(): a=random.randint(1,45) lotto.append(a) lotto.sort() - + return lotto +def compare(a,b,c) : + count=0 + for x,y in enumerate(a,b) : + if x==y : + count+=1 + + if c in a : + count+=1 + + return count + + def main(): lottocount=purchase_price_Input() count=1 @@ -64,6 +76,8 @@ def main(): print(a) count+=1 + + pass if __name__ == "__main__": From 030e2f5587fd9a27eebb5fe3bee28da2f79f47d2 Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 21:03:23 +0900 Subject: [PATCH 09/32] =?UTF-8?q?feat:=20=EB=A1=9C=EB=98=90=20=EB=8B=B9?= =?UTF-8?q?=EC=B2=A8=20=EB=B2=88=ED=98=B8=20=EB=B9=84=EA=B5=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/main.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/lotto/main.py b/src/lotto/main.py index 1a01925..6dcfba0 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -23,7 +23,7 @@ def purchase_price_Input () : if purchase_price %1000 !=0: raise ValueError("[ERROR] 1000원 단위로 입력해야 합니다.") - purchase_price=purchase_price/1000 + purchase_price=purchase_price//1000 print("%d개를 구입했습니다." %purchase_price) return purchase_price @@ -57,7 +57,7 @@ def lottonumber(): def compare(a,b,c) : count=0 - for x,y in enumerate(a,b) : + for x,y in enumerate(zip(a,b)) : if x==y : count+=1 @@ -70,15 +70,25 @@ def compare(a,b,c) : def main(): lottocount=purchase_price_Input() count=1 - + lottonumbers=[] while count<=lottocount : a=lottonumber() print(a) - + lottonumbers.append(a) count+=1 + winnumber=winnernumber_Input() + bonusnumber=bonusnumber_Input() + + + wincount=[] + for i in range(lottocount): + win=compare(lottonumbers[i],winnumber,bonusnumber) + wincount.append(win) + - pass + print(wincount) + if __name__ == "__main__": main() From 55710c205de0aa96778a90c378ddc27e2744c6e2 Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 21:21:31 +0900 Subject: [PATCH 10/32] =?UTF-8?q?feat:=20=EB=8B=B9=EC=B2=A8=EA=B8=88=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/main.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/lotto/main.py b/src/lotto/main.py index 6dcfba0..3aeb171 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -31,6 +31,7 @@ def purchase_price_Input () : def winnernumber_Input () : winnernumber=input("당첨 번호를 입력해 주세요.").split(',') winnernumber=Exception_handling(winnernumber) + winnernumber.sort() return winnernumber @@ -60,13 +61,24 @@ def compare(a,b,c) : for x,y in enumerate(zip(a,b)) : if x==y : count+=1 - + count=count*10 if c in a : count+=1 return count +def price(x): + y=x%10 + if x==3 : + return "5000" + elif x==4 : + return "50000" + elif x==5 and y==1 : + return "1,500,000" + elif x==6 : + return "2,000,000,000" + def main(): lottocount=purchase_price_Input() count=1 @@ -86,9 +98,13 @@ def main(): win=compare(lottonumbers[i],winnumber,bonusnumber) wincount.append(win) - + print("당첨 통계") + print("---") print(wincount) - + ''' + for i in range(len(lottocount)): + print("%d개 일치 %d") + ''' if __name__ == "__main__": main() From bbd658f85e3ecdf71d778280713e740bd25efb63 Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 21:28:46 +0900 Subject: [PATCH 11/32] =?UTF-8?q?fix:=20=EB=B9=84=EA=B5=90=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lotto/main.py b/src/lotto/main.py index 3aeb171..4db47f9 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -48,7 +48,7 @@ def bonusnumber_Input(): def lottonumber(): lotto=[] - while len(lotto)<=6: + while len(lotto)<=5: a=random.randint(1,45) lotto.append(a) lotto.sort() @@ -58,8 +58,8 @@ def lottonumber(): def compare(a,b,c) : count=0 - for x,y in enumerate(zip(a,b)) : - if x==y : + for i,num in enumerate(a) : + if num in b : count+=1 count=count*10 if c in a : From 6e100a2c9e80806b9c2be7a23fa8665ce79dadeb Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 21:44:19 +0900 Subject: [PATCH 12/32] =?UTF-8?q?fix:=20=EB=B9=84=EA=B5=90=20=EB=B2=84?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/main.py | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/lotto/main.py b/src/lotto/main.py index 4db47f9..0115439 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -47,10 +47,11 @@ def bonusnumber_Input(): def lottonumber(): - lotto=[] + lotto=set() while len(lotto)<=5: a=random.randint(1,45) - lotto.append(a) + lotto.add(a) + lotto=list(lotto) lotto.sort() return lotto @@ -69,14 +70,15 @@ def compare(a,b,c) : def price(x): - y=x%10 - if x==3 : + if x==30 : return "5000" - elif x==4 : + elif x==40 : return "50000" - elif x==5 and y==1 : + elif x==50 : + return "1,000,000" + elif x==51 : return "1,500,000" - elif x==6 : + elif x==60 : return "2,000,000,000" def main(): @@ -93,18 +95,28 @@ def main(): bonusnumber=bonusnumber_Input() - wincount=[] + wincount={"30": 0,"40": 0,"50": 0,"51": 0,"60": 0} for i in range(lottocount): win=compare(lottonumbers[i],winnumber,bonusnumber) - wincount.append(win) - + print(win) + if win == 30: + wincount["30"]+=1 + if win == 40: + wincount["40"]+=1 + if win == 50: + wincount["50"]+=1 + if win == 51: + wincount["51"]+=1 + if win == 60: + wincount["60"]+=1 + print("당첨 통계") print("---") print(wincount) - ''' - for i in range(len(lottocount)): - print("%d개 일치 %d") - ''' + + + + if __name__ == "__main__": main() From e1bf6191fb94a470217827ff59fb1b4dab6999dc Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 22:07:33 +0900 Subject: [PATCH 13/32] =?UTF-8?q?feat:=20=EB=8B=B9=EC=B2=A8=20=ED=86=B5?= =?UTF-8?q?=EA=B3=84=20=EB=B0=8F=20=EC=88=98=EC=9D=B5=EB=A5=A0=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/main.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/lotto/main.py b/src/lotto/main.py index 0115439..86738fa 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -48,7 +48,7 @@ def bonusnumber_Input(): def lottonumber(): lotto=set() - while len(lotto)<=5: + while len(lotto)<6: a=random.randint(1,45) lotto.add(a) lotto=list(lotto) @@ -71,15 +71,15 @@ def compare(a,b,c) : def price(x): if x==30 : - return "5000" + return 5000 elif x==40 : - return "50000" + return 50000 elif x==50 : - return "1,000,000" + return 1500000 elif x==51 : - return "1,500,000" + return 30000000 elif x==60 : - return "2,000,000,000" + return 2000000000 def main(): lottocount=purchase_price_Input() @@ -94,7 +94,7 @@ def main(): winnumber=winnernumber_Input() bonusnumber=bonusnumber_Input() - + totalprice=0 wincount={"30": 0,"40": 0,"50": 0,"51": 0,"60": 0} for i in range(lottocount): win=compare(lottonumbers[i],winnumber,bonusnumber) @@ -109,11 +109,18 @@ def main(): wincount["51"]+=1 if win == 60: wincount["60"]+=1 + totalprice+=price(win) + totalpricerate=round((lottocount*1000)/totalprice,2)*100 print("당첨 통계") print("---") - print(wincount) - + print("3개 일치 (5,000원) - %d개"%wincount["30"]) + print("4개 일치 (50,000원) - %d개"%wincount["40"]) + print("5개 일치 (1,500,000원) - %d개"%wincount["50"]) + print("5개 일치, 보너스 볼 일치 (30,000,000원) - %d개"%wincount["51"]) + print("6개 일치 (2,000,000,000원) - %d개"%wincount["60"]) + + print("총 수익률은 %d%입니다"%totalpricerate) From 46b25e14236c6b393c2dcab99432b0115d84baa7 Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 23:04:07 +0900 Subject: [PATCH 14/32] =?UTF-8?q?feat:=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/lotto.py | 144 ++++++++++++++++++++++++++++++++++++++++++- src/lotto/main.py | 149 ++++++++------------------------------------- 2 files changed, 169 insertions(+), 124 deletions(-) diff --git a/src/lotto/lotto.py b/src/lotto/lotto.py index 9c8c935..72d55f1 100644 --- a/src/lotto/lotto.py +++ b/src/lotto/lotto.py @@ -1,12 +1,152 @@ from typing import List +import random class Lotto: + """로또 번호 및 당첨 결과를 처리하는 클래스""" + def __init__(self, numbers: List[int]): self._validate(numbers) self._numbers = numbers def _validate(self, numbers: List[int]): + """로또 번호 검증: 개수, 중복, 범위""" if len(numbers) != 6: - raise ValueError + raise ValueError("로또 번호는 정확히 6개여야 합니다.") + if len(set(numbers)) != 6: + raise ValueError("로또 번호에 중복이 있어서는 안 됩니다.") + if not all(1 <= num <= 45 for num in numbers): + raise ValueError("로또 번호는 1부터 45 사이여야 합니다.") + + + def get_lotto_count(): + """로또 구입 금액 입력 및 예외 처리""" + try: + amount = input("로또 구입 금액을 입력하세요: ") + Lotto._validate_amount(amount) + amount = int(amount) + lotto_count = amount // 1000 + print(f"{lotto_count}개의 로또를 구입합니다.") + return lotto_count + except ValueError as e: + print(e) + raise + + + def _validate_amount(amount): + """로또 금액 검증""" + if not amount.isdigit(): + raise ValueError("[ERROR] 숫자를 입력하세요.") + amount = int(amount) + if amount < 1000 or amount % 1000 != 0: + raise ValueError("[ERROR] 최소 1,000원 이상, 1,000원 단위로 입력해야 합니다.") + + + def generate_random_lotto(): + """무작위 로또 번호 생성""" + return Lotto(sorted(random.sample(range(1, 46), 6))) + + + def generate_lottos(count: int): + """구입한 로또 개수만큼 번호 생성""" + return [Lotto.generate_random_lotto() for _ in range(count)] + + + def get_winning_numbers(): + """당첨 번호 및 보너스 번호 입력""" + numbers = Lotto._get_valid_numbers("당첨 번호를 입력해 주세요: ", 6) + bonus = Lotto._get_valid_bonus("보너스 번호 1개를 입력하세요: ", numbers) + return numbers, bonus + + + def _get_valid_numbers(prompt, count): + """유효한 번호 입력 받기""" + while True: + try: + numbers = list(map(int, input(prompt).replace(',', ' ').split())) + Lotto._validate_numbers(numbers, count) + return numbers + except ValueError as e: + print(e) + + + def _validate_numbers(numbers, count): + """입력된 숫자 검증""" + if len(numbers) != count or len(set(numbers)) != count: + raise ValueError(f"[ERROR] {count}개의 숫자를 입력해야 하며, 중복이 없어야 합니다.") + if not all(1 <= num <= 45 for num in numbers): + raise ValueError("[ERROR] 숫자는 1부터 45 사이여야 합니다.") + + + def _get_valid_bonus(prompt, numbers): + """보너스 번호 입력 받기""" + while True: + bonus = Lotto._safe_input_bonus(prompt) + if Lotto._is_valid_bonus(bonus, numbers): + return bonus + print("[ERROR] 올바른 보너스 번호를 입력하세요.") + + + def _safe_input_bonus(prompt): + """보너스 번호 안전 입력""" + try: + return int(input(prompt)) + except ValueError: + return -1 # 잘못된 값 반환 (검증에서 걸러짐) + + + def _is_valid_bonus(bonus, numbers): + """보너스 번호 검증""" + if bonus in numbers or not (1 <= bonus <= 45): + return False + return True + + + def check_results(purchased_lottos, winning_numbers, bonus_number): + """구입한 로또 번호와 당첨 번호 비교""" + results = {3: 0, 4: 0, 5: 0, "5_bonus": 0, 6: 0} + for lotto in purchased_lottos: + matched_count, has_bonus = Lotto._count_matches( + lotto._numbers, + winning_numbers, + bonus_number + ) + key = Lotto._determine_prize_key(matched_count, has_bonus) + if key: + results[key] += 1 + return results + + + def _count_matches(lotto_numbers, winning_numbers, bonus_number): + """로또 번호와 당첨 번호 비교하여 일치 개수와 보너스 여부 반환""" + matched_count = len(set(lotto_numbers) & set(winning_numbers)) + has_bonus = bonus_number in lotto_numbers + return matched_count, has_bonus + + + def _determine_prize_key(matched_count, has_bonus): + """당첨 등수 판별""" + if matched_count == 5 and has_bonus: + return "5_bonus" + if matched_count in {3, 4, 5, 6}: + return matched_count + return None + + def print_results(results, total_cost): + """당첨 통계를 출력""" + print("\n당첨 통계") + print("---") + print(f"3개 일치 (5,000원) - {results[3]}개") + print(f"4개 일치 (50,000원) - {results[4]}개") + print(f"5개 일치 (1,500,000원) - {results[5]}개") + print(f"5개 일치, 보너스 볼 일치 (30,000,000원) - {results['5_bonus']}개") + print(f"6개 일치 (2,000,000,000원) - {results[6]}개") + total_prize = ( + results[3] * 5000 + + results[4] * 50000 + + results[5] * 1500000 + + results["5_bonus"] * 30000000 + + results[6] * 2000000000 + ) + profit_ratio = (total_prize / total_cost) * 100 if total_cost else 0 + print(f"총 수익률은 {profit_ratio:.1f}%입니다.") - # TODO: 추가 기능 구현 diff --git a/src/lotto/main.py b/src/lotto/main.py index 86738fa..a997069 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -1,129 +1,34 @@ -import random - - -def Exception_handling(x): - if len(x) != 6: - raise ValueError("[ERROR] 당첨 번호는 6개여야 합니다.") - x=[int(i) for i in x] - - if any(n < 1 or n > 45 for n in x): - raise ValueError("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.") - if len(set(x)) != 6: - raise ValueError("[ERROR] 중복되지 않은 6개의 숫자를 입력해야 합니다.") - - return x - - -def purchase_price_Input () : - purchase_price=input("구입금액을 입력해 주세요.") - if not purchase_price.isdigit(): - raise ValueError("[ERROR] 숫자를 입력해야 합니다.") - - purchase_price=int(purchase_price) - - if purchase_price %1000 !=0: - raise ValueError("[ERROR] 1000원 단위로 입력해야 합니다.") - purchase_price=purchase_price//1000 - print("%d개를 구입했습니다." %purchase_price) - return purchase_price - - -def winnernumber_Input () : - winnernumber=input("당첨 번호를 입력해 주세요.").split(',') - winnernumber=Exception_handling(winnernumber) - winnernumber.sort() - - return winnernumber - - -def bonusnumber_Input(): - bonusnumber=input("보너스 번호를 입력해 주세요.") - if not bonusnumber.isdigit(): - raise ValueError("[ERROR] 숫자를 입력해야 합니다.") - - bonusnumber=int(bonusnumber) - - return bonusnumber - - -def lottonumber(): - lotto=set() - while len(lotto)<6: - a=random.randint(1,45) - lotto.add(a) - lotto=list(lotto) - lotto.sort() - - return lotto - - -def compare(a,b,c) : - count=0 - for i,num in enumerate(a) : - if num in b : - count+=1 - count=count*10 - if c in a : - count+=1 - - return count - - -def price(x): - if x==30 : - return 5000 - elif x==40 : - return 50000 - elif x==50 : - return 1500000 - elif x==51 : - return 30000000 - elif x==60 : - return 2000000000 +from lotto import Lotto def main(): - lottocount=purchase_price_Input() - count=1 - lottonumbers=[] - while count<=lottocount : - a=lottonumber() - print(a) - lottonumbers.append(a) - count+=1 - - winnumber=winnernumber_Input() - bonusnumber=bonusnumber_Input() + # 1. 로또 구입 금액을 입력하여 구입할 로또 개수 계산 + try: + lotto_count = Lotto.get_lotto_count() # 로또 구입 금액 입력 + except ValueError as e: + print("[ERROR] 구입 금액이 잘못되었습니다.") + return - totalprice=0 - wincount={"30": 0,"40": 0,"50": 0,"51": 0,"60": 0} - for i in range(lottocount): - win=compare(lottonumbers[i],winnumber,bonusnumber) - print(win) - if win == 30: - wincount["30"]+=1 - if win == 40: - wincount["40"]+=1 - if win == 50: - wincount["50"]+=1 - if win == 51: - wincount["51"]+=1 - if win == 60: - wincount["60"]+=1 - totalprice+=price(win) - - totalpricerate=round((lottocount*1000)/totalprice,2)*100 - print("당첨 통계") - print("---") - print("3개 일치 (5,000원) - %d개"%wincount["30"]) - print("4개 일치 (50,000원) - %d개"%wincount["40"]) - print("5개 일치 (1,500,000원) - %d개"%wincount["50"]) - print("5개 일치, 보너스 볼 일치 (30,000,000원) - %d개"%wincount["51"]) - print("6개 일치 (2,000,000,000원) - %d개"%wincount["60"]) - - print("총 수익률은 %d%입니다"%totalpricerate) - - + # 2. 로또 번호 생성 (구입한 개수만큼 로또 번호 생성) + purchased_lottos = Lotto.generate_lottos(lotto_count) + + # 📌 로또 번호 출력 + print(f"\n{lotto_count}개를 구매했습니다.") + for lotto in purchased_lottos: + print(f"{lotto._numbers}") # ✅ 리스트 형태를 그대로 출력 + + # 3. 당첨 번호와 보너스 번호 입력 + try: + winning_numbers, bonus_number = Lotto.get_winning_numbers() # 당첨 번호 및 보너스 번호 입력 + except ValueError as e: + print(f"[ERROR] {e}") + return + + # 4. 당첨 결과 확인 (로또 번호와 당첨 번호 비교) + results = Lotto.check_results(purchased_lottos, winning_numbers, bonus_number) + # 5. 당첨 통계 및 수익률 출력 + total_cost = lotto_count * 1000 # 총 구입 금액 + Lotto.print_results(results, total_cost) if __name__ == "__main__": main() From 8ff95bc91fce656516aa644bc55152182e19a668 Mon Sep 17 00:00:00 2001 From: akran Date: Thu, 13 Feb 2025 23:39:20 +0900 Subject: [PATCH 15/32] =?UTF-8?q?feat:=20=EC=99=84=EC=84=B1=EB=B3=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/check-no-external-libs.yml | 2 +- src/lotto/lotto.py | 45 +++++++++++--------- src/lotto/main.py | 15 +++---- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/.github/workflows/check-no-external-libs.yml b/.github/workflows/check-no-external-libs.yml index cd8918d..009111d 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/src/lotto/lotto.py b/src/lotto/lotto.py index 72d55f1..4cc2754 100644 --- a/src/lotto/lotto.py +++ b/src/lotto/lotto.py @@ -3,7 +3,8 @@ class Lotto: """로또 번호 및 당첨 결과를 처리하는 클래스""" - + ERROR_MESSAGE = "[ERROR] 구입 금액이 잘못되었습니다." + def __init__(self, numbers: List[int]): self._validate(numbers) self._numbers = numbers @@ -17,12 +18,13 @@ def _validate(self, numbers: List[int]): if not all(1 <= num <= 45 for num in numbers): raise ValueError("로또 번호는 1부터 45 사이여야 합니다.") - + def get_lotto_count(): """로또 구입 금액 입력 및 예외 처리""" try: amount = input("로또 구입 금액을 입력하세요: ") Lotto._validate_amount(amount) + amount = int(amount) lotto_count = amount // 1000 print(f"{lotto_count}개의 로또를 구입합니다.") @@ -31,44 +33,46 @@ def get_lotto_count(): print(e) raise - + def _validate_amount(amount): """로또 금액 검증""" - if not amount.isdigit(): + # 숫자만 포함된 문자열인지 확인 + if not amount.isdigit(): # 숫자가 아닌 문자가 포함되면 예외 raise ValueError("[ERROR] 숫자를 입력하세요.") - amount = int(amount) - if amount < 1000 or amount % 1000 != 0: - raise ValueError("[ERROR] 최소 1,000원 이상, 1,000원 단위로 입력해야 합니다.") + + amount = int(amount) # 숫자로 변환 + if amount < 1000 or amount % 1000 != 0: # 금액이 1000원 이상이거나 1000원 단위로 입력되지 않으면 예외 + raise ValueError(Lotto.ERROR_MESSAGE) - + def generate_random_lotto(): """무작위 로또 번호 생성""" return Lotto(sorted(random.sample(range(1, 46), 6))) - + def generate_lottos(count: int): """구입한 로또 개수만큼 번호 생성""" return [Lotto.generate_random_lotto() for _ in range(count)] - + def get_winning_numbers(): """당첨 번호 및 보너스 번호 입력""" numbers = Lotto._get_valid_numbers("당첨 번호를 입력해 주세요: ", 6) bonus = Lotto._get_valid_bonus("보너스 번호 1개를 입력하세요: ", numbers) return numbers, bonus - + def _get_valid_numbers(prompt, count): """유효한 번호 입력 받기""" while True: try: - numbers = list(map(int, input(prompt).replace(',', ' ').split())) - Lotto._validate_numbers(numbers, count) + numbers = list(map(int, input(prompt).replace(',', ' ').split())) # ','를 공백으로 바꾸고, 스페이스로 구분 + Lotto._validate_numbers(numbers, count) # 번호 검증 return numbers except ValueError as e: print(e) - + def _validate_numbers(numbers, count): """입력된 숫자 검증""" if len(numbers) != count or len(set(numbers)) != count: @@ -76,7 +80,7 @@ def _validate_numbers(numbers, count): if not all(1 <= num <= 45 for num in numbers): raise ValueError("[ERROR] 숫자는 1부터 45 사이여야 합니다.") - + def _get_valid_bonus(prompt, numbers): """보너스 번호 입력 받기""" while True: @@ -85,7 +89,7 @@ def _get_valid_bonus(prompt, numbers): return bonus print("[ERROR] 올바른 보너스 번호를 입력하세요.") - + def _safe_input_bonus(prompt): """보너스 번호 안전 입력""" try: @@ -93,14 +97,14 @@ def _safe_input_bonus(prompt): except ValueError: return -1 # 잘못된 값 반환 (검증에서 걸러짐) - + def _is_valid_bonus(bonus, numbers): """보너스 번호 검증""" if bonus in numbers or not (1 <= bonus <= 45): return False return True - + def check_results(purchased_lottos, winning_numbers, bonus_number): """구입한 로또 번호와 당첨 번호 비교""" results = {3: 0, 4: 0, 5: 0, "5_bonus": 0, 6: 0} @@ -115,14 +119,14 @@ def check_results(purchased_lottos, winning_numbers, bonus_number): results[key] += 1 return results - + def _count_matches(lotto_numbers, winning_numbers, bonus_number): """로또 번호와 당첨 번호 비교하여 일치 개수와 보너스 여부 반환""" matched_count = len(set(lotto_numbers) & set(winning_numbers)) has_bonus = bonus_number in lotto_numbers return matched_count, has_bonus - + def _determine_prize_key(matched_count, has_bonus): """당첨 등수 판별""" if matched_count == 5 and has_bonus: @@ -131,6 +135,7 @@ def _determine_prize_key(matched_count, has_bonus): return matched_count return None + def print_results(results, total_cost): """당첨 통계를 출력""" print("\n당첨 통계") diff --git a/src/lotto/main.py b/src/lotto/main.py index a997069..bdac369 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -1,20 +1,17 @@ -from lotto import Lotto +from lotto.lotto import Lotto def main(): - # 1. 로또 구입 금액을 입력하여 구입할 로또 개수 계산 - try: - lotto_count = Lotto.get_lotto_count() # 로또 구입 금액 입력 - except ValueError as e: - print("[ERROR] 구입 금액이 잘못되었습니다.") - return + + lotto_count = Lotto.get_lotto_count() # 로또 구입 금액 입력 + # 2. 로또 번호 생성 (구입한 개수만큼 로또 번호 생성) purchased_lottos = Lotto.generate_lottos(lotto_count) - # 📌 로또 번호 출력 + # 로또 번호 출력 print(f"\n{lotto_count}개를 구매했습니다.") for lotto in purchased_lottos: - print(f"{lotto._numbers}") # ✅ 리스트 형태를 그대로 출력 + print(f"{lotto._numbers}") # 리스트 형태를 그대로 출력 # 3. 당첨 번호와 보너스 번호 입력 try: From 4824cc17e97a87b1f381427dfaf70b07840b5991 Mon Sep 17 00:00:00 2001 From: akran Date: Sat, 15 Feb 2025 23:37:56 +0900 Subject: [PATCH 16/32] =?UTF-8?q?fix:=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pytest.ini | 2 + src/lotto/__init__.py | 4 + src/lotto/lotto.py | 183 ++++++++++----------------------------- src/lotto/main.py | 133 +++++++++++++++++++++++++--- tests/lotto/test_main.py | 2 + 5 files changed, 172 insertions(+), 152 deletions(-) diff --git a/pytest.ini b/pytest.ini index fcccae1..c701c12 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,2 +1,4 @@ [pytest] pythonpath = src +markers = + custom_name: 테스트 설명을 위한 커스텀 마커 \ No newline at end of file diff --git a/src/lotto/__init__.py b/src/lotto/__init__.py index e69de29..5042919 100644 --- a/src/lotto/__init__.py +++ b/src/lotto/__init__.py @@ -0,0 +1,4 @@ +from .lotto import Lotto + + +__all__ = ["Lotto"] \ No newline at end of file diff --git a/src/lotto/lotto.py b/src/lotto/lotto.py index 4cc2754..a47e8f7 100644 --- a/src/lotto/lotto.py +++ b/src/lotto/lotto.py @@ -1,15 +1,46 @@ -from typing import List import random - -class Lotto: +from enum import Enum + + +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): + """ + Rank 객체 초기화 + """ + self.match_cnt = match_cnt + self.bonus_match = bonus_match + self.prize = prize + + @classmethod + def get_rank(cls, match_cnt, bonus): + """ + 일치 개수와 보너스 번호 여부를 기반으로 당첨 순위 반환 + """ + for rank in cls: + if rank.match_cnt == match_cnt and rank.bonus_match == bonus: + return rank + return cls.NONE + + +class Lotto(): """로또 번호 및 당첨 결과를 처리하는 클래스""" ERROR_MESSAGE = "[ERROR] 구입 금액이 잘못되었습니다." - 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("로또 번호는 정확히 6개여야 합니다.") @@ -19,139 +50,15 @@ def _validate(self, numbers: List[int]): raise ValueError("로또 번호는 1부터 45 사이여야 합니다.") - def get_lotto_count(): - """로또 구입 금액 입력 및 예외 처리""" - try: - amount = input("로또 구입 금액을 입력하세요: ") - Lotto._validate_amount(amount) - - amount = int(amount) - lotto_count = amount // 1000 - print(f"{lotto_count}개의 로또를 구입합니다.") - return lotto_count - except ValueError as e: - print(e) - raise - - - def _validate_amount(amount): - """로또 금액 검증""" - # 숫자만 포함된 문자열인지 확인 - if not amount.isdigit(): # 숫자가 아닌 문자가 포함되면 예외 - raise ValueError("[ERROR] 숫자를 입력하세요.") - - amount = int(amount) # 숫자로 변환 - if amount < 1000 or amount % 1000 != 0: # 금액이 1000원 이상이거나 1000원 단위로 입력되지 않으면 예외 - raise ValueError(Lotto.ERROR_MESSAGE) - - - def generate_random_lotto(): + @classmethod + def generate_random_lotto(cls): """무작위 로또 번호 생성""" - return Lotto(sorted(random.sample(range(1, 46), 6))) - - - def generate_lottos(count: int): - """구입한 로또 개수만큼 번호 생성""" - return [Lotto.generate_random_lotto() for _ in range(count)] - - - def get_winning_numbers(): - """당첨 번호 및 보너스 번호 입력""" - numbers = Lotto._get_valid_numbers("당첨 번호를 입력해 주세요: ", 6) - bonus = Lotto._get_valid_bonus("보너스 번호 1개를 입력하세요: ", numbers) - return numbers, bonus + return cls(random.sample(range(1, 46), 6)) - - def _get_valid_numbers(prompt, count): - """유효한 번호 입력 받기""" - while True: - try: - numbers = list(map(int, input(prompt).replace(',', ' ').split())) # ','를 공백으로 바꾸고, 스페이스로 구분 - Lotto._validate_numbers(numbers, count) # 번호 검증 - return numbers - except ValueError as e: - print(e) - - - def _validate_numbers(numbers, count): - """입력된 숫자 검증""" - if len(numbers) != count or len(set(numbers)) != count: - raise ValueError(f"[ERROR] {count}개의 숫자를 입력해야 하며, 중복이 없어야 합니다.") - if not all(1 <= num <= 45 for num in numbers): - raise ValueError("[ERROR] 숫자는 1부터 45 사이여야 합니다.") - - def _get_valid_bonus(prompt, numbers): - """보너스 번호 입력 받기""" - while True: - bonus = Lotto._safe_input_bonus(prompt) - if Lotto._is_valid_bonus(bonus, numbers): - return bonus - print("[ERROR] 올바른 보너스 번호를 입력하세요.") - - - def _safe_input_bonus(prompt): - """보너스 번호 안전 입력""" - try: - return int(input(prompt)) - except ValueError: - return -1 # 잘못된 값 반환 (검증에서 걸러짐) - - - def _is_valid_bonus(bonus, numbers): - """보너스 번호 검증""" - if bonus in numbers or not (1 <= bonus <= 45): - return False - return True - - - def check_results(purchased_lottos, winning_numbers, bonus_number): - """구입한 로또 번호와 당첨 번호 비교""" - results = {3: 0, 4: 0, 5: 0, "5_bonus": 0, 6: 0} - for lotto in purchased_lottos: - matched_count, has_bonus = Lotto._count_matches( - lotto._numbers, - winning_numbers, - bonus_number - ) - key = Lotto._determine_prize_key(matched_count, has_bonus) - if key: - results[key] += 1 - return results - - - def _count_matches(lotto_numbers, winning_numbers, bonus_number): - """로또 번호와 당첨 번호 비교하여 일치 개수와 보너스 여부 반환""" - matched_count = len(set(lotto_numbers) & set(winning_numbers)) - has_bonus = bonus_number in lotto_numbers - return matched_count, has_bonus - - - def _determine_prize_key(matched_count, has_bonus): - """당첨 등수 판별""" - if matched_count == 5 and has_bonus: - return "5_bonus" - if matched_count in {3, 4, 5, 6}: - return matched_count - return None - - - def print_results(results, total_cost): - """당첨 통계를 출력""" - print("\n당첨 통계") - print("---") - print(f"3개 일치 (5,000원) - {results[3]}개") - print(f"4개 일치 (50,000원) - {results[4]}개") - print(f"5개 일치 (1,500,000원) - {results[5]}개") - print(f"5개 일치, 보너스 볼 일치 (30,000,000원) - {results['5_bonus']}개") - print(f"6개 일치 (2,000,000,000원) - {results[6]}개") - total_prize = ( - results[3] * 5000 - + results[4] * 50000 - + results[5] * 1500000 - + results["5_bonus"] * 30000000 - + results[6] * 2000000000 - ) - profit_ratio = (total_prize / total_cost) * 100 if total_cost else 0 - print(f"총 수익률은 {profit_ratio:.1f}%입니다.") + def get_numbers(self): + return self._numbers + def __str__(self): + """str 형식으로 변환하여 반환""" + return str(self._numbers) diff --git a/src/lotto/main.py b/src/lotto/main.py index bdac369..fda4ac9 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -1,31 +1,136 @@ -from lotto.lotto import Lotto +from .lotto import Lotto, Rank + + + +def _validate_amount(amount): + """로또 금액 검증""" + # 숫자만 포함된 문자열인지 확인 + if not amount.isdigit(): # 숫자가 아닌 문자가 포함되면 예외 + raise ValueError("[ERROR] 숫자를 입력하세요.") + + amount = int(amount) # 숫자로 변환 + if amount < 1000 or amount % 1000 != 0: # 금액이 1000원 이상이거나 1000원 단위로 입력되지 않으면 예외 + raise ValueError("[ERROR] 1000원 단위로 입력해야 합니다. .") + + +def get_lotto_count(): + """로또 구입 금액 입력 및 예외 처리""" + try: + amount = input("로또 구입 금액을 입력하세요: ") + _validate_amount(amount) + + amount = int(amount) + lotto_count = amount // 1000 + print(f"{lotto_count}개의 로또를 구입합니다.") + return lotto_count + except ValueError as e: + print(e) + raise + + + +def _validate_numbers(numbers, count): + """입력된 숫자 검증""" + if len(numbers) != count or len(set(numbers)) != count: + raise ValueError(f"[ERROR] {count}개의 숫자를 입력해야 하며, 중복이 없어야 합니다.") + if not all(1 <= num <= 45 for num in numbers): + raise ValueError("[ERROR] 숫자는 1부터 45 사이여야 합니다.") + +def print_lotto_tickets(tickets): + print(f"\n{len(tickets)}개를 구매했습니다.") + for ticket in tickets: + print(ticket) + +def get_winning_numbers(): + while True: + print("\n당첨 번호를 입력해 주세요.") + try: + winning_numbers = list(map(int, input().split(","))) + count=len(winning_numbers) + _validate_numbers(winning_numbers,count) + print(str(winning_numbers)) + return winning_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 get_bonus_number(winning_numbers): + """보너스 번호 입력 (유효성 검증 포함)""" + while True: + try: + bonus_number = input("보너스 번호를 입력하세요: ") + return check_bonus_number(bonus_number, winning_numbers) + except ValueError as error: + print(f"[ERROR] {error}") + + + +def check_results(purchased_lottos, winning_numbers, bonus_number): + """구입한 로또 번호와 당첨 번호 비교""" + + # ✅ Rank.NONE도 포함하도록 초기화 + results = {rank: 0 for rank in Rank} + totalprize = 0 + + for lotto in purchased_lottos: + lotto_numbers = lotto.get_numbers() # ✅ 리스트 형태의 로또 번호 가져오기 + matched_count = len(set(lotto_numbers) & set(winning_numbers)) # ✅ 로또 번호 비교 + has_bonus = bonus_number in lotto_numbers # ✅ 보너스 번호가 포함되어 있는지 확인 + + rank = Rank.get_rank(matched_count, has_bonus) # ✅ 항상 Rank 객체 반환 + results[rank] += 1 # ✅ 꽝(0개 맞춤)도 정상 카운트됨 + totalprize += rank.prize # ✅ 당첨 금액 합산 + + return results, totalprize + + + +def print_result(result, totalprize, amount): + profit_percentage = round((totalprize / (amount * 1000)) * 100, 2) + + print("\n당첨 통계") + print("---") + for rank in Rank: + if rank == Rank.SECOND: + print(f"{rank.match_cnt}개 일치, 보너스 볼 일치 ({rank.prize:,}원) - " + f"{result[rank]}개") + + if rank != Rank.NONE and rank != Rank.SECOND: + print(f"{rank.match_cnt}개 일치 ({rank.prize:,}원) - " + f"{result[rank]}개") + + print(f"총 수익률은 {profit_percentage}%입니다.") def main(): - lotto_count = Lotto.get_lotto_count() # 로또 구입 금액 입력 + lotto_count = get_lotto_count() # 로또 구입 금액 입력 # 2. 로또 번호 생성 (구입한 개수만큼 로또 번호 생성) - purchased_lottos = Lotto.generate_lottos(lotto_count) + purchased_lottos = [Lotto.generate_random_lotto() for _ in range(lotto_count)] + print(purchased_lottos) - # 로또 번호 출력 - print(f"\n{lotto_count}개를 구매했습니다.") - for lotto in purchased_lottos: - print(f"{lotto._numbers}") # 리스트 형태를 그대로 출력 # 3. 당첨 번호와 보너스 번호 입력 - try: - winning_numbers, bonus_number = Lotto.get_winning_numbers() # 당첨 번호 및 보너스 번호 입력 - except ValueError as e: - print(f"[ERROR] {e}") - return + + winning_numbers = get_winning_numbers() # 당첨 번호 및 보너스 번호 입력 + bonus_number = get_bonus_number(winning_numbers) # 4. 당첨 결과 확인 (로또 번호와 당첨 번호 비교) - results = Lotto.check_results(purchased_lottos, winning_numbers, bonus_number) + results, totalprize = check_results(purchased_lottos, winning_numbers, bonus_number) # 5. 당첨 통계 및 수익률 출력 total_cost = lotto_count * 1000 # 총 구입 금액 - Lotto.print_results(results, total_cost) + print_result(results, totalprize, total_cost) if __name__ == "__main__": main() diff --git a/tests/lotto/test_main.py b/tests/lotto/test_main.py index cb89654..7df2e0f 100644 --- a/tests/lotto/test_main.py +++ b/tests/lotto/test_main.py @@ -57,3 +57,5 @@ def test_예외_테스트(): # 잘못된 금액 입력 with patch("builtins.input", side_effect=["1000j"]): main() + + From fc4ccdbce28cd8bd4cdfb2a2a3ee3f2e78a83d55 Mon Sep 17 00:00:00 2001 From: akran Date: Sat, 15 Feb 2025 23:47:15 +0900 Subject: [PATCH 17/32] =?UTF-8?q?fix:=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/main.py | 134 ++++++++++++++++------------------------------ 1 file changed, 47 insertions(+), 87 deletions(-) diff --git a/src/lotto/main.py b/src/lotto/main.py index fda4ac9..5b99e72 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -1,55 +1,35 @@ -from .lotto import Lotto, Rank - - - -def _validate_amount(amount): - """로또 금액 검증""" - # 숫자만 포함된 문자열인지 확인 - if not amount.isdigit(): # 숫자가 아닌 문자가 포함되면 예외 - raise ValueError("[ERROR] 숫자를 입력하세요.") - - amount = int(amount) # 숫자로 변환 - if amount < 1000 or amount % 1000 != 0: # 금액이 1000원 이상이거나 1000원 단위로 입력되지 않으면 예외 - raise ValueError("[ERROR] 1000원 단위로 입력해야 합니다. .") - - -def get_lotto_count(): - """로또 구입 금액 입력 및 예외 처리""" +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: - amount = input("로또 구입 금액을 입력하세요: ") - _validate_amount(amount) - - amount = int(amount) - lotto_count = amount // 1000 - print(f"{lotto_count}개의 로또를 구입합니다.") - return lotto_count - except ValueError as e: - print(e) + print("구입금액을 입력해 주세요.") + amount = input() + return check_amount(amount) + except ValueError as error: + print(f"[ERROR] {error}") raise - - -def _validate_numbers(numbers, count): - """입력된 숫자 검증""" - if len(numbers) != count or len(set(numbers)) != count: - raise ValueError(f"[ERROR] {count}개의 숫자를 입력해야 하며, 중복이 없어야 합니다.") - if not all(1 <= num <= 45 for num in numbers): - raise ValueError("[ERROR] 숫자는 1부터 45 사이여야 합니다.") - def print_lotto_tickets(tickets): print(f"\n{len(tickets)}개를 구매했습니다.") for ticket in tickets: - print(ticket) + print(ticket) # ✅ __str__() 사용하여 출력 -def get_winning_numbers(): +def prompt_winning_numbers(): while True: print("\n당첨 번호를 입력해 주세요.") try: winning_numbers = list(map(int, input().split(","))) - count=len(winning_numbers) - _validate_numbers(winning_numbers,count) - print(str(winning_numbers)) - return winning_numbers + return Lotto(winning_numbers).get_numbers() except ValueError as error: print(f"[ERROR] {error}") @@ -62,75 +42,55 @@ def check_bonus_number(bonus_num, winning_numbers): raise ValueError("로또 번호의 숫자 범위는 1~45까지입니다.") return int(bonus_num) - -def get_bonus_number(winning_numbers): - """보너스 번호 입력 (유효성 검증 포함)""" +def prompt_bonus_number(winning_numbers): while True: try: - bonus_number = input("보너스 번호를 입력하세요: ") - return check_bonus_number(bonus_number, winning_numbers) + bonus_num = input("\n보너스 번호를 입력해 주세요.\n") + return check_bonus_number(bonus_num, winning_numbers) except ValueError as error: print(f"[ERROR] {error}") - - -def check_results(purchased_lottos, winning_numbers, bonus_number): - """구입한 로또 번호와 당첨 번호 비교""" - - # ✅ Rank.NONE도 포함하도록 초기화 +def evaluate_tickets(tickets, winning_numbers, bonus_num): results = {rank: 0 for rank in Rank} - totalprize = 0 - - for lotto in purchased_lottos: - lotto_numbers = lotto.get_numbers() # ✅ 리스트 형태의 로또 번호 가져오기 - matched_count = len(set(lotto_numbers) & set(winning_numbers)) # ✅ 로또 번호 비교 - has_bonus = bonus_number in lotto_numbers # ✅ 보너스 번호가 포함되어 있는지 확인 - - rank = Rank.get_rank(matched_count, has_bonus) # ✅ 항상 Rank 객체 반환 - results[rank] += 1 # ✅ 꽝(0개 맞춤)도 정상 카운트됨 - totalprize += rank.prize # ✅ 당첨 금액 합산 + total_prize = 0 - return results, totalprize + for ticket in tickets: + ticket_numbers = ticket.get_numbers() # ✅ 변수명 변경 + match_count = len(set(winning_numbers) & set(ticket_numbers)) # ✅ 비교 순서 통일 + bonus = bonus_num in ticket_numbers # ✅ 보너스 번호 `int` 변환 후 비교 + rank = Rank.get_rank(match_count, bonus) + results[rank] += 1 + total_prize += rank.prize + return results, total_prize -def print_result(result, totalprize, amount): - profit_percentage = round((totalprize / (amount * 1000)) * 100, 2) +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"{result[rank]}개") + f"{results[rank]}개") if rank != Rank.NONE and rank != Rank.SECOND: print(f"{rank.match_cnt}개 일치 ({rank.prize:,}원) - " - f"{result[rank]}개") + f"{results[rank]}개") print(f"총 수익률은 {profit_percentage}%입니다.") def main(): - - lotto_count = get_lotto_count() # 로또 구입 금액 입력 - - - # 2. 로또 번호 생성 (구입한 개수만큼 로또 번호 생성) - purchased_lottos = [Lotto.generate_random_lotto() for _ in range(lotto_count)] - print(purchased_lottos) - - - # 3. 당첨 번호와 보너스 번호 입력 - - winning_numbers = get_winning_numbers() # 당첨 번호 및 보너스 번호 입력 - bonus_number = get_bonus_number(winning_numbers) - - # 4. 당첨 결과 확인 (로또 번호와 당첨 번호 비교) - results, totalprize = check_results(purchased_lottos, winning_numbers, bonus_number) - - # 5. 당첨 통계 및 수익률 출력 - total_cost = lotto_count * 1000 # 총 구입 금액 - print_result(results, totalprize, total_cost) + amount = prompt_purchase_amount() + tickets = [Lotto.generate_random_lotto() for _ in range(amount)] # ✅ 메서드명 수정 + print_lotto_tickets(tickets) + + winning_numbers = prompt_winning_numbers() + bonus_num = prompt_bonus_number(winning_numbers) # ✅ `int` 변환 적용 + + results, total_prize = evaluate_tickets(tickets, winning_numbers, bonus_num) + print_results(results, total_prize, amount) if __name__ == "__main__": main() From 197bb9f36413468935242432363f41b3731d43de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=B1=EC=8A=B9=EC=9A=B0?= Date: Thu, 13 Feb 2025 19:45:01 +0900 Subject: [PATCH 18/32] [#0] rename `requirement.txt` -> `requirements.txt` to make it usual filename --- requirement.txt => requirements.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename requirement.txt => requirements.txt (100%) diff --git a/requirement.txt b/requirements.txt similarity index 100% rename from requirement.txt rename to requirements.txt From c379455bffafaa4957b404e9845079edf935e0fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=B1=EC=8A=B9=EC=9A=B0?= Date: Thu, 13 Feb 2025 19:45:31 +0900 Subject: [PATCH 19/32] [#0] add custom marker for test descriptions in pytest configuration --- pytest.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pytest.ini b/pytest.ini index c701c12..fed1f09 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,4 +1,5 @@ [pytest] pythonpath = src markers = - custom_name: 테스트 설명을 위한 커스텀 마커 \ No newline at end of file + custom_name: 테스트 설명을 위한 커스텀 마커 + From 1c157192f00e445798007fb8938646707ec1be87 Mon Sep 17 00:00:00 2001 From: SW Baek Date: Thu, 13 Feb 2025 22:33:47 +0900 Subject: [PATCH 20/32] [#0] add enum to `check-no-external-libs.yml` --- .github/workflows/check-no-external-libs.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-no-external-libs.yml b/.github/workflows/check-no-external-libs.yml index 009111d..5ea2eb1 100644 --- a/.github/workflows/check-no-external-libs.yml +++ b/.github/workflows/check-no-external-libs.yml @@ -20,7 +20,9 @@ jobs: import os import ast - allowed_modules = {'sys', 'os', 'math', 'random', 'datetime', 're', 'enum'} + + allowed_modules = {'sys', 'os', 'math', 'random', 'datetime', 're' 'enum'} + 3b3027c ([#0] add enum to `check-no-external-libs.yml`) def check_imports(file_path): with open(file_path, 'r', encoding='utf-8') as f: @@ -37,4 +39,4 @@ jobs: for root, _, files in os.walk('src'): for file in files: if file.endswith('.py'): - check_imports(os.path.join(root, file))" \ No newline at end of file + check_imports(os.path.join(root, file))" From 557bfd2218cb6526c5c5311f9e35a3dab3bfed24 Mon Sep 17 00:00:00 2001 From: SW Baek Date: Thu, 13 Feb 2025 22:38:28 +0900 Subject: [PATCH 21/32] [#0] make `check-function-length.yml` to work only for function length --- .github/workflows/check-function-length.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/check-function-length.yml b/.github/workflows/check-function-length.yml index 5d06939..75ec6ea 100644 --- a/.github/workflows/check-function-length.yml +++ b/.github/workflows/check-function-length.yml @@ -18,6 +18,9 @@ jobs: echo "max-locals=10" >> .pylintrc echo "max-args=5" >> .pylintrc echo "max-statements=15" >> .pylintrc + echo "[MESSAGES CONTROL]" >> .pylintrc + echo "disable=all" >> .pylintrc + echo "enable=design" >> .pylintrc - name: Run pylint run: pylint --rcfile=.pylintrc src/ From 651f1c82e3940e9f3264381881807c382d17628e Mon Sep 17 00:00:00 2001 From: SW Baek Date: Thu, 13 Feb 2025 23:08:26 +0900 Subject: [PATCH 22/32] [#0] enhance `check-commit-convention.yml` to avoid `[#0]` commit From 9431b48f0029aea7930b1dba6c461e7714ecc305 Mon Sep 17 00:00:00 2001 From: SW Baek Date: Thu, 13 Feb 2025 23:22:04 +0900 Subject: [PATCH 23/32] [#0] add lost comma in `check-no-external-libs.yml` --- .github/workflows/check-no-external-libs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/check-no-external-libs.yml b/.github/workflows/check-no-external-libs.yml index 5ea2eb1..e24c96a 100644 --- a/.github/workflows/check-no-external-libs.yml +++ b/.github/workflows/check-no-external-libs.yml @@ -22,7 +22,6 @@ jobs: allowed_modules = {'sys', 'os', 'math', 'random', 'datetime', 're' 'enum'} - 3b3027c ([#0] add enum to `check-no-external-libs.yml`) def check_imports(file_path): with open(file_path, 'r', encoding='utf-8') as f: From 68e121a4f3706a0278ec1f55e145ac3f7e911d15 Mon Sep 17 00:00:00 2001 From: SW Baek Date: Thu, 13 Feb 2025 23:27:32 +0900 Subject: [PATCH 24/32] [#0] enhance `__init__.py` to pass PEP8 --- src/lotto/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lotto/__init__.py b/src/lotto/__init__.py index 5042919..68d4591 100644 --- a/src/lotto/__init__.py +++ b/src/lotto/__init__.py @@ -1,4 +1,6 @@ from .lotto import Lotto -__all__ = ["Lotto"] \ No newline at end of file + +__all__ = ["Lotto"] + From 1c2ba7f722a997d71002912d3aab84a49bf9dfa8 Mon Sep 17 00:00:00 2001 From: SW Baek Date: Thu, 13 Feb 2025 23:45:22 +0900 Subject: [PATCH 25/32] [#0] enhance `check-no-external-libs.yml` to recognize internal library --- .github/workflows/check-no-external-libs.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-no-external-libs.yml b/.github/workflows/check-no-external-libs.yml index e24c96a..3a8e85e 100644 --- a/.github/workflows/check-no-external-libs.yml +++ b/.github/workflows/check-no-external-libs.yml @@ -23,16 +23,22 @@ jobs: allowed_modules = {'sys', 'os', 'math', 'random', 'datetime', 're' 'enum'} + def is_internal_module(module_name): + \"\"\" 내부 모듈(`src/` 폴더 내 Python 파일)인지 확인 \"\"\" + module_path = os.path.join('src', module_name.replace('.', '/') + '.py') + module_dir = os.path.join('src', module_name.replace('.', '/')) + return os.path.exists(module_path) or os.path.isdir(module_dir) + def check_imports(file_path): with open(file_path, 'r', encoding='utf-8') as f: tree = ast.parse(f.read(), filename=file_path) for node in ast.walk(tree): if isinstance(node, ast.Import): for alias in node.names: - if alias.name not in allowed_modules: + if alias.name not in allowed_modules and not is_internal_module(alias.name): raise SystemExit(f'❌ External library detected: {alias.name} in {file_path}') elif isinstance(node, ast.ImportFrom): - if node.module and node.module not in allowed_modules: + if node.module and node.module not in allowed_modules and not is_internal_module(node.module): raise SystemExit(f'❌ External library detected: {node.module} in {file_path}') for root, _, files in os.walk('src'): From 04a77d022b9d3d26e5ff44793d3188f55ebc5128 Mon Sep 17 00:00:00 2001 From: akran Date: Sun, 16 Feb 2025 23:17:47 +0900 Subject: [PATCH 26/32] =?UTF-8?q?fix:=20PEP8=20=EA=B7=9C=EA=B2=A9=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/lotto.py | 5 ++-- src/lotto/main.py | 64 +++++++++++++++++++++++++++++++++------------- 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/src/lotto/lotto.py b/src/lotto/lotto.py index a47e8f7..4836964 100644 --- a/src/lotto/lotto.py +++ b/src/lotto/lotto.py @@ -35,7 +35,6 @@ def get_rank(cls, match_cnt, bonus): class Lotto(): """로또 번호 및 당첨 결과를 처리하는 클래스""" ERROR_MESSAGE = "[ERROR] 구입 금액이 잘못되었습니다." - def __init__(self, numbers: list[int]): self._validate(numbers) self._numbers = sorted(numbers) @@ -49,9 +48,8 @@ def _validate(self, numbers: list[int]): if not all(1 <= num <= 45 for num in numbers): raise ValueError("로또 번호는 1부터 45 사이여야 합니다.") - @classmethod - def generate_random_lotto(cls): + def generate_randomlotto(cls): """무작위 로또 번호 생성""" return cls(random.sample(range(1, 46), 6)) @@ -59,6 +57,7 @@ def generate_random_lotto(cls): def get_numbers(self): return self._numbers + def __str__(self): """str 형식으로 변환하여 반환""" return str(self._numbers) diff --git a/src/lotto/main.py b/src/lotto/main.py index 5b99e72..b89bfb8 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -1,15 +1,22 @@ from .lotto import Rank, Lotto + def check_amount(input_amount): + """로또 구입 금액 검증""" if not input_amount.isdigit(): raise ValueError("[ERROR] 숫자를 입력해 주세요.") - if int(input_amount) % 1000 != 0: + + amount = int(input_amount) + if amount % 1000 != 0: raise ValueError("구입 금액은 1,000원으로 나누어 떨어져야 합니다.") - if int(input_amount) < 1000: + if amount < 1000: raise ValueError("구입 금액은 1,000원 이상이어야 합니다.") - return int(input_amount) // 1000 + + return amount // 1000 + def prompt_purchase_amount(): + """로또 구입 금액 입력""" while True: try: print("구입금액을 입력해 주세요.") @@ -17,14 +24,17 @@ def prompt_purchase_amount(): 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) # ✅ __str__() 사용하여 출력 + print(ticket) # ✅ `__str__()` 사용하여 출력 + def prompt_winning_numbers(): + """당첨 번호 입력""" while True: print("\n당첨 번호를 입력해 주세요.") try: @@ -33,16 +43,23 @@ def prompt_winning_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: + + bonus_num = int(bonus_num) + if bonus_num in winning_numbers: raise ValueError("보너스 숫자와 입력한 당첨 번호는 중복되지 않아야 합니다.") - if int(bonus_num) > 45 or int(bonus_num) < 1: + if bonus_num > 45 or bonus_num < 1: raise ValueError("로또 번호의 숫자 범위는 1~45까지입니다.") - return int(bonus_num) + + return bonus_num + def prompt_bonus_number(winning_numbers): + """보너스 번호 입력""" while True: try: bonus_num = input("\n보너스 번호를 입력해 주세요.\n") @@ -50,14 +67,16 @@ def prompt_bonus_number(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 # ✅ 보너스 번호 `int` 변환 후 비교 + 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 @@ -65,32 +84,41 @@ def evaluate_tickets(tickets, winning_numbers, bonus_num): 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]}개") + print( + f"{rank.match_cnt}개 일치, 보너스 볼 일치 ({rank.prize:,}원) - {results[rank]}개" + ) if rank != Rank.NONE and rank != Rank.SECOND: - print(f"{rank.match_cnt}개 일치 ({rank.prize:,}원) - " - f"{results[rank]}개") + print( + f"{rank.match_cnt}개 일치 ({rank.prize:,}원) - {results[rank]}개" + ) print(f"총 수익률은 {profit_percentage}%입니다.") + def main(): + """로또 게임 실행""" amount = prompt_purchase_amount() - tickets = [Lotto.generate_random_lotto() for _ in range(amount)] # ✅ 메서드명 수정 + tickets = [Lotto.generate_random_lotto() for _ in range(amount)] print_lotto_tickets(tickets) winning_numbers = prompt_winning_numbers() - bonus_num = prompt_bonus_number(winning_numbers) # ✅ `int` 변환 적용 + bonus_num = prompt_bonus_number(winning_numbers) - results, total_prize = evaluate_tickets(tickets, winning_numbers, bonus_num) + results, total_prize = evaluate_tickets( + tickets, winning_numbers, bonus_num + ) print_results(results, total_prize, amount) + if __name__ == "__main__": main() From 7356e5426ee8ea1e045abeee3192cef05ab175a9 Mon Sep 17 00:00:00 2001 From: akran Date: Sun, 16 Feb 2025 23:22:24 +0900 Subject: [PATCH 27/32] =?UTF-8?q?fix:=20docs=20=EB=B3=B5=EA=B5=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 245 +++++++++++++++++++++++++++++++++++++++---------- docs/README.md | 46 ++++++++++ 2 files changed, 245 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 654e834..928ae25 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,199 @@ -구현 기능 목록 - -입력 처리 - 구입 금액 입력 받기 - 1,000원 단위로 입력 확인 - 잘못된 입력 처리 (ValueError 발생 및 [ERROR] 메시지 출력) - - 당첨 번호 입력 받기 - 쉼표(,)로 구분된 6개의 숫자 입력 - 숫자는 1~45 범위 내에 있어야 함 - 중복되지 않도록 검사 - 잘못된 입력 처리 (ValueError 발생 및 [ERROR] 메시지 출력) - -보너스 번호 입력 받기 - 숫자는 1~45 범위 내에 있어야 함 - 당첨 번호와 중복되지 않도록 검사 - 잘못된 입력 처리 (ValueError 발생 및 [ERROR] 메시지 출력) - - -로또 발행 - 입력된 금액에 따라 로또 발행 - 1,000원당 1개씩 생성 - 각 로또는 1~45 범위의 숫자 6개 (중복 없이) 랜덤 생성 - 생성된 로또 번호는 오름차순 정렬 - 발행된 로또 번호 출력 - - 당첨 결과 계산 - 각 로또 번호와 당첨 번호 비교 - 일치하는 번호 개수 계산 - 보너스 번호 일치 여부 확인 - - 당첨 내역 계산 - 각 당첨 등수 개수 출력 - -수익률 계산 - 총 당첨 금액 계산 - 수익률 계산 ((총 당첨 금액 / 구입 금액) * 100) - 소수점 둘째 자리에서 반올림하여 출력 - -예외 처리 - 입력값이 숫자가 아닐 경우 예외 처리 - 구입 금액이 1,000원 단위가 아닐 경우 예외 처리 - 당첨 번호가 6개가 아닐 경우 예외 처리 - 당첨 번호가 1~45 범위를 벗어날 경우 예외 처리 - 보너스 번호가 1~45 범위를 벗어날 경우 예외 처리 - 보너스 번호가 당첨 번호와 중복될 경우 예외 처리 \ No newline at end of file +# 미션 - 로또 + +> [!NOTE] +> 이 코드는 원래 [java-lotto-6](https://github.com/woowacourse-precourse/java-lotto-6)에서 제공된 **Java 기반의 로또 게임**을 **Python**에 맞게 변환한 과제입니다. 프로젝트 구조, 요구 사항, 기능 구현 방식은 원본 저장소를 바탕으로 Python 환경에 맞추어 수정하였습니다. +--- + +## 🔍 진행 방식 + +- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있습니다. +- 세 가지 요구 사항을 만족하기 위해 노력해야 하며, 특히 기능을 구현하기 전에 기능 목록을 작성하고 기능 단위로 커밋하는 방식으로 진행해야 합니다. +- 기능 요구 사항에 명시되지 않은 부분은 스스로 판단하여 구현할 수 있습니다. + +## 📮 미션 제출 방법 + +- 미션 구현을 완료한 후 GitHub을 통해 제출해야 합니다. + - **Pull Request로 최종 제출** + +## 🚨 과제 제출 전 체크 리스트 - 0점 방지 + +- 기능을 모두 정상적으로 구현했더라도 **요구 사항에 명시된 출력값 형식을 지키지 않을 경우 0점 처리**됩니다. +- 기능 구현을 완료한 뒤 아래 가이드에 따라 테스트를 실행하고 모든 테스트가 성공하는지 확인합니다. +- **테스트가 실패할 경우 0점 처리**되므로, 반드시 확인 후 제출해야 합니다. + + + +### 테스트 실행 가이드 + +- 터미널에서 `python --version`을 실행하여 Python 버전이 3.9 이상인지 확인합니다. +- 프로젝트의 의존성 패키지를 설치하기 위해 `pip install -r requirements.txt` 명령어를 실행합니다. +- 아래 명령어를 실행하여 모든 테스트가 성공하는지 확인합니다. + +```bash +pytest +``` + +테스트 실행 시 출력 예시는 다음과 같습니다. + +```bash +=========================== test session starts ============================ +platform darwin -- Python 3.9.x, pytest-6.2.5 +rootdir: /path/to/project +collected 5 items + +tests/lotto/test_main.py .... [ 80%] +tests/lotto/test_lotto.py .. [100%] + +============================ 100% passing in Xs ============================= +``` + +--- + +## 🚀 기능 요구 사항 + +로또 게임 기능을 구현해야 합니다. 로또 게임은 아래와 같은 규칙으로 진행됩니다. + +``` +- 로또 번호의 숫자 범위는 1~45까지입니다. +- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑습니다. +- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑습니다. +- 당첨은 1등부터 5등까지 있습니다. 당첨 기준과 금액은 아래와 같습니다. + - 1등: 6개 번호 일치 / 2,000,000,000원 + - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 + - 3등: 5개 번호 일치 / 1,500,000원 + - 4등: 4개 번호 일치 / 50,000원 + - 5등: 3개 번호 일치 / 5,000원 +``` + +- 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 합니다. +- 로또 1장의 가격은 1,000원입니다. +- 당첨 번호와 보너스 번호를 입력받습니다. +- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료합니다. +- 사용자가 잘못된 값을 입력할 경우 `ValueError`를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받습니다. + +### 입출력 요구 사항 + +#### 입력 + +- 로또 구입 금액을 입력받습니다. 구입 금액은 1,000원 단위로 입력받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리합니다. + +``` +14000 +``` + +- 당첨 번호를 입력받습니다. 번호는 쉼표(,)를 기준으로 구분합니다. + +``` +1,2,3,4,5,6 +``` + +- 보너스 번호를 입력받습니다. + +``` +7 +``` + +#### 출력 + +- 발행한 로또 수량 및 번호를 출력합니다. 로또 번호는 오름차순으로 정렬하여 보여줍니다. + +``` +8개를 구매했습니다. +[8, 21, 23, 41, 42, 43] +[3, 5, 11, 16, 32, 38] +[7, 11, 16, 35, 36, 44] +[1, 8, 11, 31, 41, 42] +[13, 14, 16, 38, 42, 45] +[7, 11, 30, 40, 42, 43] +[2, 13, 22, 32, 38, 45] +[1, 3, 5, 14, 22, 45] +``` + +- 당첨 내역을 출력합니다. + +``` +3개 일치 (5,000원) - 1개 +4개 일치 (50,000원) - 0개 +5개 일치 (1,500,000원) - 0개 +5개 일치, 보너스 볼 일치 (30,000,000원) - 0개 +6개 일치 (2,000,000,000원) - 0개 +``` + +- 수익률은 소수점 둘째 자리에서 반올림합니다. (ex. 100.0%, 51.5%, 1,000,000.0%) + +``` +총 수익률은 62.5%입니다. +``` + +- 예외 상황 시 에러 문구를 출력해야 합니다. 단, 에러 문구는 "[ERROR]"로 시작해야 합니다. + +``` +[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다. +``` + +#### 실행 결과 예시 + +``` +구입금액을 입력해 주세요. +8000 +8개를 구매했습니다. +[8, 21, 23, 41, 42, 43] +[3, 5, 11, 16, 32, 38] +[7, 11, 16, 35, 36, 44] +[1, 8, 11, 31, 41, 42] +[13, 14, 16, 38, 42, 45] +[7, 11, 30, 40, 42, 43] +[2, 13, 22, 32, 38, 45] +[1, 3, 5, 14, 22, 45] +당첨 번호를 입력해 주세요. +1,2,3,4,5,6 +보너스 번호를 입력해 주세요. +7 +당첨 통계 +--- +3개 일치 (5,000원) - 1개 +4개 일치 (50,000원) - 0개 +5개 일치 (1,500,000원) - 0개 +5개 일치, 보너스 볼 일치 (30,000,000원) - 0개 +6개 일치 (2,000,000,000원) - 0개 +총 수익률은 62.5%입니다. +``` + +--- + +## 🎯 프로그래밍 요구 사항 + +- Python 3.9 이상에서 실행 가능해야 합니다. **정상적으로 동작하지 않을 경우 0점 처리**됩니다. +- 프로그램 실행의 시작점은 `src/lotto/main.py`의 `main()` 함수입니다. +- 외부 라이브러리를 사용하지 않습니다. +- [Python 코드 스타일 가이드(Python PEP8)](https://peps.python.org/pep-0008/)을 준수하며 프로그래밍합니다. +- 프로그램 종료 시 `sys.exit()`를 호출하지 않습니다. +- 프로그램 구현이 완료되면 `tests/lotto/test_main.py`의 모든 테스트가 성공해야 합니다. **테스트가 실패할 경우 0점 처리**됩니다. +- 프로그래밍 요구 사항에서 별도로 명시하지 않는 한 파일과 패키지 이름을 수정하거나 이동하지 않습니다. +- 들여쓰기(인덴트) 깊이를 3을 넘지 않도록 구현합니다. 2까지만 허용합니다. + - 예를 들어, while문 안에 if문이 있으면 들여쓰기는 2입니다. + - 함수나 메서드를 분리해 들여쓰기 깊이를 줄이는 것이 좋습니다. +- 삼항 연산자를 사용하지 않습니다. +- 함수나 메서드는 한 가지 일만 하도록 작게 작성해야 합니다. +- Pytest와 Assert를 이용해 본인이 정리한 기능 목록이 정상 동작하는지 테스트 코드로 확인합니다. + +### 추가된 요구 사항 + +- 함수나 메서드의 길이는 15라인을 넘지 않도록 구현합니다. +- else 예약어를 사용하지 않습니다. + - 힌트: if 조건절에서 값을 반환하면 else를 생략할 수 있습니다. +- Python의 Enum을 적용합니다. +- 도메인 로직에 단위 테스트를 구현해야 합니다. 단, UI(System.out, input) 로직은 제외합니다. + - 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리합니다. + - src/lotto/lotto.py: 핵심 로직을 구현 + - src/lotto/main.py: UI(입출력)를 담당하는 로직 구현 + - 단위 테스트 작성이 익숙하지 않다면 `tests/lotto/test_lotto.py`를 참고하여 학습한 후 테스트를 구현합니다. + +--- + +## ✏️ 과제 진행 요구 사항 + +- 미션은 [python-lotto](https://github.com/swthewhite/python-lotto) 저장소를 Fork & Clone하여 시작합니다. +- **기능을 구현하기 전 `docs/README.md`에 구현할 기능 목록을 정리**해 추가합니다. +- **Git의 커밋 단위는 앞 단계에서 `docs/README.md`에 정리한 기능 목록 단위**로 추가합니다. + - [커밋 메시지 컨벤션](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 가이드를 참고해 커밋 메시지를 작성합니다. \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index e69de29..654e834 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,46 @@ +구현 기능 목록 + +입력 처리 + 구입 금액 입력 받기 + 1,000원 단위로 입력 확인 + 잘못된 입력 처리 (ValueError 발생 및 [ERROR] 메시지 출력) + + 당첨 번호 입력 받기 + 쉼표(,)로 구분된 6개의 숫자 입력 + 숫자는 1~45 범위 내에 있어야 함 + 중복되지 않도록 검사 + 잘못된 입력 처리 (ValueError 발생 및 [ERROR] 메시지 출력) + +보너스 번호 입력 받기 + 숫자는 1~45 범위 내에 있어야 함 + 당첨 번호와 중복되지 않도록 검사 + 잘못된 입력 처리 (ValueError 발생 및 [ERROR] 메시지 출력) + + +로또 발행 + 입력된 금액에 따라 로또 발행 + 1,000원당 1개씩 생성 + 각 로또는 1~45 범위의 숫자 6개 (중복 없이) 랜덤 생성 + 생성된 로또 번호는 오름차순 정렬 + 발행된 로또 번호 출력 + + 당첨 결과 계산 + 각 로또 번호와 당첨 번호 비교 + 일치하는 번호 개수 계산 + 보너스 번호 일치 여부 확인 + + 당첨 내역 계산 + 각 당첨 등수 개수 출력 + +수익률 계산 + 총 당첨 금액 계산 + 수익률 계산 ((총 당첨 금액 / 구입 금액) * 100) + 소수점 둘째 자리에서 반올림하여 출력 + +예외 처리 + 입력값이 숫자가 아닐 경우 예외 처리 + 구입 금액이 1,000원 단위가 아닐 경우 예외 처리 + 당첨 번호가 6개가 아닐 경우 예외 처리 + 당첨 번호가 1~45 범위를 벗어날 경우 예외 처리 + 보너스 번호가 1~45 범위를 벗어날 경우 예외 처리 + 보너스 번호가 당첨 번호와 중복될 경우 예외 처리 \ No newline at end of file From 942010580c059cf9030b5d313b9bd39226d1f2e4 Mon Sep 17 00:00:00 2001 From: akran Date: Sun, 16 Feb 2025 23:27:37 +0900 Subject: [PATCH 28/32] =?UTF-8?q?fix:=20PEP8=20=EA=B7=9C=EA=B2=A9=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/lotto.py | 73 +++++++++++++++++++++++++++++++++++++++------- src/lotto/main.py | 8 +++-- 2 files changed, 68 insertions(+), 13 deletions(-) diff --git a/src/lotto/lotto.py b/src/lotto/lotto.py index 4836964..0b3c02a 100644 --- a/src/lotto/lotto.py +++ b/src/lotto/lotto.py @@ -4,7 +4,14 @@ class Rank(Enum): """ - 로또 당첨 순위 정의하는 클래스 + 로또 당첨 순위를 정의하는 클래스. + + - FIFTH: 3개 일치 (5,000원) + - FOURTH: 4개 일치 (50,000원) + - THIRD: 5개 일치 (1,500,000원) + - SECOND: 5개 + 보너스 번호 일치 (30,000,000원) + - FIRST: 6개 일치 (2,000,000,000원) + - NONE: 0개 일치 (당첨 없음) """ FIFTH = (3, False, 5_000) FOURTH = (4, False, 50_000) @@ -15,7 +22,12 @@ class Rank(Enum): def __init__(self, match_cnt, bonus_match, prize): """ - Rank 객체 초기화 + Rank 객체 초기화. + + Args: + match_cnt (int): 일치하는 번호 개수 + bonus_match (bool): 보너스 번호 일치 여부 + prize (int): 당첨 금액 """ self.match_cnt = match_cnt self.bonus_match = bonus_match @@ -24,7 +36,14 @@ def __init__(self, match_cnt, bonus_match, prize): @classmethod def get_rank(cls, match_cnt, bonus): """ - 일치 개수와 보너스 번호 여부를 기반으로 당첨 순위 반환 + 일치 개수와 보너스 번호 여부를 기반으로 당첨 순위 반환. + + Args: + match_cnt (int): 일치하는 번호 개수 + bonus (bool): 보너스 번호 일치 여부 + + Returns: + Rank: 해당하는 당첨 순위 """ for rank in cls: if rank.match_cnt == match_cnt and rank.bonus_match == bonus: @@ -32,15 +51,35 @@ def get_rank(cls, match_cnt, bonus): return cls.NONE -class Lotto(): - """로또 번호 및 당첨 결과를 처리하는 클래스""" +class Lotto: + """ + 로또 번호 및 당첨 결과를 처리하는 클래스. + + - 1~45 사이의 서로 다른 6개의 숫자를 가짐. + - 로또 번호 검증 및 생성 기능 포함. + """ ERROR_MESSAGE = "[ERROR] 구입 금액이 잘못되었습니다." + def __init__(self, numbers: list[int]): + """ + Lotto 객체 초기화. + + Args: + numbers (list[int]): 1~45 사이의 6개 정수 리스트 + """ self._validate(numbers) self._numbers = sorted(numbers) def _validate(self, numbers: list[int]): - """로또 번호 검증: 개수, 중복, 범위""" + """ + 로또 번호 검증: 개수, 중복, 범위 확인. + + Args: + numbers (list[int]): 1~45 사이의 6개 정수 리스트 + + Raises: + ValueError: 유효하지 않은 로또 번호일 경우 예외 발생 + """ if len(numbers) != 6: raise ValueError("로또 번호는 정확히 6개여야 합니다.") if len(set(numbers)) != 6: @@ -50,14 +89,28 @@ def _validate(self, numbers: list[int]): @classmethod def generate_randomlotto(cls): - """무작위 로또 번호 생성""" - return cls(random.sample(range(1, 46), 6)) + """ + 무작위 로또 번호 생성. + Returns: + Lotto: 생성된 로또 객체 + """ + return cls(random.sample(range(1, 46), 6)) def get_numbers(self): - return self._numbers + """ + 로또 번호 반환. + Returns: + list[int]: 정렬된 로또 번호 리스트 + """ + return self._numbers def __str__(self): - """str 형식으로 변환하여 반환""" + """ + 문자열 변환. + + Returns: + str: 로또 번호 리스트를 문자열로 반환 + """ return str(self._numbers) diff --git a/src/lotto/main.py b/src/lotto/main.py index b89bfb8..f124c6f 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -94,12 +94,14 @@ def print_results(results, total_prize, amount): for rank in Rank: if rank == Rank.SECOND: print( - f"{rank.match_cnt}개 일치, 보너스 볼 일치 ({rank.prize:,}원) - {results[rank]}개" + f"{rank.match_cnt}개 일치, 보너스 볼 일치 ({rank.prize:,}원) - " + f"{results[rank]}개" ) if rank != Rank.NONE and rank != Rank.SECOND: print( - f"{rank.match_cnt}개 일치 ({rank.prize:,}원) - {results[rank]}개" + f"{rank.match_cnt}개 일치 ({rank.prize:,}원) - " + f"{results[rank]}개" ) print(f"총 수익률은 {profit_percentage}%입니다.") @@ -108,7 +110,7 @@ def print_results(results, total_prize, amount): def main(): """로또 게임 실행""" amount = prompt_purchase_amount() - tickets = [Lotto.generate_random_lotto() for _ in range(amount)] + tickets = [Lotto.generate_randomlotto() for _ in range(amount)] print_lotto_tickets(tickets) winning_numbers = prompt_winning_numbers() From 1ca86d08dbbbe57c73994618a7787a7c302de6ec Mon Sep 17 00:00:00 2001 From: akran Date: Mon, 17 Feb 2025 00:02:20 +0900 Subject: [PATCH 29/32] =?UTF-8?q?fix:=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/main.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/lotto/main.py b/src/lotto/main.py index f124c6f..33346e6 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -17,13 +17,10 @@ def check_amount(input_amount): def prompt_purchase_amount(): """로또 구입 금액 입력""" - while True: - try: - print("구입금액을 입력해 주세요.") - amount = input() - return check_amount(amount) - except ValueError as error: - print(f"[ERROR] {error}") + print("구입금액을 입력해 주세요.") + amount = input() + return check_amount(amount) + def print_lotto_tickets(tickets): @@ -123,4 +120,4 @@ def main(): if __name__ == "__main__": - main() + main() \ No newline at end of file From 7d72664a6b1870008bdc729b4cd3cad0a56b7753 Mon Sep 17 00:00:00 2001 From: akran Date: Mon, 17 Feb 2025 00:22:06 +0900 Subject: [PATCH 30/32] =?UTF-8?q?style:=20PEP8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lotto/__init__.py | 1 - src/lotto/main.py | 3 +-- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lotto/__init__.py b/src/lotto/__init__.py index 1dff551..809061d 100644 --- a/src/lotto/__init__.py +++ b/src/lotto/__init__.py @@ -1,5 +1,4 @@ from .lotto import Lotto - __all__ = ["Lotto"] diff --git a/src/lotto/main.py b/src/lotto/main.py index 33346e6..f9ca45b 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -22,7 +22,6 @@ def prompt_purchase_amount(): return check_amount(amount) - def print_lotto_tickets(tickets): """구매한 로또 번호 출력""" print(f"\n{len(tickets)}개를 구매했습니다.") @@ -120,4 +119,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main() From cb0e757011d6d137b4de73c04c9ac17cb0d6f49d Mon Sep 17 00:00:00 2001 From: akran Date: Mon, 17 Feb 2025 00:24:07 +0900 Subject: [PATCH 31/32] =?UTF-8?q?style:=20yml=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/check-no-external-libs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check-no-external-libs.yml b/.github/workflows/check-no-external-libs.yml index b5b0a0a..6ba6d6e 100644 --- a/.github/workflows/check-no-external-libs.yml +++ b/.github/workflows/check-no-external-libs.yml @@ -21,7 +21,7 @@ jobs: import ast - allowed_modules = {'sys', 'os', 'math', 'random', 'datetime', 're' 'enum'} + allowed_modules = {'sys', 'os', 'math', 'random', 'datetime', 're', 'enum'} def is_internal_module(module_name): \"\"\" 내부 모듈(`src/` 폴더 내 Python 파일)인지 확인 \"\"\" module_path = os.path.join('src', module_name.replace('.', '/') + '.py') From 063e41e8393914bbd681de8cf321f07730b385db Mon Sep 17 00:00:00 2001 From: akran Date: Mon, 17 Feb 2025 00:40:06 +0900 Subject: [PATCH 32/32] style: PEP8 --- src/lotto/__init__.py | 4 ++-- src/lotto/main.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lotto/__init__.py b/src/lotto/__init__.py index 809061d..51e868d 100644 --- a/src/lotto/__init__.py +++ b/src/lotto/__init__.py @@ -1,4 +1,4 @@ -from .lotto import Lotto +from .lotto import Lotto, Rank -__all__ = ["Lotto"] +__all__ = ["Lotto", "Rank"] diff --git a/src/lotto/main.py b/src/lotto/main.py index f9ca45b..e338a95 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -1,4 +1,4 @@ -from .lotto import Rank, Lotto +from lotto import Rank, Lotto def check_amount(input_amount):