-
Notifications
You must be signed in to change notification settings - Fork 107
[그리디] 김민욱 로또 미션 1, 2 단계 제출합니다. #153
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
Changes from all commits
adf5bbd
48c77a2
474aadd
b5eb6ff
381d67b
d4c2f13
15b6263
d5fff4a
b0d1d45
84eb0a5
c5de9ef
a5a6c94
4200424
96c0a6d
1a69d0a
41c7019
e37570e
bf79e60
ab9294d
b2c9729
71766eb
3b833ed
1cf15ec
0df50c8
6e69fda
1dc050d
35e644e
99e7044
b469635
1bc2243
1bc0835
4429df3
33c6110
978b639
5e9291d
167c8b0
f22c39d
9c7c8f2
a41be4e
e97bb51
ff87d7b
bdccdfe
06e66e3
9b880d8
5db58c1
f1dbc56
40664db
9182842
b0e11d8
d50e676
cdf6530
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| # 래플 로또 (Lotto) | ||
|
|
||
| 구입 금액을 입력받아 로또를 자동 발행하고, 당첨 번호와 비교하여 당첨 내역 및 수익률을 계산하는 콘솔 기반 애플리케이션입니다. | ||
|
|
||
| ## 🚀 기능 구현 순서 및 명세 | ||
|
|
||
| **[1] 구입 금액을 입력 받는다.** | ||
| * `InputView`를 통해 사용자 입력을 받는다. | ||
| * 입력 문자열 검증: 숫자 외의 문자가 포함되어 있으면 `IllegalArgumentException`을 발생시킨다. | ||
| * 도메인 검증: 0 이하의 값이거나 1,000원으로 나누어 떨어지지 않으면 예외를 발생시킨다. | ||
|
|
||
| **[2] 구입 금액으로 시도 횟수(로또 수량) 만들기** | ||
| * `TrialNumber` 도메인 객체를 생성하여 수량을 산출한다. | ||
| * 계산 로직: 구입 금액 / 1,000 | ||
|
|
||
| **[3] 시도 횟수만큼 랜덤 로또 생성하기** | ||
| * `LottoNumberGenerator` (전략 패턴)를 통해 1~45 사이의 중복되지 않는 난수 6개를 추출한다. | ||
| * 생성된 번호들은 오름차순으로 정렬된다. | ||
| * `Lotto` 객체 생성 시 번호가 6개가 아니거나 중복이 존재하면 `IllegalArgumentException`을 발생시켜 무결성을 보장한다. | ||
| * 시도 횟수만큼 생성된 `Lotto` 객체들을 일급 컬렉션인 `LottoNumber`로 래핑하여 관리한다. | ||
|
|
||
| **[4] 생성된 로또 번호 출력하기** | ||
| * `OutputView`를 통해 발행된 로또의 총 수량과 각 로또의 번호 배열을 콘솔에 출력한다. | ||
|
|
||
| **[5] 지난주 당첨 번호 입력 받기** | ||
| * `InputView`를 통해 당첨 번호를 입력받는다. | ||
| * 입력 문자열 검증: 숫자, 쉼표(,), 공백 외의 문자가 포함되어 있으면 예외를 발생시킨다. | ||
| * 쉼표를 기준으로 문자열을 분리하여 `List<Integer>` 형태로 파싱한다. | ||
|
|
||
| **[6] 당첨 번호와 로또 번호 비교해서 결과 탐색하기** | ||
| * `CalculateLottoNumber` 도메인에서 발행된 로또 리스트와 당첨 번호를 대조한다. | ||
| * 일치하는 숫자의 개수를 산출하고, 이를 `Rank` Enum 객체(당첨 기준 및 상금 캡슐화)와 매핑하여 상태를 저장한다. | ||
|
|
||
| **[7] 결과(통계 및 수익률) 출력하기** | ||
| * `OutputView`를 통해 3개~6개 일치에 대한 각 등수별 당첨 횟수를 출력한다. | ||
| * 총 수익률을 계산하여 출력한다. | ||
| * 수익률 계산식: 총 상금 / 구입 금액 | ||
| * 수익률은 소수점 셋째 자리에서 내림(버림) 처리하여 소수점 둘째 자리까지 표기한다. (예: 0.35) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import controller.Controller; | ||
|
|
||
| public class Application { | ||
| public static void main(String[] args) { | ||
| Controller controller = new Controller(); | ||
| controller.run(); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package controller; | ||
|
|
||
| import domain.*; | ||
| import view.InputView; | ||
| import view.OutputView; | ||
|
|
||
| import java.util.List; | ||
| import java.util.function.Supplier; | ||
|
|
||
| public class Controller { | ||
| public void run() { | ||
| TrialNumber trialNumber = getTrialNumber(); | ||
| LottoTickets lottoTickets = issueLottoTickets(trialNumber); | ||
| Lotto winningLotto = getWinningLotto(); | ||
| calculateAndPrintResults(trialNumber, lottoTickets, winningLotto); | ||
| } | ||
|
|
||
| private TrialNumber getTrialNumber() { | ||
| return retry(() -> { | ||
| OutputView.printInputPurchaseAmount(); | ||
| int amount = InputView.inputPurchaseMoney(); | ||
| return new TrialNumber(amount); | ||
| }); | ||
| } | ||
|
|
||
| private LottoTickets issueLottoTickets(TrialNumber trialNumber) { | ||
| int trialCount = trialNumber.getTrialNumber(); | ||
| LottoMachine lottoMachine = new LottoMachine(new RandomLottoNumberGenerator()); | ||
| List<Lotto> generatedLottos = lottoMachine.issue(trialCount); | ||
| LottoTickets lottoTickets = new LottoTickets(generatedLottos); | ||
| OutputView.printLottoNumber(lottoTickets, trialCount); | ||
| return lottoTickets; | ||
| } | ||
|
|
||
| private Lotto getWinningLotto() { | ||
| return retry(() -> { | ||
| OutputView.printInputWinningNumber(); | ||
| List<Integer> inputWinningNumbers = InputView.inputWinningNumber(); | ||
| return new Lotto(inputWinningNumbers); | ||
| }); | ||
| } | ||
|
|
||
| private void calculateAndPrintResults(TrialNumber trialNumber, LottoTickets lottoTickets, Lotto winningLotto) { | ||
| LottoResult statisticsResult = new LottoResult(lottoTickets, winningLotto); | ||
| int purchaseAmount = trialNumber.getPurchaseAmount(); | ||
| OutputView.printWinningStatistics(statisticsResult, purchaseAmount); | ||
| } | ||
|
|
||
| private <T> T retry(Supplier<T> supplier) { | ||
| try { | ||
| return supplier.get(); | ||
| } catch (IllegalArgumentException e) { | ||
| OutputView.printErrorMessage(e.getMessage()); | ||
| return retry(supplier); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| package domain; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
|
|
||
| public class Lotto { | ||
| private static final int MIN_NUMBER = 1; | ||
| private static final int MAX_NUMBER = 45; | ||
| private static final int LOTTO_SIZE = 6; | ||
|
|
||
| private final List<Integer> lottoNumber; | ||
|
|
||
| public Lotto(List<Integer> lottoNumber) { | ||
| validate(lottoNumber); | ||
| validateRange(lottoNumber); | ||
| this.lottoNumber = lottoNumber; | ||
| } | ||
|
|
||
| private void validate(List<Integer> lottoNumber) { | ||
| if (lottoNumber.size() != LOTTO_SIZE) { | ||
| throw new IllegalArgumentException("[ERROR] 로또 번호는 6개여야 합니다."); | ||
| } | ||
| if (new HashSet<>(lottoNumber).size() != LOTTO_SIZE) { | ||
| throw new IllegalArgumentException("[ERROR] 로또 번호에 중복된 숫자가 있습니다."); | ||
| } | ||
| } | ||
|
Comment on lines
+20
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 번호 개수와 중복 여부만 검증하고 번호 범위(1 ~ 45)는 검증하지 않고 있어요. 만약 그렇게 수정한다면,
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용자기 입력한 winningNumber를 객체화를 한다면 값이 검증이 안 되기 때문에 해당 validate가 필요하다고 생각합니다! 리팩토링 하겠습니다! |
||
|
|
||
|
|
||
| private void validateRange(List<Integer> lottoNumber) { | ||
| if (lottoNumber.stream().anyMatch(this::isOutOfRange)) { | ||
| throw new IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| private boolean isOutOfRange(int number) { | ||
| return number < MIN_NUMBER || number > MAX_NUMBER; | ||
| } | ||
|
|
||
| public List<Integer> getNumbers() { | ||
| return Collections.unmodifiableList(lottoNumber); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package domain; | ||
|
|
||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
| import java.util.stream.IntStream; | ||
|
|
||
| public class LottoMachine { | ||
| private final LottoNumberGenerator generator; | ||
|
|
||
| public LottoMachine(LottoNumberGenerator generator) { | ||
| this.generator = generator; | ||
| } | ||
|
|
||
| public List<Lotto> issue(int trialCount) { | ||
| return IntStream.range(0, trialCount) | ||
| .mapToObj(i -> new Lotto(generator.generate())) | ||
| .collect(Collectors.toList()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package domain; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public interface LottoNumberGenerator { | ||
| List<Integer> generate(); | ||
| } |
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 지금 클래스명
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 클래스명을 동사로 하는 것보다는 명사로 하는군요! |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| package domain; | ||
|
|
||
| import java.util.EnumMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| public class LottoResult { | ||
| private final Map<Rank, Integer> matchResults; | ||
|
|
||
| public LottoResult(LottoTickets lottoTickets, Lotto winningLotto) { | ||
| this.matchResults = new EnumMap<>(Rank.class); | ||
| initResults(); | ||
| calculate(lottoTickets.getLottoNumber(), winningLotto.getNumbers()); | ||
| } | ||
|
|
||
| private void initResults() { | ||
| for (Rank rank : Rank.values()) { | ||
| matchResults.put(rank, 0); | ||
| } | ||
| } | ||
|
|
||
| private void calculate(List<Lotto> lottoNumber, List<Integer> winningNumbers) { | ||
| for (Lotto lotto : lottoNumber) { | ||
| int matchCount = countMatch(lotto.getNumbers(), winningNumbers); | ||
| Rank rank = Rank.valueOfMatchCount(matchCount); | ||
| matchResults.put(rank, matchResults.get(rank) + 1); | ||
| } | ||
| } | ||
|
|
||
| private int countMatch(List<Integer> lottoNumber, List<Integer> winningNumbers) { | ||
| return (int) lottoNumber.stream() | ||
| .filter(winningNumbers::contains) | ||
| .count(); | ||
| } | ||
|
|
||
| public double calculateProfitRate(int purchaseAmount) { | ||
| long totalPrize = 0; | ||
| for (Map.Entry<Rank, Integer> entry : matchResults.entrySet()) { | ||
| totalPrize += (long) entry.getKey().getPrizeMoney() * entry.getValue(); | ||
| } | ||
| double rawProfitRate = (double) totalPrize / purchaseAmount; | ||
| return Math.floor(rawProfitRate * 100) / 100.0; | ||
| } | ||
|
|
||
| public int getRankCount(Rank rank) { | ||
| return matchResults.get(rank); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package domain; | ||
|
|
||
| import java.util.Collections; | ||
| import java.util.List; | ||
|
|
||
| public class LottoTickets { | ||
| private final List<Lotto> lottoTickets; | ||
|
|
||
| public LottoTickets(List<Lotto> lottoTickets) { | ||
| this.lottoTickets = lottoTickets; | ||
| } | ||
|
|
||
| public List<Lotto> getLottoNumber() { | ||
| return Collections.unmodifiableList(lottoTickets); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package domain; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
|
|
||
| public class RandomLottoNumberGenerator implements LottoNumberGenerator { | ||
|
|
||
|
|
||
| private static final int MIN_LOTTO_NUMBER = 1; | ||
| private static final int MAX_LOTTO_NUMBER = 45; | ||
| private static final int LOTTO_NUMBER_COUNT = 6; | ||
|
|
||
| private final List<Integer> lottoPool; | ||
|
|
||
| public RandomLottoNumberGenerator() { | ||
| this.lottoPool = new ArrayList<>(); | ||
| for (int i = MIN_LOTTO_NUMBER; i <= MAX_LOTTO_NUMBER; i++) { | ||
| lottoPool.add(i); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public List<Integer> generate() { | ||
| Collections.shuffle(lottoPool); | ||
| List<Integer> selectedNumbers = new ArrayList<>(lottoPool.subList(0, LOTTO_NUMBER_COUNT)); | ||
| Collections.sort(selectedNumbers); | ||
| return selectedNumbers; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package domain; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| public enum Rank { | ||
| NONE(0, 0), | ||
| THREE(3, 5000), | ||
| FOUR(4, 50000), | ||
| FIVE(5, 1500000), | ||
| SIX(6, 2000000000); | ||
|
|
||
| private final int matchCount; | ||
| private final int prizeMoney; | ||
|
|
||
| Rank(int matchCount, int prizeMoney) { | ||
| this.matchCount = matchCount; | ||
| this.prizeMoney = prizeMoney; | ||
| } | ||
|
|
||
| public static Rank valueOfMatchCount(int matchCount) { | ||
| return Arrays.stream(values()) | ||
| .filter(rank -> rank.matchCount == matchCount) | ||
| .findFirst() | ||
| .orElse(NONE); | ||
| } | ||
|
|
||
| public static List<Rank> getWinningRanks() { | ||
| return Arrays.stream(values()) | ||
| .filter(rank -> rank != NONE) | ||
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| public int getPrizeMoney() { | ||
| return prizeMoney; | ||
| } | ||
|
|
||
| public int getMatchCount() { | ||
| return matchCount; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,33 @@ | ||||||||||||||||||||||||||
| package domain; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| public class TrialNumber { | ||||||||||||||||||||||||||
| private static final int LOTTO_PRICE = 1000; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| private final int trialCount; | ||||||||||||||||||||||||||
| private final int purchaseAmount; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| public TrialNumber(int purchaseAmount) { | ||||||||||||||||||||||||||
| validateAmount(purchaseAmount); | ||||||||||||||||||||||||||
| this.purchaseAmount = purchaseAmount; | ||||||||||||||||||||||||||
| this.trialCount = purchaseAmount / LOTTO_PRICE; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| private void validateAmount(int amount) { | ||||||||||||||||||||||||||
| if (amount <= 0) { | ||||||||||||||||||||||||||
| throw new IllegalArgumentException("[ERROR] 구입 금액은 0보다 커야 합니다."); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| if (amount % LOTTO_PRICE != 0) { | ||||||||||||||||||||||||||
| throw new IllegalArgumentException("[ERROR] 구입 금액은 1000원 단위여야 합니다."); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| public int getTrialNumber() { | ||||||||||||||||||||||||||
| return trialCount; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| public int getPurchaseAmount() { | ||||||||||||||||||||||||||
| return purchaseAmount; | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
|
Comment on lines
+25
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
요거 개행 추가해주세요! ++ 이 파일 말고도 다른 파일들도 포맷팅이 필요한 부분이 좀 보이는데, 전체적으로 한번 수행해주시면 좋을 것 같아요~
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 넵 ! |
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package view; | ||
|
|
||
| import java.util.Arrays; | ||
| import java.util.List; | ||
| import java.util.Scanner; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| public class InputView { | ||
| private static final Scanner SCANNER = new Scanner(System.in); | ||
|
|
||
| public static int inputPurchaseMoney() { | ||
| String input = SCANNER.nextLine().trim(); | ||
| validatePurchaseMoneyFormat(input); // 파싱 전 String 상태로 형식 검증 | ||
| return Integer.parseInt(input); | ||
| } | ||
|
|
||
| public static List<Integer> inputWinningNumber() { | ||
| String winningNumber = SCANNER.nextLine(); | ||
| validateWinningNumberFormat(winningNumber); // 파싱 전 String 상태로 형식 검증 | ||
| return parse(winningNumber); | ||
| } | ||
|
|
||
| private static List<Integer> parse(String input) { | ||
| return Arrays.stream(input.split(",")) | ||
| .map(String::trim) | ||
| .map(Integer::parseInt) | ||
| .collect(Collectors.toList()); | ||
| } | ||
|
|
||
| private static void validatePurchaseMoneyFormat(String input) { | ||
| if (!input.matches("^[0-9]+$")) { | ||
| throw new IllegalArgumentException("[ERROR] 구입 금액은 숫자만 입력해야 합니다."); | ||
| } | ||
| } | ||
|
|
||
| private static void validateWinningNumberFormat(String input) { | ||
| // 숫자, 쉼표, 공백 외의 문자가 포함되어 있는지 정규표현식으로 검사 | ||
| if (!input.matches("^[0-9,\\s]+$")) { | ||
| throw new IllegalArgumentException("[ERROR] 당첨 번호는 숫자, 공백, 쉼표(,)만 포함할 수 있습니다."); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
run()메소드가 살짝 길어보이는 감이 있는데요, 각 단계를 각각의 메소드로 분리해보면 전체 흐름이 한눈에 보이도록 정리할 수 있을 것 같아요!There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
좋은 생각입니다!