Skip to content
Open
Show file tree
Hide file tree
Changes from 14 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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
- 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발급한다.
- 로또 1장의 가격은 1,000원이다.
- 로또 번호는 1~45 사이의 숫자 6개로 구성된다.
- 수동으로 구매할 로또 수를 입력받는다.
- 수동으로 구매할 로또 번호를 입력받는다.
- 당첨 번호 6개를 입력받는다.
- 보너스 번호 1개를 입력받는다.
- 발행한 로또와 당첨 번호를 비교하여 당첨 통계를 계산한다.
Expand All @@ -51,14 +53,16 @@

### 로또 기계

- 구입 금액을 기준으로 발행할 로또 장수를 계산한다.
- 자동 발행 로또 수를 계산한다.
- 각 로또와 당첨 번호를 비교하여 당첨 통계를 계산한다.
- 당첨 금액 및 수익률을 계산한다.

### 입력

- 구입 금액을 입력받는다.
- 1,000원 단위의 양수
- 수동으로 구매할 로또 수를 입력받는다.
- 수동으로 구매할 번호를 입력받는다.
- 당첨 번호 6개와 보너스 번호 1개를 입력받는다.
- 1~45 사이의 숫자, 중복 불가

Expand Down
19 changes: 19 additions & 0 deletions src/main/java/lotto/InputRetry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package lotto;

import java.util.function.Supplier;

public class InputRetry {

private InputRetry() {
}

public static <T> T retry(Supplier<T> action) {
while (true) {
try {
return action.get();
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
}
23 changes: 15 additions & 8 deletions src/main/java/lotto/Lotto.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public class Lotto {
public static final int LOTTO_NUMBER_COUNT = 6;

private static final String ERROR_INVALID_COUNT = "로또 번호는 6개여야 한다";
private static final String ERROR_DUPLICATE_NUMBER = "로또 번호에 중복이 있을 수 없습니다.";
private static final String ERROR_INVALID_FORMAT = "숫자 형식이 올바르지 않습니다: ";

private final Set<LottoNumber> numbers;

Expand All @@ -34,6 +36,14 @@ public static Lotto fromIntegers(List<Integer> numbers) {
return new Lotto(toLottoNumbers(numbers));
}

public List<LottoNumber> numbers() {
return Collections.unmodifiableList(new ArrayList<>(numbers));
}

public boolean contains(LottoNumber number) {
return numbers.contains(number);
}

private static List<LottoNumber> toLottoNumbers(List<Integer> numbers) {
return numbers.stream()
.map(LottoNumber::of)
Expand All @@ -53,22 +63,19 @@ private static int parseIntOrThrow(String s) {
try {
return Integer.parseInt(s.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("숫자 형식이 올바르지 않습니다: " + s);
throw new IllegalArgumentException(ERROR_INVALID_FORMAT + s);
}
}

private static void validateNumbersCount(List<LottoNumber> numbers) {
if (numbers.size() != LOTTO_NUMBER_COUNT) {
throw new IllegalArgumentException(ERROR_INVALID_COUNT);
}
}

public List<LottoNumber> numbers() {
return Collections.unmodifiableList(new ArrayList<>(numbers));
}

public boolean contains(LottoNumber number) {
return numbers.contains(number);
long distinctCount = numbers.stream().distinct().count();
if (distinctCount != LOTTO_NUMBER_COUNT) {
throw new IllegalArgumentException(ERROR_DUPLICATE_NUMBER);
}
}

@Override
Expand Down
42 changes: 34 additions & 8 deletions src/main/java/lotto/LottoApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,45 @@ public class LottoApplication {

public static void main(String[] args) {

int input = InputView.readPurchaseAmount();
PurchaseAmount amount = new PurchaseAmount(input);
PurchaseAmount amount = createPurchaseAmount();

Lottos lottos = LottoMachine.randomLottos(amount.ticketCount());
ResultView.printPurchasedLottos(lottos);
ManualLottoCount manualCount = createManualLottoCount(amount);
Lottos manualLottos = createManualLottos(manualCount);
Lottos autoLottos = LottoMachine.randomLottos(amount.autoCount(manualCount));

Lotto winningNumbers = new Lotto(InputView.readWinningNumbers());
LottoNumber bonusNumber = LottoNumber.of(InputView.readBonusNumber());
Lottos purchased = manualLottos.merge(autoLottos);
ResultView.printPurchasedLottos(purchased, manualCount);

LottoMatchResult matchResult = lottos.matchResult(
new WinningNumbers(winningNumbers, bonusNumber));
WinningNumbers winningNumbers = createWinningNumbers();
LottoMatchResult matchResult = purchased.matchResult(winningNumbers);
ProfitRate profitRate = new ProfitRate(matchResult.totalPrize(), amount);

ResultView.printLottoResult(matchResult, profitRate);
}

private static PurchaseAmount createPurchaseAmount() {
return InputRetry.retry(() ->
new PurchaseAmount(InputView.readPurchaseAmount())
);
}

private static ManualLottoCount createManualLottoCount(PurchaseAmount amount) {
return InputRetry.retry(() ->
new ManualLottoCount(InputView.readManualLottoCount(), amount)
);
}

private static Lottos createManualLottos(ManualLottoCount manualCount) {
return InputRetry.retry(() ->
Lottos.manualLottos(InputView.readManualLottos(manualCount.count()))
);
}

private static WinningNumbers createWinningNumbers() {
return InputRetry.retry(() -> {
Lotto winning = new Lotto(InputView.readWinningNumbers());
LottoNumber bonus = LottoNumber.of(InputView.readBonusNumber());
return new WinningNumbers(winning, bonus);
});
}
}
12 changes: 5 additions & 7 deletions src/main/java/lotto/LottoNumber.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package lotto;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class LottoNumber implements Comparable<LottoNumber> {
Expand All @@ -9,11 +11,11 @@ public class LottoNumber implements Comparable<LottoNumber> {

private static final String ERROR_OUT_OF_RANGE = "로또 번호는 1~45 범위의 숫자여야 한다";

private static final LottoNumber[] CACHE = new LottoNumber[MAX_NUMBER + 1];
private static final Map<Integer, LottoNumber> CACHE = new HashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

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

👍


static {
for (int i = MIN_NUMBER; i <= MAX_NUMBER; i++) {
CACHE[i] = new LottoNumber(i);
CACHE.put(i, new LottoNumber(i));
}
}

Expand All @@ -26,11 +28,7 @@ private LottoNumber(int number) {

public static LottoNumber of(int number) {
validateRange(number);
return CACHE[number];
}

public static LottoNumber of(String number) {
return of(Integer.parseInt(number));
return CACHE.get(number);
}

public int number() {
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/lotto/LottoRank.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,11 @@ public static LottoRank of(int matchCount, boolean hasBonus) {
.orElse(MISS);
}

public boolean isMiss() {
return this == MISS;
}

public boolean isSecond() {
return this == SECOND;
}
}
10 changes: 10 additions & 0 deletions src/main/java/lotto/Lottos.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ public Lottos(List<Lotto> lottos) {
this.lottos = lottos;
}

public static Lottos manualLottos(List<String> manualNumbers) {
return new Lottos(manualNumbers.stream().map(Lotto::new).toList());
}

public Lottos merge(Lottos other) {
List<Lotto> mergedLottos = new ArrayList<>(this.lottos);
mergedLottos.addAll(other.lottos);
return new Lottos(mergedLottos);
}

public int count() {
return lottos.size();
}
Expand Down
35 changes: 35 additions & 0 deletions src/main/java/lotto/ManualLottoCount.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package lotto;

public class ManualLottoCount {

private static final String ERROR_NEGATIVE_COUNT = "수동 로또 수량은 0 이상이어야 합니다.";
private static final String ERROR_EXCEED_PURCHASE = "수동 장수는 구입 가능한 수량을 초과할 수 없습니다.";
private final int count;

public ManualLottoCount(int count, int purchaseAmount) {
this(count, new PurchaseAmount(purchaseAmount));
}

public ManualLottoCount(int count, PurchaseAmount amount) {
validateNonNegativeCount(count);
validateNotExceedPurchaseAmount(count, amount);
this.count = count;
}

public int count() {
return count;
}

private static void validateNonNegativeCount(int count) {
if (count < 0) {
throw new IllegalArgumentException(ERROR_NEGATIVE_COUNT);
}
}

private static void validateNotExceedPurchaseAmount(int count, PurchaseAmount amount) {
if (amount.ticketCount() < count) {
throw new IllegalArgumentException(ERROR_EXCEED_PURCHASE);
}
}

}
5 changes: 4 additions & 1 deletion src/main/java/lotto/PurchaseAmount.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ public int ticketCount() {
return value / PRICE_PER_LOTTO;
}

public int autoCount(ManualLottoCount manualCount) {
return ticketCount() - manualCount.count();
}

private static void validate(int amount) {
if (amount < PRICE_PER_LOTTO || amount % PRICE_PER_LOTTO != 0) {
throw new IllegalArgumentException(ERROR_INVALID_PURCHASE_AMOUNT);
}
}

}
43 changes: 34 additions & 9 deletions src/main/java/lotto/view/InputView.java
Original file line number Diff line number Diff line change
@@ -1,32 +1,57 @@
package lotto.view;

import java.util.ArrayList;
import java.util.InputMismatchException;
import java.util.List;
import java.util.Scanner;
import lotto.InputRetry;

public class InputView {

private static final Scanner SCANNER = new Scanner(System.in);

public static int readPurchaseAmount() {
System.out.println("구입금액을 입력해 주세요.");
return parseIntOrThrow(SCANNER.nextLine());
return readInt();
}

public static int readManualLottoCount() {
System.out.println("수동으로 구매할 로또 수를 입력해 주세요.");
return readInt();
}

public static List<String> readManualLottos(int count) {
System.out.println("수동으로 구매할 번호를 입력해 주세요.");
List<String> input = new ArrayList<>();
while (count-- > 0) {
input.add(SCANNER.nextLine().trim());
}
System.out.println();
return input;
}

public static String readWinningNumbers() {
System.out.println("지난 주 당첨 번호를 입력해 주세요.");
return SCANNER.nextLine();
}

public static String readBonusNumber() {
public static int readBonusNumber() {
System.out.println("보너스 볼을 입력해 주세요.");
return SCANNER.nextLine();
return readInt();
}

private static int parseIntOrThrow(String input) {
try {
return Integer.parseInt(input.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("숫자만 입력 가능합니다: " + input);
}
private static int readInt() {
return InputRetry.retry(() -> {
try {
int value = SCANNER.nextInt();
SCANNER.nextLine();
System.out.println();
return value;
} catch (InputMismatchException e) {
SCANNER.nextLine();
throw new IllegalArgumentException("숫자를 올바르게 입력하세요.");
}
});
}

}
11 changes: 6 additions & 5 deletions src/main/java/lotto/view/ResultView.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,21 @@
import lotto.LottoMatchResult;
import lotto.LottoRank;
import lotto.Lottos;
import lotto.ManualLottoCount;
import lotto.ProfitRate;

public class ResultView {

public static void printPurchasedLottos(Lottos lottos) {
System.out.printf("%d개를 구매했습니다.%n", lottos.count());
public static void printPurchasedLottos(Lottos lottos, ManualLottoCount manualCount) {
System.out.printf("수동으로 %d장, 자동으로 %d개를 구매했습니다.%n", manualCount.count(),
lottos.count() - manualCount.count());
for (String line : lottos.toDisplayStrings()) {
System.out.println(line);
}
System.out.println();
}

public static void printLottoResult(LottoMatchResult matchResult, ProfitRate profitRate) {
System.out.println();
System.out.println("당첨 통계");
System.out.println("---------");

Expand All @@ -35,12 +36,12 @@ public static void printLottoResult(LottoMatchResult matchResult, ProfitRate pro
}

private static void printRank(LottoMatchResult matchResult, LottoRank rank) {
if (rank == LottoRank.MISS) {
if (rank.isMiss()) {
return;
}
int count = matchResult.countMatches(rank);
int prize = rank.prize();
if (rank == LottoRank.SECOND) {
if (rank.isSecond()) {
System.out.printf("5개 일치, 보너스 볼 일치 (%d원) - %d개%n", prize, count);
return;
}
Expand Down
7 changes: 7 additions & 0 deletions src/test/java/lotto/LottoTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ void sortLottoNumbers() {
assertThat(new Lotto("1,2,3,4,8,5")).isEqualTo(new Lotto("1,2,3,4,5,8"));
}

@DisplayName("로또 번호는 중복 시 예외가 발생한다")
@Test
void duplicateLottoNumbers() {
assertThatIllegalArgumentException().isThrownBy(() -> new Lotto("1,2,3,4,8,8"))
.withMessageContaining("중복");
}

static Stream<List<Integer>> invalidLottoSizes() {
return Stream.of(
List.of(),
Expand Down
Loading