Skip to content

lotto - step3 #4172

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

Merged
merged 6 commits into from
Apr 8, 2025
Merged
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@ build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**
!**/src/test/**
bin/

### STS ###
.apt_generated
22 changes: 22 additions & 0 deletions docs/step3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# 3단계 - 로또(2등) 기능 목록

## 데이터 모델 개선

- [x] Rank enum 별도 클래스로 구현
- 로또 등수별 일치 개수와 상금 정보 관리
- 일치 개수와 보너스 볼 여부에 따른 등수 계산 로직 구현

- [x] 일급 컬렉션 적용 - Lotto 클래스

## 기능 구현

- [x] 보너스 볼 입력 기능 구현
- [x] InputView에 보너스 볼 입력 메서드 추가
- [x] 보너스 볼 유효성 검증 (1~45 사이, 당첨 번호와 중복 불가)

- [x] 당첨 결과 계산 기능 개선
- [x] 보너스 볼 일치 여부 확인 로직 구현
- [x] LottoResult 클래스 개선하여 2등 당첨 관리

- [x] 출력 기능 개선
- [x] ResultView 수정하여 2등 당첨 결과 출력
38 changes: 23 additions & 15 deletions src/main/java/lotto/LottoService.java
Original file line number Diff line number Diff line change
@@ -1,27 +1,35 @@
package lotto;

import java.util.List;
import lotto.domain.Lotto;
import lotto.domain.LottoGenerator;
import lotto.domain.LottoNumber;
import lotto.domain.LottoResult;

import java.util.List;
import java.util.stream.Collectors;
import lotto.domain.Rank;

public class LottoService {
private final LottoGenerator lottoGenerator;
private final LottoGenerator lottoGenerator;

public LottoService() {
this.lottoGenerator = new LottoGenerator();
}
public LottoService() {
this.lottoGenerator = new LottoGenerator();
}

public List<Lotto> buyLottos(int money) {
return lottoGenerator.generate(money);
}
public List<Lotto> buyLottos(int money) {
return lottoGenerator.generate(money);
}

public LottoResult calculateResult(
List<Lotto> lottos, Lotto winningLotto, LottoNumber bonusNumber) {
LottoResult lottoResult = new LottoResult();

public LottoResult calculateResult(List<Lotto> lottos, Lotto winningLotto) {
List<LottoResult.Rank> ranks =
lottos.stream().map(lotto -> lotto.countMatchingNumbers(winningLotto))
.map(LottoResult.Rank::valueOf).collect(Collectors.toList());
return new LottoResult(ranks);
for (Lotto lotto : lottos) {
int matchCount = lotto.countMatchingNumbers(winningLotto);
boolean bonusMatch = lotto.contains(bonusNumber);

Rank rank = Rank.valueOf(matchCount, bonusMatch);
lottoResult.addRank(rank);
}

return lottoResult;
}
}
39 changes: 24 additions & 15 deletions src/main/java/lotto/Main.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,39 @@
package lotto;

import java.util.List;
import lotto.domain.Lotto;
import lotto.domain.LottoNumber;
import lotto.domain.LottoResult;
import lotto.ui.InputView;
import lotto.ui.ResultView;

import java.util.List;

public class Main {
public static void main(String[] args) {
InputView inputView = new InputView();
ResultView resultView = new ResultView();
LottoService lottoService = new LottoService();
public static void main(String[] args) {
InputView inputView = new InputView();
ResultView resultView = new ResultView();
LottoService lottoService = new LottoService();

int money = inputView.readMoney();
List<Lotto> lottos = lottoService.buyLottos(money);

resultView.printLottoCount(lottos.size());
resultView.printLottos(lottos);

int money = inputView.readMoney();
List<Lotto> lottos = lottoService.buyLottos(money);
List<Integer> winningNumbers = inputView.readWinningNumbers();
Lotto winningLotto = Lotto.of(winningNumbers);

resultView.printLottoCount(lottos.size());
resultView.printLottos(lottos);
LottoNumber bonusNumber = inputView.readBonusLottoNumber();
validateBonusNumber(bonusNumber, winningLotto);

List<Integer> winningNumbers = inputView.readWinningNumbers();
Lotto winningLotto = new Lotto(winningNumbers);
LottoResult lottoResult = lottoService.calculateResult(lottos, winningLotto, bonusNumber);

LottoResult lottoResult = lottoService.calculateResult(lottos, winningLotto);
resultView.printLottoResult(lottoResult);
resultView.printProfitRate(lottoResult.calculateProfitRate(money));
}

resultView.printLottoResult(lottoResult);
resultView.printProfitRate(lottoResult.calculateProfitRate(money));
private static void validateBonusNumber(LottoNumber bonusNumber, Lotto winningLotto) {
if (winningLotto.contains(bonusNumber)) {
throw new IllegalArgumentException("보너스 번호는 당첨 번호와 중복되면 안 됩니다.");
}
}
}
51 changes: 32 additions & 19 deletions src/main/java/lotto/domain/Lotto.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
package lotto.domain;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class Lotto {
private static final int LOTTO_SIZE = 6;
private final List<Integer> numbers;
private static final int LOTTO_SIZE = 6;
private final List<LottoNumber> numbers;

public Lotto(List<Integer> numbers) {
validate(numbers);
this.numbers = numbers;
}
public Lotto(List<LottoNumber> lottoNumbers) {
validateSize(lottoNumbers);
validateDuplicate(lottoNumbers);
this.numbers = lottoNumbers;
}

private void validate(List<Integer> numbers) {
if (numbers.size() != LOTTO_SIZE) {
throw new IllegalArgumentException("로또 번호는 6개여야 합니다.");
}
public static Lotto of(List<Integer> numbers) {

Choose a reason for hiding this comment

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

정적 팩토리 메서드의 경우 관습적으로 파라메터가 1개인 경우 from을 사용합니다. 파라메터가 여러개일 때 of를 사용하여요.

영문표현과 연관있는 관습이라고 봐주시면 좋을 것 같네요 😄

List<LottoNumber> lottoNumbers =
numbers.stream().map(LottoNumber::new).collect(Collectors.toList());
return new Lotto(lottoNumbers);
}

if (numbers.stream().distinct().count() != LOTTO_SIZE) {
throw new IllegalArgumentException("로또 번호에 중복이 있습니다.");
}
private static void validateSize(List<?> numbers) {
if (numbers.size() != LOTTO_SIZE) {
throw new IllegalArgumentException("로또 번호는 6개여야 합니다.");
}
}

public List<Integer> getNumbers() {
return Collections.unmodifiableList(numbers);
private static void validateDuplicate(List<?> numbers) {
if (numbers.stream().distinct().count() != LOTTO_SIZE) {
throw new IllegalArgumentException("로또 번호에 중복이 있습니다.");
}
}

public int countMatchingNumbers(Lotto winningLotto) {
return (int) numbers.stream().filter(winningLotto.getNumbers()::contains).count();
}
public List<Integer> getNumbers() {
return numbers.stream().map(LottoNumber::getNumber).collect(Collectors.toUnmodifiableList());
}

public int countMatchingNumbers(Lotto winningLotto) {
return (int) numbers.stream().filter(number -> winningLotto.contains(number)).count();
}

public boolean contains(LottoNumber lottoNumber) {
return numbers.contains(lottoNumber);
}
}
57 changes: 30 additions & 27 deletions src/main/java/lotto/domain/LottoGenerator.java
Original file line number Diff line number Diff line change
@@ -7,36 +7,39 @@
import java.util.stream.IntStream;

public class LottoGenerator {
private static final int LOTTO_PRICE = 1000;
private static final int MIN_LOTTO_NUMBER = 1;
private static final int MAX_LOTTO_NUMBER = 45;
private static final int LOTTO_SIZE = 6;
private static final List<Integer> NUMBERS = IntStream
.rangeClosed(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER).boxed().collect(Collectors.toList());

public List<Lotto> generate(int money) {
validateMoney(money);
int lottoCount = money / LOTTO_PRICE;

return IntStream.range(0, lottoCount).mapToObj(i -> generateRandomLotto())
.collect(Collectors.toList());
}
private static final int LOTTO_PRICE = 1000;
private static final int LOTTO_SIZE = 6;
private static final List<LottoNumber> ALL_LOTTO_NUMBERS =
IntStream.rangeClosed(LottoNumber.MIN_LOTTO_NUMBER, LottoNumber.MAX_LOTTO_NUMBER)
.mapToObj(LottoNumber::new)
.collect(Collectors.toList());

public List<Lotto> generate(int money) {
validateMoney(money);
int lottoCount = money / LOTTO_PRICE;

private void validateMoney(int money) {
if (money < LOTTO_PRICE) {
throw new IllegalArgumentException("로또 구입 금액은 최소 1000원 이상이어야 합니다.");
}
return IntStream.range(0, lottoCount)
.mapToObj(i -> generateRandomLotto())
.collect(Collectors.toList());
}

if (money % LOTTO_PRICE != 0) {
throw new IllegalArgumentException("로또 구입 금액은 1000원 단위여야 합니다.");
}
private void validateMoney(int money) {
if (money < LOTTO_PRICE) {
throw new IllegalArgumentException("로또 구입 금액은 최소 1000원 이상이어야 합니다.");
}

private Lotto generateRandomLotto() {
List<Integer> numbers = new ArrayList<>(NUMBERS);
Collections.shuffle(numbers);
List<Integer> selectedNumbers = numbers.subList(0, LOTTO_SIZE);
Collections.sort(selectedNumbers);
return new Lotto(selectedNumbers);
if (money % LOTTO_PRICE != 0) {
throw new IllegalArgumentException("로또 구입 금액은 1000원 단위여야 합니다.");
}
}

private Lotto generateRandomLotto() {
List<LottoNumber> numbers = new ArrayList<>(ALL_LOTTO_NUMBERS);
Collections.shuffle(numbers);
List<LottoNumber> selectedNumbers = numbers.subList(0, LOTTO_SIZE);

selectedNumbers.sort((ln1, ln2) -> Integer.compare(ln1.getNumber(), ln2.getNumber()));

return new Lotto(selectedNumbers);
}
}
38 changes: 38 additions & 0 deletions src/main/java/lotto/domain/LottoNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package lotto.domain;

import java.util.Objects;

public class LottoNumber {

Choose a reason for hiding this comment

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

value object 👍

public static final int MIN_LOTTO_NUMBER = 1;
public static final int MAX_LOTTO_NUMBER = 45;

private final int number;

public LottoNumber(int number) {
validate(number);
this.number = number;
}

private void validate(int number) {
if (number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException("로또 번호는 1부터 45 사이여야 합니다.");
}
}

public int getNumber() {
return number;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
LottoNumber that = (LottoNumber) o;
return number == that.number;
}

@Override
public int hashCode() {
return Objects.hash(number);
}
}
69 changes: 27 additions & 42 deletions src/main/java/lotto/domain/LottoResult.java
Original file line number Diff line number Diff line change
@@ -1,56 +1,41 @@
package lotto.domain;

import java.util.Arrays;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

public class LottoResult {
private final List<Rank> ranks;
private final Map<Rank, Integer> rankCounts;

public LottoResult(List<Rank> ranks) {
this.ranks = ranks;
}
public LottoResult() {
this.rankCounts = new HashMap<>();
initializeRankCounts();
}

public List<Rank> getRanks() {
return ranks;
private void initializeRankCounts() {
for (Rank rank : Rank.values()) {
rankCounts.put(rank, 0);
}
}

public Map<Rank, Long> getStatistics() {
return ranks.stream().collect(Collectors.groupingBy(rank -> rank, Collectors.counting()));
}
public void addRank(Rank rank) {
rankCounts.put(rank, rankCounts.get(rank) + 1);
}

public long calculateTotalPrize() {
return ranks.stream().mapToLong(Rank::getPrize).sum();
}

public double calculateProfitRate(int purchaseAmount) {
return (double) calculateTotalPrize() / purchaseAmount;
}
public int getRankCount(Rank rank) {
return rankCounts.getOrDefault(rank, 0);
}

public enum Rank {
FIRST(6, 2_000_000_000), SECOND(5, 1_500_000), THIRD(4, 50_000), FOURTH(3, 5_000), MISS(0,
0);
public Map<Rank, Integer> getRankCounts() {
return new HashMap<>(rankCounts);
}

private final int matchCount;
private final int prize;
public long calculateTotalPrize() {
return rankCounts.entrySet().stream()
.mapToLong(entry -> (long) entry.getKey().getWinningMoney() * entry.getValue())
.sum();
}

Rank(int matchCount, int prize) {
this.matchCount = matchCount;
this.prize = prize;
}

public static Rank valueOf(int matchCount) {
return Arrays.stream(values()).filter(rank -> rank.matchCount == matchCount).findFirst()
.orElse(MISS);
}

public int getMatchCount() {
return matchCount;
}

public int getPrize() {
return prize;
}
}
public double calculateProfitRate(int purchaseAmount) {
return (double) calculateTotalPrize() / purchaseAmount;
}
}
Loading