Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[로또] 김채원 미션 제출합니다. #1340

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
5265546
docs: 기능 요구 사항, 예외 케이스 정리
Dim-chae Oct 31, 2024
1b9db57
feat: 로또 구입 금액 입력 및 구매 수량을 구하는 기능 구현
Dim-chae Nov 4, 2024
87c124f
feat: 당첨 번호, 보너스 번호 입력 기능 구현
Dim-chae Nov 4, 2024
0e2e679
fix: try 문의 변수 스코프 문제 해결을 위한 변수 선언 위치 변경
Dim-chae Nov 4, 2024
37d8091
feat: 구입 금액 입력 시 0 이하일 경우, 1000으로 나누어 떨어지지 않을 경우에 관한 예외 처리 기능 추가
Dim-chae Nov 4, 2024
8d42cf3
feat: 구매 수량에 따라 로또 번호를 생성하는 기능 구현
Dim-chae Nov 4, 2024
c49dc6e
feat: 구매 수량과 생성된 로또 번호를 출력하는 기능 구현
Dim-chae Nov 4, 2024
88f2083
feat: 입력 받은 당첨 번호 문자열을 쉼표 단위로 나누어 저장하는 기능 추가
Dim-chae Nov 4, 2024
c252b48
feat: 당첨 번호와 로또 번호를 대조하여 당첨 여부 판별하는 기능 구현
Dim-chae Nov 4, 2024
0597e1a
feat: 총 당첨 금액과 수익률을 계산하는 기능 구현
Dim-chae Nov 4, 2024
2442769
feat: 당첨 통계(수익률, 총 당첨 내역) 출력 기능 구현
Dim-chae Nov 4, 2024
e8a5fc3
feat: 로또 번호 난수를 정렬하여 저장하는 기능 구현
Dim-chae Nov 4, 2024
3a7d4ef
feat: 입력받은 로또 당첨 번호에 대한 유효성 검사 기능 추가
Dim-chae Nov 4, 2024
d2c1dec
test: 입력받은 로또 번호에 대한 테스트 케이스 추가
Dim-chae Nov 4, 2024
468c7cc
refactor: 객체지향 원칙에 따른 클래스 분리
Dim-chae Nov 4, 2024
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
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,49 @@
# java-lotto-precourse

## 기능 요구 사항
1. 로또 구입 금액을 **1,000원 단위**로 입력 받는다.
1-1. 구매 수량은 입력 받은 금액을 1,000으로 나눈 값이다.
2. 당첨 번호를 **쉼표** 기준으로 **6개** 입력 받고, 보너스 번호를 **1개** 입력 받는다.
> **[입력 형식]**
12,5,7,23,40,1
19
3. 구매 수량(1-1번)에 따라 로또를 발행한다.
로또 발행 시 1개 당 **1부터 45까지**의 수 중 **6개**의 수를 중복 없이 선택한다.
4. 로또의 **수량**과 그 수량만큼의 **로또 번호**를 *오름차순*으로 정렬하여 출력한다.
> **[출력 형식]**
5개를 구매했습니다.
[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]
5. 당첨 번호(2번)와 발행한 로또의 번호를 모두 대조하여 당첨 케이스 별로 **당첨 여부**를 판별한다.
5-1. 당첨 케이스는 **3개 / 4개 / 5개 / 5개 및 보너스 볼 / 6개 일치**이다.
6. **총 당첨 금액**을 **구매 수량**으로 나누고, *소수점 둘째 자리*에서 반올림하여 **수익률**을 구한다.
7. **당첨 통계**(수익률, 총 당첨 내역)를 출력한다.
> **[출력 형식]**
3개 일치 (5,000원) - 2개
4개 일치 (50,000원) - 1개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개
총 수익률은 150.0%입니다.
## 예외 케이스
사용자가 잘못된 값을 입력할 경우 IllegalArgumentException 발생
[ERROR]로 시작하는 **에러 메시지** 출력 후 해당 시점부터의 입력을 다시 받음

**[기본 예외 케이스]**
- 비어 있는 값
- 문자, 문자열 등 숫자가 아닌 값
- 0 또는 음수

**[구입 금액 입력]**
- 1,000으로 나누어 떨어지지 않는 숫자

**[당첨 번호 입력 - 공통]**
- 1~45 범위 외의 숫자
- 중복되는 숫자

**[당첨 번호 입력 - 쉼표 기준 6개]**
- 쉼표, 숫자 중 하나라도 포함되지 않음
- 쉼표 기준으로 나누었을 때 6개의 숫자가 되지 않음
16 changes: 14 additions & 2 deletions src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
try {
int money = InputProcessor.getPurchaseAmount();
LottoGame game = new LottoGame(money);
game.printLottos();

Lotto winningLotto = InputProcessor.getWinningLotto();
int bonusNumber = InputProcessor.getBonusNumber(winningLotto.getNumbers());

game.checkWinningLottos(winningLotto, bonusNumber);
game.printWinningStatistics();
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
}
47 changes: 47 additions & 0 deletions src/main/java/lotto/InputProcessor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package lotto;

import camp.nextstep.edu.missionutils.Console;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class InputProcessor {
public static int getPurchaseAmount() {
System.out.println("구입금액을 입력해 주세요.");
String input = Console.readLine();
int money = parseInteger(input);
InputValidator.validatePurchaseAmount(money);
return money;
}

public static Lotto getWinningLotto() {
System.out.println("\n당첨 번호를 입력해 주세요.");
String input = Console.readLine();
List<Integer> numbers = parseIntegerList(input);
InputValidator.validateWinningNumbers(numbers);
return new Lotto(numbers);
}

public static int getBonusNumber(List<Integer> winningNumbers) {
System.out.println("\n보너스 번호를 입력해 주세요.");
String input = Console.readLine();
int bonusNumber = parseInteger(input);
InputValidator.validateBonusNumber(bonusNumber, winningNumbers);
return bonusNumber;
}

private static int parseInteger(String input) {
try {
return Integer.parseInt(input);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("[ERROR] 올바른 숫자를 입력해주세요.");
}
}

private static List<Integer> parseIntegerList(String input) {
return Arrays.stream(input.split(","))
.map(String::trim)
.map(InputProcessor::parseInteger)
.collect(Collectors.toList());
}
}
60 changes: 60 additions & 0 deletions src/main/java/lotto/InputValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package lotto;

import java.util.List;

public class InputValidator {
private static final int LOTTO_PRICE = 1000;
private static final int LOTTO_NUMBER_COUNT = 6;
private static final int MIN_LOTTO_NUMBER = 1;
private static final int MAX_LOTTO_NUMBER = 45;

public static void validatePurchaseAmount(int money) {
if (money <= 0) {
throw new IllegalArgumentException("[ERROR] 구입금액은 양수여야 합니다.");
}
if (money % LOTTO_PRICE != 0) {
throw new IllegalArgumentException("[ERROR] 구입금액은 " + LOTTO_PRICE + "원 단위여야 합니다.");
}
}

public static void validateWinningNumbers(List<Integer> numbers) {
validateNumberCount(numbers);
validateNoDuplicates(numbers);
validateNumberRange(numbers);
}

public static void validateBonusNumber(int bonusNumber, List<Integer> winningNumbers) {
validateNumberRange(bonusNumber);
if (winningNumbers.contains(bonusNumber)) {
throw new IllegalArgumentException("[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다.");
}
}

private static void validateNumberCount(List<Integer> numbers) {
if (numbers.size() != LOTTO_NUMBER_COUNT) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 " + LOTTO_NUMBER_COUNT + "개여야 합니다.");
}
}

private static void validateNoDuplicates(List<Integer> numbers) {
if (numbers.stream().distinct().count() != LOTTO_NUMBER_COUNT) {
throw new IllegalArgumentException("[ERROR] 로또 번호들 중 중복된 숫자가 있습니다.");
}
}

private static void validateNumberRange(List<Integer> numbers) {
if (numbers.stream().anyMatch(n -> !isValidLottoNumber(n))) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 " + MIN_LOTTO_NUMBER + "부터 " + MAX_LOTTO_NUMBER + " 사이의 숫자여야 합니다.");
}
}

private static void validateNumberRange(int number) {
if (!isValidLottoNumber(number)) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 " + MIN_LOTTO_NUMBER + "부터 " + MAX_LOTTO_NUMBER + " 사이의 숫자여야 합니다.");
}
}

private static boolean isValidLottoNumber(int number) {
return number >= MIN_LOTTO_NUMBER && number <= MAX_LOTTO_NUMBER;
}
}
17 changes: 14 additions & 3 deletions src/main/java/lotto/Lotto.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
package lotto;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Lotto {
private final List<Integer> numbers;

public Lotto(List<Integer> numbers) {
validate(numbers);
this.numbers = numbers;
this.numbers = new ArrayList<>(numbers);
Collections.sort(this.numbers);
}

private void validate(List<Integer> numbers) {
if (numbers.size() != 6) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 6개여야 합니다.");
}
if (numbers.stream().distinct().count() != 6) {
throw new IllegalArgumentException("[ERROR] 로또 번호들 중 중복된 숫자가 있습니다.");
}
if (numbers.stream().anyMatch(n -> n < 1 || n > 45)) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
}
}

// TODO: 추가 기능 구현
}
public List<Integer> getNumbers() {
return new ArrayList<>(numbers);
}
}
44 changes: 44 additions & 0 deletions src/main/java/lotto/LottoGame.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package lotto;

import java.util.ArrayList;
import java.util.List;

public class LottoGame {
private final List<Lotto> lottos;
private final WinningResult winningResult;
private final int purchaseAmount;

public LottoGame(int money) {
this.purchaseAmount = money;
this.lottos = new ArrayList<>();
this.winningResult = new WinningResult();
buyLottos(money);
}

private void buyLottos(int money) {
int lottoCount = money / 1000;
for (int i = 0; i < lottoCount; i++) {
lottos.add(LottoGenerator.generate());
}
}

public void printLottos() {
System.out.println(lottos.size() + "개를 구매했습니다.");
for (Lotto lotto : lottos) {
System.out.println(lotto.getNumbers());
}
}

public void checkWinningLottos(Lotto winningLotto, int bonusNumber) {
for (Lotto lotto : lottos) {
LottoRank rank = LottoRank.getRank(lotto, winningLotto, bonusNumber);
winningResult.addWinningRank(rank);
}
}

public void printWinningStatistics() {
winningResult.printStatistics();
double returnRate = winningResult.calculateReturnRate(purchaseAmount);
System.out.printf("총 수익률은 %.1f%%입니다.\n", returnRate);
}
}
11 changes: 11 additions & 0 deletions src/main/java/lotto/LottoGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package lotto;

import camp.nextstep.edu.missionutils.Randoms;
import java.util.List;

public class LottoGenerator {
public static Lotto generate() {
List<Integer> numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6);
return new Lotto(numbers);
}
}
41 changes: 41 additions & 0 deletions src/main/java/lotto/LottoRank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package lotto;

public enum LottoRank {
FIRST(6, 2_000_000_000, "6개 일치 (2,000,000,000원)"),
SECOND(5, 30_000_000, "5개 일치, 보너스 볼 일치 (30,000,000원)"),
THIRD(5, 1_500_000, "5개 일치 (1,500,000원)"),
FOURTH(4, 50_000, "4개 일치 (50,000원)"),
FIFTH(3, 5_000, "3개 일치 (5,000원)"),
NONE(0, 0, "");

private final int matchCount;
private final int prize;
private final String description;

LottoRank(int matchCount, int prize, String description) {
this.matchCount = matchCount;
this.prize = prize;
this.description = description;
}

public static LottoRank getRank(Lotto lotto, Lotto winningLotto, int bonusNumber) {
int matches = (int) lotto.getNumbers().stream()
.filter(winningLotto.getNumbers()::contains)
.count();

if (matches == 6) return FIRST;
if (matches == 5 && lotto.getNumbers().contains(bonusNumber)) return SECOND;
if (matches == 5) return THIRD;
if (matches == 4) return FOURTH;
if (matches == 3) return FIFTH;
return NONE;
}

public int getPrize() {
return prize;
}

public String getDescription() {
return description;
}
}
37 changes: 37 additions & 0 deletions src/main/java/lotto/WinningResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package lotto;

import java.util.EnumMap;
import java.util.Map;

public class WinningResult {
private final Map<LottoRank, Integer> rankCounts;

public WinningResult() {
this.rankCounts = new EnumMap<>(LottoRank.class);
for (LottoRank rank : LottoRank.values()) {
rankCounts.put(rank, 0);
}
}

public void addWinningRank(LottoRank rank) {
rankCounts.put(rank, rankCounts.get(rank) + 1);
}

public void printStatistics() {
System.out.println("\n당첨 통계");
System.out.println("---");
for (LottoRank rank : LottoRank.values()) {
if (rank != LottoRank.NONE) {
System.out.printf("%s - %d개\n", rank.getDescription(), rankCounts.get(rank));
}
}
}

public double calculateReturnRate(int purchaseAmount) {
long totalPrize = 0;
for (LottoRank rank : LottoRank.values()) {
totalPrize += (long) rankCounts.get(rank) * rank.getPrize();
}
return (double) totalPrize * 100 / purchaseAmount;
}
}
30 changes: 28 additions & 2 deletions src/test/java/lotto/LottoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,31 @@ class LottoTest {
.isInstanceOf(IllegalArgumentException.class);
}

// TODO: 추가 기능 구현에 따른 테스트 코드 작성
}
@DisplayName("로또 번호에 빈 문자열이 있으면 예외가 발생한다.")
@Test
void 로또_번호에_빈_문자열이_있으면_예외가_발생한다() {
assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5, null)))
.isInstanceOf(IllegalArgumentException.class);
}

@DisplayName("로또 번호가 범위를 넘으면 예외가 발생한다.")
@Test
void 로또_번호가_범위를_넘으면_예외가_발생한다() {
assertThatThrownBy(() -> new Lotto(List.of(47, 2, 3, 4, 5, 6)))
.isInstanceOf(IllegalArgumentException.class);
}

@DisplayName("로또 번호가 0이면 예외가 발생한다.")
@Test
void 로또_번호가_0이면_예외가_발생한다() {
assertThatThrownBy(() -> new Lotto(List.of(0, 2, 3, 4, 5, 6)))
.isInstanceOf(IllegalArgumentException.class);
}

@DisplayName("로또 번호에 음수가 있으면 예외가 발생한다.")
@Test
void 로또_번호에_음수가_있으면_예외가_발생한다() {
assertThatThrownBy(() -> new Lotto(List.of(-1, 2, 3, 4, 5, 6)))
.isInstanceOf(IllegalArgumentException.class);
}
}