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
50 changes: 50 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# 요구사항 명세서
## [카테고리]
1. 사용자
2. 로또 발행기
3. 결과 비교기
4. 시스템

## [카테고리 별 요구사항 내용]
### 1. 사용자
- 로또 구입 금액을 입력한다.
- 당첨 번호 추첨 시 중복되지 않는 6개의 숫자와 보너스 번호를 1개를 입력한다.


### 2. 로또 발행기
- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
- 로또 구입 금액에 해당하는 만큼 로또를 발행한다.


### 3. 결과 비교기
- 당첨 번호를 입력 받아 발행한 로또와 비교한다.


### 4. 시스템
- 당첨 내역과 수익률을 출력하고 로또 게임을 종료한다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고 "[ERROR]"로 시작하는 에러 메세지를 출력 후 종료한다.

# 기능 목록
### 1. 입력 기능
- 사용자가 입력하는 금액을 입력 받는 기능
- 사용자가 입력하는 당첨 번호, 보너스 번호를 입력 받는 기능


### 2. 출력 기능
- 발행한 로또 내역 출력 기능
- 당첨 통계와 수익률 출력 기능


### 3. 게임 로직 기능
- 로또 발행 기능
- 당첨 통계 기능
- 수익률 계산 기능


### 4. 검증 기능
- 입력 검증
- 사용자가 입력한 금액이 1000원 단위로 1000의 배수인지 검증
- 사용자가 입력한 당첨 번호가 정수 1 ~ 45인지 검증
- 사용자가 입력한 당첨 번호가 숫자 6개인지 검증
- 사용자가 입력한 보너스 번호가 정수 1 ~ 45인지 검증
- 사용자가 입력한 보너스 번호가 숫자 1개인지 검증
3 changes: 3 additions & 0 deletions src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package lotto;

import lotto.controller.Controller;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
new Controller().run();
}
}
61 changes: 61 additions & 0 deletions src/main/java/lotto/controller/Controller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package lotto.controller;

import lotto.domain.Lotto.AnswerLotto;
import lotto.domain.Lotto.Lotto;
import lotto.domain.user.User;
import lotto.service.ResultService;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static camp.nextstep.edu.missionutils.Console.readLine;
import static lotto.domain.Lotto.LottoGenerator.generateLottos;
import static lotto.domain.Lotto.MoneyGenerator.generateMoney;

public class Controller {
Copy link
Collaborator

Choose a reason for hiding this comment

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

tab 컨벤션 4칸

private User user;
private ResultService resultService;

public Controller() {
this.resultService = new ResultService();
}

public void run() {
System.out.println("구입금액을 입력해 주세요.");
int money = generateMoney();

buyLotto(money);

List<Lotto> lottos = user.getLottos();
System.out.println(lottos.size() + "개를 구매했습니다.");
for (Lotto lotto : lottos) {
System.out.println(lotto);
}
Comment on lines +31 to +35
Copy link
Collaborator

Choose a reason for hiding this comment

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

발행한 로또 수량 및 번호를 출력한다. 로또 번호는 오름차순으로 정렬하여 보여준다.

  • 이러한 요구사항이 있는데 위의 코드는 이 요구사항을 지킨걸까요?


System.out.println("당첨 번호를 입력해 주세요.");
String answerNumbers = readLine();
System.out.println("보너스 번호를 입력해 주세요.");
int bonusLottoNumber = Integer.parseInt(readLine());
List<Integer> answerLottoNumbers = Stream.of(answerNumbers.split(","))
.map(Integer::valueOf).collect(Collectors.toList());
AnswerLotto answerLotto = new AnswerLotto(answerLottoNumbers, bonusLottoNumber);

Map<String, Integer> statistics = resultService.getStatistics(lottos, answerLotto.getAnswerNumbers(), answerLotto.getBonusNumber());
double returnRate = resultService.getReturnRate(money);

System.out.println("당첨 통계");
System.out.println("---");
System.out.println("3개 일치 (5,000원)" + "-" + statistics.get("THREE") + "개");
System.out.println("4개 일치 (50,000원)" + "-" + statistics.get("FOUR") + "개");
System.out.println("5개 일치 (1,500,000원)" + "-" + statistics.get("FIVE") + "개");
System.out.println("5개 일치, 보너스 볼 일치 (30,000,000원)" + "-" + statistics.get("FIVEPLUSBONUS") + "개");
System.out.println("6개 일치 (2,000,000,000원)" + "-" + statistics.get("SIX") + "개");
System.out.println(String.format("총 수익률은 %.1f입니다.", returnRate));
Comment on lines +37 to +55
Copy link
Collaborator

Choose a reason for hiding this comment

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

리팩토링 하신다고 했으니까 이 부분은 피드백 생략할게요

}

private void buyLotto(int money) {
user = new User(generateLottos(money));
}
}
50 changes: 50 additions & 0 deletions src/main/java/lotto/domain/Lotto/AnswerLotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package lotto.domain.Lotto;

import java.util.List;

public class AnswerLotto {
private List<Integer> answerNumbers;
Copy link
Collaborator

Choose a reason for hiding this comment

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

List<Integer> answerNumbers가 아닌 Lotto answerNumber로 가져오면 중복된 검증을 없앨 수 있지 않을까요?

  • 밑에 보이는 검증이 Lotto에서도 동일하게 보이네요. 차이가 있어서 이렇게 나눈건가요

private int bonusNumber;

public AnswerLotto(List<Integer> answerNumbers, Integer bonusNumber) {
validateLottoSize(answerNumbers);
validateLottoHasDuplicate(answerNumbers);
validateLottoNumbersRange(answerNumbers);
this.answerNumbers = answerNumbers;
this.bonusNumber = bonusNumber;
}
Comment on lines +9 to +15
Copy link
Collaborator

Choose a reason for hiding this comment

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

answerNumbers에 있는 숫자를 bonusNumber에서 중복해서 선택하면 어떻게 되나요?


private void validateLottoSize(List<Integer> numbers) {
if (numbers.size() != 6) {
throw new IllegalArgumentException("정답 로또 번호가 6자리가 아닙니다.");
}
}

private void validateLottoHasDuplicate(List<Integer> numbers) {
if (hasDuplicate(numbers)) {
throw new IllegalArgumentException("로또 번호에 중복된 번호가 있습니다.");
}
}

private boolean hasDuplicate(List<Integer> numbers) {
return numbers.stream().distinct().count() != 6;
}
Comment on lines +29 to +31
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
private boolean hasDuplicate(List<Integer> numbers) {
return numbers.stream().distinct().count() != 6;
}
private boolean hasDuplicateNumber(final List<Integer> baseballs) {
return baseballs.stream()
.distinct()
.count() != LOTTO_SIZE;
}
  • Dot은 1줄에 1개
  • 매직넘버 지양

다른 검증 로직도 동일한 피드백입니다


private void validateLottoNumbersRange(List<Integer> numbers) {
if (isNotInRange(numbers)) {
throw new IllegalArgumentException("로또 번호 중에 1 ~ 45가 아닌 것이 있습니다.");
}
}

private boolean isNotInRange(List<Integer> numbers) {
return numbers.stream().anyMatch(number -> number < 1 || number > 45);
}

public List<Integer> getAnswerNumbers() {
return answerNumbers;
Copy link
Collaborator

Choose a reason for hiding this comment

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

immutable

}

public int getBonusNumber() {
return bonusNumber;
}
}
49 changes: 49 additions & 0 deletions src/main/java/lotto/domain/Lotto/Lotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package lotto.domain.Lotto;

import java.util.List;

public class Lotto {
Copy link
Collaborator

Choose a reason for hiding this comment

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

AnswerLotto 피드백 동일

private final List<Integer> numbers;

public Lotto(List<Integer> numbers) {
validateLottoSize(numbers);
validateLottoHasDuplicate(numbers);
validateLottoNumbersRange(numbers);
this.numbers = numbers;
}

private void validateLottoSize(List<Integer> numbers) {
if (numbers.size() != 6) {
throw new IllegalArgumentException("로또 번호가 6자리가 아닙니다.");
}
}

private void validateLottoHasDuplicate(List<Integer> numbers) {
if (hasDuplicate(numbers)) {
throw new IllegalArgumentException("로또 번호에 중복된 번호가 있습니다.");
}
}

private boolean hasDuplicate(List<Integer> numbers) {
return numbers.stream().distinct().count() != 6;
}

private void validateLottoNumbersRange(List<Integer> numbers) {
if (isNotInRange(numbers)) {
throw new IllegalArgumentException("로또 번호 중에 1 ~ 45가 아닌 것이 있습니다.");
}
}

private boolean isNotInRange(List<Integer> numbers) {
return numbers.stream().anyMatch(number -> number < 1 || number > 45);
}

public List<Integer> getNumbers() {
return numbers;
}

@Override
public String toString() {
return numbers.toString();
}
}
30 changes: 30 additions & 0 deletions src/main/java/lotto/domain/Lotto/LottoGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package lotto.domain.Lotto;

import camp.nextstep.edu.missionutils.Randoms;

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

public class LottoGenerator {
private LottoGenerator() {
}

public static List<Lotto> generateLottos(int money) {
List<Lotto> lottos = new ArrayList<>();
final int quantity = money / 1000;

for (int i = 0; i < quantity; i++) {
lottos.add(generateLotto());
}

return lottos;
}
Comment on lines +12 to +21
Copy link
Collaborator

Choose a reason for hiding this comment

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

구매한 money를 넘기는게 아니라 1000원 단위로 구매한 로또의 개수를 넘기는게 어떤가요


private static Lotto generateLotto() {
return new Lotto(getLottoNumbers());
}

private static List<Integer> getLottoNumbers() {
return Randoms.pickUniqueNumbersInRange(1, 45, 6);
}
}
43 changes: 43 additions & 0 deletions src/main/java/lotto/domain/Lotto/MoneyGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package lotto.domain.Lotto;

import static camp.nextstep.edu.missionutils.Console.readLine;

public class MoneyGenerator {
private MoneyGenerator() {
}

public static int generateMoney() {
int money = inputMoney();
validateMoney(money);
return money;
}

private static int inputMoney() {
String inputMoney = readLine();
return convertMoneyTypeStringToInt(inputMoney);
}

private static int convertMoneyTypeStringToInt(String inputMoney) {
return Integer.parseInt(inputMoney);
}
Comment on lines +9 to +22
Copy link
Collaborator

Choose a reason for hiding this comment

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

inputMony에서 readLine() -> a123을 입력했을 때 convertMoneyTypeStringToInt에서 어떤 일이 발생하죠?

private static void validateMoney(int money) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

위에 라인과 개행

validateMoneyIsPositive(money);
validateMoneyIsMultiplesOfPrice(money);
}

private static void validateMoneyIsPositive(int money) {
if (money < 0) {
throw new IllegalArgumentException("구입금액은 음수를 입력할 수 없습니다.");
}
}

private static void validateMoneyIsMultiplesOfPrice(int money) {
if (isNotMultiplesOfPrice(money)) {
throw new IllegalArgumentException("구입금액은 1000의 배수여야 합니다.");
}
}

private static boolean isNotMultiplesOfPrice(int money) {
return (money / 1000) < 0 || (money % 1000) > 0;
}
}
17 changes: 17 additions & 0 deletions src/main/java/lotto/domain/user/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package lotto.domain.user;

import lotto.domain.Lotto.Lotto;

import java.util.List;

public class User {
private List<Lotto> lottos;

public User(List<Lotto> lottos) {
this.lottos = lottos;
}

public List<Lotto> getLottos() {
return lottos;
Copy link
Collaborator

Choose a reason for hiding this comment

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

immutable

}
}
68 changes: 68 additions & 0 deletions src/main/java/lotto/service/ResultService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package lotto.service;

import lotto.domain.Lotto.Lotto;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ResultService {
private Map<Integer, String> collectMap = new HashMap<>(){{
put(3, "THREE");
put(4, "FOUR");
put(5, "FIVE");
put(6, "SIX");
}};
private Map<String, Integer> statisticCollectCntMap = new HashMap<>(){{
put("THREE", 0);
put("FOUR", 0);
put("FIVE", 0);
put("FIVEPLUSBONUS", 0);
put("SIX", 0);
}};
Comment on lines +9 to +22
Copy link
Collaborator

Choose a reason for hiding this comment

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

  • ~~Map보다 의미있는 필드명을 사용하는게 어떨까요
  • Map이 당첨과 관련된 메타정보를 표현하는거 같은데 ResultService에서 이를 관리하는게 맞을까요?
    • Hint: Enum


public Map<String, Integer> getStatistics(List<Lotto> userLottos, List<Integer> answerNumbers, int bonusNumber) {
int bonusCnt;
int collectCnt;

for (Lotto userLotto : userLottos) {
List<Integer> eachLottoNumbers = userLotto.getNumbers();
bonusCnt = isContainsBonusNumber(eachLottoNumbers, bonusNumber);
collectCnt = getCollectCnt(eachLottoNumbers, answerNumbers);

try {
if (bonusCnt == 1 && collectCnt == 5) {
statisticCollectCntMap.compute("FIVEPLUSBONUS", (k, v) -> Integer.valueOf(v + 1));
} else {
statisticCollectCntMap.compute(collectMap.get(collectCnt), (k, v) -> Integer.valueOf(v + 1));
}
} catch (NullPointerException e) {

}
Comment on lines +33 to +41
Copy link
Collaborator

Choose a reason for hiding this comment

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

NPE를 잡는 이유가 있나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Map에 없는 키를 get하면 NPE가 발생합니다.

https://codechacha.com/ko/java-how-to-update-value-of-key-from-hashmap/

}

return statisticCollectCntMap;
}

private int isContainsBonusNumber(List<Integer> eachLottoNumbers, Integer bonusNumber) {
if (eachLottoNumbers.contains(bonusNumber)) {
return 1;
}
return 0;
}
Comment on lines +47 to +52
Copy link
Collaborator

Choose a reason for hiding this comment

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

1줄로 개선


private int getCollectCnt(List<Integer> eachLottoNumbers, List<Integer> answerNumbers) {
eachLottoNumbers.retainAll(answerNumbers);
return eachLottoNumbers.size();
}

public double getReturnRate(int money) {
return (double) 100 * (
statisticCollectCntMap.get("THREE") * 5000 +
statisticCollectCntMap.get("FOUR") * 50000 +
statisticCollectCntMap.get("FIVE") * 1500000 +
statisticCollectCntMap.get("FIVEPLUSBONUS") * 30000000 +
statisticCollectCntMap.get("SIX") * 2000000000
) / money;
}
}
Loading