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

step4 #3258

Open
wants to merge 3 commits into
base: joooahn
Choose a base branch
from
Open

step4 #3258

Show file tree
Hide file tree
Changes from 2 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
12 changes: 7 additions & 5 deletions src/main/java/lotto/domain/BonusBall.java
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package lotto.domain;

import lotto.exception.TicketNumberOutOfBoundException;

public class BonusBall {
private final Integer bonusBall;
private final LottoNo bonusBall;

private BonusBall(Integer bonusBall) {
private BonusBall(LottoNo bonusBall) {
this.bonusBall = bonusBall;
}

public static BonusBall from(String bonusBall) {
return new BonusBall(Integer.valueOf(bonusBall));
public static BonusBall from(String bonusBall) throws TicketNumberOutOfBoundException {
return new BonusBall(LottoNo.from(Integer.valueOf(bonusBall)));
}

public Integer getBonusBall() {
public LottoNo getBonusBall() {
return bonusBall;
}
}
11 changes: 7 additions & 4 deletions src/main/java/lotto/domain/LottoGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@

public class LottoGenerator {

public static LottoTickets generateTickets(long money) {
public static LottoTickets generateAutoTickets(long numberOfTicket) {
List<Ticket> tickets = new ArrayList<>();
long numberOfTickets = money / LOTTO_PRICE;
long numberOfTickets = numberOfTicket;

for (int i = 0; i < numberOfTickets; i++) {
List<Integer> candidateNumbers = makeCandidateNumbers();
Expand All @@ -24,10 +24,13 @@ public static LottoTickets generateTickets(long money) {
return LottoTickets.from(tickets);
}

public static LottoTickets generateManualTickets(List<Ticket> tickets) {
return LottoTickets.from(tickets);
}

private static List<Integer> makeCandidateNumbers() {
List<Integer> candidateNumbers = IntStream.rangeClosed(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER)
return IntStream.rangeClosed(MIN_LOTTO_NUMBER, MAX_LOTTO_NUMBER)
.boxed()
.collect(Collectors.toList());
return candidateNumbers;
}
}
32 changes: 32 additions & 0 deletions src/main/java/lotto/domain/LottoNo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package lotto.domain;

import lotto.exception.TicketNumberOutOfBoundException;

public class LottoNo {
int number;

private LottoNo(int number) throws TicketNumberOutOfBoundException {
if (checkValid(number)) {
this.number = number;
}
}

public static LottoNo from(int number) throws TicketNumberOutOfBoundException {
return new LottoNo(number);
}

private boolean checkValid(int number) throws TicketNumberOutOfBoundException {
if (number < 0 || number > 45) {
throw new TicketNumberOutOfBoundException("1 ~ 45 범위만 입력 가능합니다.");
}
return true;
}

public int number() {
return this.number;
}

public boolean equals(LottoNo lottoNo) {
return this.number == lottoNo.number();
}
}
79 changes: 55 additions & 24 deletions src/main/java/lotto/domain/LottoTickets.java
Original file line number Diff line number Diff line change
@@ -1,61 +1,84 @@
package lotto.domain;

import lotto.exception.TicketNumberOutOfBoundException;
import lotto.exception.TicketPriceOutOfBoundException;

import java.util.*;
import java.util.stream.Collectors;

import static lotto.domain.Ticket.LOTTO_PRICE;

public class LottoTickets {
Copy link

Choose a reason for hiding this comment

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

LottoTickets 의 책임이 점점 많아지고 있어요.
다른 객체로 책임을 분산 할 수 없나 고민해볼까요? 🤔

private final List<Ticket> tickets;
private final Integer numberOfManualTickets;
private final Integer numberOfAutoTickets;

private LottoTickets(List<Ticket> tickets) {
private LottoTickets(List<Ticket> tickets, int numberOfManualTickets, int numberOfAutoTickets) {
this.tickets = tickets;
this.numberOfManualTickets = numberOfManualTickets;
this.numberOfAutoTickets = numberOfAutoTickets;
}

public static LottoTickets from(List<Ticket> tickets) {
return new LottoTickets(tickets);
return new LottoTickets(tickets, 0, 0);
}

public static LottoTickets of(LottoTickets manualTickets, LottoTickets autoTickets) {
return new LottoTickets(manualTickets.merge(autoTickets), manualTickets.numberOfTickets(), autoTickets.numberOfTickets());
}

private List<Ticket> merge(LottoTickets tickets) {
List<Ticket> mergedTickets = new ArrayList<>();
mergedTickets.addAll(this.tickets);
mergedTickets.addAll(tickets.getTickets());
return mergedTickets;
}

public int numberOfTickets() {
return tickets.size();
}

public int totalPrice() {
return tickets.size() * 1000;
return tickets.size() * LOTTO_PRICE;
}

public static LottoTickets buyTickets(long money, List<Ticket> manualTickets) throws TicketNumberOutOfBoundException, TicketPriceOutOfBoundException {
if (money < 0) {
throw new TicketPriceOutOfBoundException("가격은 음수가 불가능합니다.");
}
int numberOfManualTicket = manualTickets.size();
int numberOfAutoTicket = (int) (money / LOTTO_PRICE) - numberOfManualTicket;
return LottoTickets.of(buyManualTickets(manualTickets), buyAutoTickets(numberOfAutoTicket));
}

public boolean checkValidTickets() {
return tickets.stream().allMatch(Ticket::checkValidTickets);
public static LottoTickets buyManualTickets(List<Ticket> tickets) {
return LottoGenerator.generateManualTickets(tickets);
}

public static LottoTickets buyTickets(long money) {
return LottoGenerator.generateTickets(money);
public static LottoTickets buyAutoTickets(long numberOfTicket) throws TicketNumberOutOfBoundException {
if (numberOfTicket < 0) {
throw new TicketNumberOutOfBoundException("티켓 개수는 음수가 불가능합니다.");
}
return LottoGenerator.generateAutoTickets(numberOfTicket);
}

public List<Ticket> getTickets() {
return tickets;
}

public WinningStatus winningStatus(WinningNumber winningNumber) {
List<PrizeType> prizeTypes = listOfPrize(winningNumber);
return makeWinningStatus(prizeTypes);
List<Prize> prizeTypesOfTickets = listOfPrize(winningNumber);
return makeWinningStatus(prizeTypesOfTickets);
}

public List<PrizeType> listOfPrize(WinningNumber winningNumber) {
public List<Prize> listOfPrize(WinningNumber winningNumber) {
return tickets.stream()
.map(ticket -> ticket.checkLotteryWinningStatus(winningNumber))
.collect(Collectors.toList());
}

private WinningStatus makeWinningStatus(List<PrizeType> prizeTypes) {
Map<PrizeType, Integer> winningStatus = new HashMap<>();
Arrays.stream(PrizeType.values())
.forEach(prizeType -> winningStatus.put(prizeType, countPrizeType(prizeTypes, prizeType)));
return WinningStatus.from(winningStatus);
}

public int countPrizeType(List<PrizeType> prizeTypes, PrizeType prizeType) {
return (int) prizeTypes.stream()
.filter(t -> t == prizeType)
.count();
private WinningStatus makeWinningStatus(List<Prize> prizeTypesOfTickets) {
return WinningStatus.from(prizeTypesOfTickets);
}

public double returnRate(WinningNumber winningNumber) {
Copy link

Choose a reason for hiding this comment

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

지금 listOfPrize를 두번 만들고 있는데요, WinningStatus 만들때 totalPrice 만 같이 넘겨주면
WinningStatus 안에 있는 정보들로 returnRate도 만들어 줄 수 있을 것 같아요.

Copy link
Author

Choose a reason for hiding this comment

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

winningStatus(당첨 통계), returnRate(수익를)을 구할 땐 LottoTickets에게 WinningNumber만 주고 계산하도록 구현했어요
의미상 LottoTickets에게 당첨 번호를 주고 결과를 물어보는게 맞다고 생각해서입니다.

listOfPrice를 2번 사용한다면 listOfPrice가 수정될 경우 2군데 수정해야 한다는 단점이 있지만,
WinningStatus의 정보들로 returnRate를 만드려면 다음과 같이 파라미터를 통해 WinningStatus를 넘겨줘야 해 lottoTickets와 WinningStatus간의 새로운 의존관계가 생겨서 좋지 않다고 생각하는데 어떻게 생각하시나요?
lottoTickets.returnRate(winningStatus)

Copy link

Choose a reason for hiding this comment

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

returnRate 메소드에서 winningNumberList<Prize> types = listOfPrize(winningNumber); 를 구할때 사용 하고 있는것으로 보이는데요. WinningStatus 를 만들때 동일하게 listOfPrize(winningNumber); 의 결과를 이용 하는것으로 보여요.
그래서 WinningStatus의 필드인 winningStatus를 이용해서 충분히 같은 값을 구할 수 있다고 생각했습니다.

아래 LottoTickets 의 returnRate 메소드는

public double returnRate(WinningNumber winningNumber) {
        double result = (double) sumOfPrize(winningNumber) / totalPrice();
        return Math.floor(result * 100) / 100;
    }

    private long sumOfPrize(WinningNumber winningNumber) {
        List<Prize> types = listOfPrize(winningNumber);
        return types.stream()
                .mapToLong(Prize::prize)
                .sum();
    }

WinningStatus 에서는 아래 로직으로 대체 할 수 있을 것 같습니다.

public double returnRate() {
        double result = (double) sumOfPrize() / price;
        return Math.floor(result * 100) / 100;
    }

    private long sumOfPrize() {
        return winningStatus.entrySet().stream()
                .mapToLong(it -> it.getKey().prize() * it.getValue())
                .sum();
    }

Expand All @@ -65,10 +88,18 @@ public double returnRate(WinningNumber winningNumber) {

private long sumOfPrize(WinningNumber winningNumber) {
long sum = 0;
List<PrizeType> types = listOfPrize(winningNumber);
for (PrizeType type : types) {
List<Prize> types = listOfPrize(winningNumber);
for (Prize type : types) {
sum += type.prize();
}
return sum;
}
}

public int getNumberOfManualTickets() {
return numberOfManualTickets;
}

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

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

public enum Prize {
FIRST_PRIZE(6, false, 2_000_000_000),

SECOND_PRIZE(5, true, 300_000_000),

THIRD_PRIZE(5, false, 1_500_000),

FOURTH_PRIZE(4, false, 50_000),

FIFTH_PRIZE(3, false, 5_000),

NOT_MATCHING(0, false, 0);

private final Integer numberOfMatching;
private final Boolean bonusMatching;
private final Long prize;

Prize(int numberOfMatching, boolean bonusMatching, long prize) {
this.numberOfMatching = numberOfMatching;
this.bonusMatching = bonusMatching;
this.prize = prize;
}

public int numberOfMatching() {
return numberOfMatching;
}

public static Prize create(int numberOfMatching, boolean bonusMatching) {
return Arrays.stream(Prize.values())
.filter(p -> p.numberOfMatching == numberOfMatching && p.bonusMatching == bonusMatching)
.findFirst()
.orElse(NOT_MATCHING);
}

public static Map<Prize, Integer> winningStatus(List<Prize> prizeTypesOfTickets) {
Map<Prize, Integer> map = new HashMap<>();
Arrays.stream(Prize.values())
.forEach(prizeType -> map.put(prizeType, countPrizeType(prizeTypesOfTickets, prizeType)));
return map;
Copy link

Choose a reason for hiding this comment

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

이렇게도 할수 있을 것 같아요 :)

Suggested change
return map;
return prizeTypesOfTickets.stream()
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

Copy link
Author

Choose a reason for hiding this comment

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

와 이 부분을 어떻게 깔끔하게 처리할 수 없어서 고민이 많았는데 이렇게 간단한 방법이 있었다니.. 감사합니다
이렇게 좋은 stream api + lambda 같은 모던 자바에 익숙해지려면 어떻게 해야 할까요?

Copy link

Choose a reason for hiding this comment

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

많이 써보는 수 밖에 없을 것같아요 😭
저는 Stream 사용할때 Collectors, Comparator 에 뭐가있는지 . 찍어서 한번씩 찾아보고 있습니다 ㅋㅋ
집계, 정렬 같은 부분은 DB 쿼리 기준으로 생각하면 왠만한 부분은 Stream에도 구현되어 있더라구요.

}

private static int countPrizeType(List<Prize> prizeTypesOfTickets, Prize prize) {
return (int) prizeTypesOfTickets.stream()
.filter(t -> t == prize)
.count();
}

public long prize() {
return prize;
}
}
42 changes: 0 additions & 42 deletions src/main/java/lotto/domain/PrizeType.java

This file was deleted.

50 changes: 40 additions & 10 deletions src/main/java/lotto/domain/Ticket.java
Original file line number Diff line number Diff line change
@@ -1,39 +1,69 @@
package lotto.domain;

import lotto.exception.TicketNumberOutOfBoundException;

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

public class Ticket {
private final List<Integer> numbers;
private final List<LottoNo> numbers;
public static final int MIN_LOTTO_NUMBER = 1;
public static final int MAX_LOTTO_NUMBER = 45;
public static final int SIZE_LOTTO_NUMBER = 6;
public static final int LOTTO_PRICE = 1000;

private Ticket(List<Integer> numbers) {
private Ticket(List<LottoNo> numbers) {
this.numbers = numbers;
}

public static Ticket from(List<Integer> numbers) {
return new Ticket(numbers);
return new Ticket(numbers.stream()
.map(LottoNo::from)
.collect(Collectors.toList()));
}

public boolean checkValidTickets() {
return numbers.stream().allMatch(num -> num >= MIN_LOTTO_NUMBER && num <= MAX_LOTTO_NUMBER);
public static Ticket fromString(String input) {
return fromLottoNoList(splitAndMakeList(input));
}

public static Ticket fromLottoNoList(List<LottoNo> numbers) {
return new Ticket(numbers);
}

public PrizeType checkLotteryWinningStatus(WinningNumber winningNumber) {
return PrizeType.create(countMatchingNumber(winningNumber), isBonusBallMatched(winningNumber));
public Prize checkLotteryWinningStatus(WinningNumber winningNumber) {
return Prize.create(countMatchingNumber(winningNumber), isBonusBallMatched(winningNumber));
}

public int countMatchingNumber(WinningNumber winningNumber) {
return winningNumber.countMatchingNumber(numbers);
return winningNumber.countMatchingNumber(Ticket.fromLottoNoList(numbers));
}

public boolean isBonusBallMatched(WinningNumber winningNumber) {
return winningNumber.isBonusBallMatched(numbers);
return winningNumber.isBonusBallMatched(Ticket.fromLottoNoList(numbers));
}

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

public boolean contains(LottoNo target) {
return numbers.stream()
.anyMatch(lottoNo -> lottoNo.equals(target));
}

private static List<LottoNo> splitAndMakeList(String input) {
Copy link

Choose a reason for hiding this comment

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

이 메소드를 실행시키는 테스트가 없어보여요!

String[] split = input.split(",");
return Arrays.stream(split)
.map(String::trim)
.map(Integer::parseInt)
.map(num -> {
try {
return LottoNo.from(num);
} catch (TicketNumberOutOfBoundException e) {
throw new RuntimeException(e);
}
})
Copy link

Choose a reason for hiding this comment

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

Lotto 에서 String 형태의 숫자를 받아서 Lotto를 생성해주면 어떨까요?
그리고, Stream 안에 try-catch 가 들어가면 가독성이 떨어지게 되는데요, 이부분은 어떻게 해결 할 수 있을까요 🤔

Copy link
Author

Choose a reason for hiding this comment

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

저도 lambda에서 try-catch가 복잡해질때가 고민이었는데 다음 문서 참고해 해결했습니다~ https://www.slipp.net/questions/572

.collect(Collectors.toList());
}
}
Loading