Skip to content

Commit 7f12b29

Browse files
authored
3단계 - 로또(2등) (#4214)
* [refactor] Lotto 멤버변수로 Set 사용하도록 변경하여 중복 제거, LottoNumber Comparable 구현체로 변경 * [refactor] Lottos 생성자 내부 로직 제거 * [feat] Lotto 보너스볼 추가 및 Prize enum 리팩토링 * [refactor] LottoResults Prize enum 을 key 로 갖는 Map으로 리팩토링 * [feat] 전체 애플리케이션에 input, output 기능 추가 반영 * [docs] 로또 2단계 기능 요구사항 README.md 작성 * [refactor] Lotto 에 Set을 받는 생성자 생성, 인스턴스 변수는 List로 변경 * [refactor] 총 상금 계산 메서드 네이밍 리팩토링 * [refactor] WinningLotto 객체 생성 후 로또 당첨 여부 확인 - 당첨 번호와 보너스볼 번호 사이의 중복 불가 유효성 검증 추가 - 로또 몇등 당첨 확인 책임 WinningLotto 에 부여 * [refactor] Lotto 의 인스턴스 변수 Set 타입으로 변경 - Set 타입으로 중복 제거에 대한 유효성 검증 * [refactor] LottoNumber 객체 재사용으로 메모리 사용량 고려 - LottoNumber 객체는 범위가 지정되어 있으므로 매번 새로 생성하지 않고 인스턴스를 재사용하도록 변경 * [refactor] LottoResultCalculator 에서 최종 결과 계산 - Lottos 에 있던 계산 로직을 LottoResultCalculator로 이동 * [refactor] LottoNumber 내부 정적 팩토리 메서드로 변경 * [refactor] PurchaseAmount 에서 좀 더 범용적인 의미의 Money로 변경 * [refactor] LottoResultCalculator 대신 Lottos 내부에서 반복하도록 변경 * [refactor] WinningLotto 부생성자 추가
1 parent b8703ce commit 7f12b29

18 files changed

+263
-194
lines changed
Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,19 @@
11
package lotto;
22

3-
import lotto.model.PurchaseAmount;
4-
import lotto.model.Lotto;
5-
import lotto.model.LottoResults;
6-
import lotto.model.Lottos;
3+
import lotto.model.*;
74
import lotto.view.InputView;
85
import lotto.view.OutputView;
96

107
public class LottoApplication {
118
public static void main(String[] args) {
12-
PurchaseAmount purchaseAmount = InputView.readBudgetInput();
13-
Lotto winningLotto = InputView.readWinningLottoInput();
9+
Money purchaseAmount = InputView.readPurchaseAmountInput();
10+
WinningLotto winningLotto = InputView.readWinningLottoInput();
1411

1512
OutputView.printPurchaseCount(purchaseAmount.countLottoTickets());
1613
Lottos lottos = purchaseAmount.buyLottos();
1714
OutputView.printBoughtLottos(lottos);
1815

19-
LottoResults result = lottos.calculateResults(winningLotto);
16+
LottoResults result = lottos.match(winningLotto);
2017
OutputView.printResults(result, purchaseAmount);
2118
}
2219
}

src/main/java/lotto/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
- 로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급한다. (로또 1장에 1,000원)
55
- 로또 번호는 1부터 45까지의 숫자 중 6개를 랜덤으로 선택한다.
66
- 당첨 번호 6개를 입력하면, 구입한 로또와 비교하여 당첨 결과를 알려준다.
7+
- 보너스 번호를 뽑아 당첨 번호 5개와 보너스 번호 1개가 일치하는 로또는 2등상을 준다.
78
- 당첨 결과는 3개, 4개, 5개, 6개 일치에 따라 각각 5,000원, 50,000원, 1,500,000원, 2,000,000,000원의 상금을 지급한다.
9+
- 2등은 30,000,000원을 지급한다.
810
- 수익률을 계산하여 출력한다. (수익률 = 총 당첨 금액 / 구입 금액)
911
- 수익률은 소수점 둘째 자리까지 표시한다.
1012
- 1 이상인 경우 이익, 1 미만인 경우 손해로 표시한다.
Lines changed: 24 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,54 @@
11
package lotto.model;
22

3-
import java.util.Arrays;
4-
import java.util.Collections;
5-
import java.util.List;
3+
import java.util.*;
64
import java.util.stream.Collectors;
75
import java.util.stream.IntStream;
86

97
public class Lotto {
10-
private final List<LottoNumber> numbers;
8+
private final static int LOTTO_NUMBER_SIZE = 6;
9+
private final static List<Integer> rangedInts = IntStream.rangeClosed(1, 45).boxed().collect(Collectors.toList());
10+
private final Set<LottoNumber> numbers;
1111

1212
public Lotto() {
1313
this(generateRandomNumbers());
1414
}
1515

1616
public Lotto(int... numbers) {
17-
this(Arrays.stream(numbers).boxed().collect(Collectors.toList()));
17+
this(Arrays.stream(numbers).mapToObj(LottoNumber::of).collect(Collectors.toSet()));
1818
}
1919

20-
public Lotto(List<Integer> numbers) {
20+
public Lotto(Set<LottoNumber> numbers) {
2121
checkValidity(numbers);
22-
Collections.sort(numbers);
23-
this.numbers = numbers.stream().map(LottoNumber::new).collect(Collectors.toList());
24-
}
25-
26-
public List<LottoNumber> value() {
27-
return Collections.unmodifiableList(this.numbers);
22+
this.numbers = numbers;
2823
}
2924

3025
public int countMatchNumbers(Lotto lotto) {
31-
int matchCount = 0;
32-
for (LottoNumber number : this.numbers) {
33-
matchCount += addMatchCount(number, lotto);
34-
}
35-
return matchCount;
26+
return Math.toIntExact(this.numbers.stream().filter(lotto::contains).count());
3627
}
3728

38-
private void checkValidity(List<Integer> numbers) {
39-
if (numbers.size() != 6) {
40-
throw new IllegalArgumentException("로또 번호는 6개여야 합니다.");
41-
}
42-
if (hasDuplicated(numbers)) {
43-
throw new IllegalArgumentException("로또 번호는 중복될 수 없습니다.");
44-
}
29+
public boolean matchesBonusNumber(LottoNumber bonusNumber) {
30+
return contains(bonusNumber);
4531
}
4632

47-
private boolean hasDuplicated(List<Integer> numbers) {
48-
long distinctCount = numbers.stream().distinct().count();
49-
return distinctCount != numbers.size();
33+
@Override
34+
public String toString() {
35+
List<LottoNumber> lottoNumbers = new ArrayList<>(this.numbers);
36+
Collections.sort(lottoNumbers);
37+
return lottoNumbers.toString();
5038
}
5139

52-
private int addMatchCount(LottoNumber number, Lotto lotto) {
53-
if (lotto.contains(number)) {
54-
return 1;
55-
}
56-
return 0;
40+
public boolean contains(LottoNumber number) {
41+
return numbers.contains(number);
5742
}
5843

59-
private boolean contains(LottoNumber number) {
60-
return numbers.contains(number);
44+
private void checkValidity(Set<LottoNumber> numbers) {
45+
if (numbers.size() != LOTTO_NUMBER_SIZE) {
46+
throw new IllegalArgumentException("로또 번호는 6개여야 합니다.");
47+
}
6148
}
6249

63-
private static List<Integer> generateRandomNumbers() {
64-
List<Integer> array = IntStream.rangeClosed(1, 45).boxed().collect(Collectors.toList());
65-
Collections.shuffle(array);
66-
return array.subList(0, 6);
50+
private static Set<LottoNumber> generateRandomNumbers() {
51+
Collections.shuffle(rangedInts);
52+
return rangedInts.subList(0, LOTTO_NUMBER_SIZE).stream().map(LottoNumber::of).collect(Collectors.toSet());
6753
}
6854
}

src/main/java/lotto/model/LottoNumber.java

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,22 @@
11
package lotto.model;
22

3+
import java.util.Map;
34
import java.util.Objects;
5+
import java.util.concurrent.ConcurrentHashMap;
6+
import java.util.stream.IntStream;
47

5-
public class LottoNumber {
6-
private final int number;
8+
public class LottoNumber implements Comparable<LottoNumber> {
9+
private final static Map<Integer, LottoNumber> CACHE;
710

8-
public LottoNumber(int number) {
9-
if (!isValid(number)) {
10-
throw new IllegalArgumentException("로또 번호는 1부터 45 사이의 숫자여야 합니다.");
11-
}
12-
this.number = number;
11+
static {
12+
CACHE = new ConcurrentHashMap<>();
13+
IntStream.rangeClosed(1, 45).forEach(i -> CACHE.put(i, new LottoNumber(i)));
1314
}
1415

15-
private boolean isValid(int number) {
16-
return number >= 1 && number <= 45;
16+
private final int number;
17+
18+
private LottoNumber(int number) {
19+
this.number = number;
1720
}
1821

1922
public int getNumber() {
@@ -36,4 +39,24 @@ public int hashCode() {
3639
public String toString() {
3740
return String.valueOf(number);
3841
}
42+
43+
@Override
44+
public int compareTo(LottoNumber another) {
45+
return Integer.compare(this.number, another.number);
46+
}
47+
48+
private static boolean isValid(int number) {
49+
return CACHE.containsKey(number);
50+
}
51+
52+
public static LottoNumber of(int number) {
53+
if (!isValid(number)) {
54+
throw new IllegalArgumentException("로또 번호는 1부터 45 사이의 숫자여야 합니다.");
55+
}
56+
return CACHE.get(number);
57+
}
58+
59+
public static LottoNumber of(String input) {
60+
return of(Integer.parseInt(input));
61+
}
3962
}
Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,56 @@
11
package lotto.model;
22

3-
import java.util.Arrays;
43
import java.util.HashMap;
54
import java.util.Map;
65
import java.util.Objects;
76

87
public class LottoResults {
9-
private final Map<Integer, Integer> matchCounts;
8+
private final Map<Prize, Integer> prizeCounts;
109

1110
public LottoResults() {
1211
this(new HashMap<>());
1312
}
1413

15-
public LottoResults(Map<Integer, Integer> matchCounts) {
16-
this.matchCounts = matchCounts;
14+
public LottoResults(Map<Prize, Integer> prizeCounts) {
15+
this.prizeCounts = prizeCounts;
1716
}
1817

19-
public void updateMatchCount(int matchCount) {
20-
if (matchCount < 3) {
21-
return;
22-
}
23-
matchCounts.put(matchCount, matchCounts.getOrDefault(matchCount, 0) + 1);
18+
public void update(Prize prize) {
19+
prizeCounts.put(prize, prizeCounts.getOrDefault(prize, 0) + 1);
20+
}
21+
22+
public Money getTotalPrizeValue() {
23+
return new Money(prizeCounts.entrySet().stream()
24+
.mapToLong(entry -> (long) entry.getValue() * entry.getKey().value())
25+
.sum());
2426
}
2527

26-
public long getPrizeValue() {
27-
return matchCounts.entrySet().stream()
28-
.mapToLong(entry -> (long) entry.getValue() * Prize.fromMatchCount(entry.getKey()).value())
29-
.sum();
28+
public double getReturnRate(Money purchaseAmount) {
29+
Money totalPrize = getTotalPrizeValue();
30+
return calculateReturnRate(purchaseAmount, totalPrize);
3031
}
3132

32-
public double getReturnRate(PurchaseAmount purchaseAmount) {
33-
long totalPrize = getPrizeValue();
34-
return purchaseAmount.getReturnRate(totalPrize);
33+
private double calculateReturnRate(Money purchaseAmount, Money totalPrize) {
34+
if (purchaseAmount.isZero()) {
35+
return 0;
36+
}
37+
double rate = totalPrize.divideBy(purchaseAmount);
38+
return Math.floor(rate * 100) / 100.0;
3539
}
3640

37-
public Integer getMatchCount(int matchCount) {
38-
return matchCounts.getOrDefault(matchCount, 0);
41+
public Integer getPrizeCount(Prize prize) {
42+
return prizeCounts.getOrDefault(prize, 0);
3943
}
4044

4145
@Override
4246
public boolean equals(Object o) {
4347
if (o == null || getClass() != o.getClass()) return false;
4448
LottoResults that = (LottoResults) o;
45-
return Objects.equals(matchCounts, that.matchCounts);
49+
return Objects.equals(prizeCounts, that.prizeCounts);
4650
}
4751

4852
@Override
4953
public int hashCode() {
50-
return Objects.hashCode(matchCounts);
51-
}
52-
53-
@Override
54-
public String toString() {
55-
return "LottoResults{" +
56-
"matchCounts=" + matchCounts +
57-
'}';
54+
return Objects.hashCode(prizeCounts);
5855
}
5956
}
Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,41 @@
11
package lotto.model;
22

33
import java.util.ArrayList;
4-
import java.util.Arrays;
54
import java.util.List;
65

76
public class Lottos {
87
private final List<Lotto> lottos;
98

109
public Lottos(int count) {
11-
this(new ArrayList<>());
12-
for (int i = 0; i < count; i++) {
13-
lottos.add(new Lotto());
14-
}
10+
this(createLottos(count));
1511
}
1612

1713
public Lottos(List<Lotto> lottos) {
1814
this.lottos = lottos;
1915
}
2016

21-
public LottoResults calculateResults(Lotto winningLotto) {
22-
LottoResults results = new LottoResults();
23-
24-
for (Lotto lotto : lottos) {
25-
int matchCount = lotto.countMatchNumbers(winningLotto);
26-
results.updateMatchCount(matchCount);
27-
}
28-
29-
return results;
30-
}
31-
32-
private void addMatchCount(int matchCount, List<Integer> results) {
33-
if (matchCount >= 3 && matchCount <= 6) {
34-
int idx = matchCount - 3;
35-
results.set(idx, results.get(idx) + 1);
17+
public LottoResults match(WinningLotto winningLotto) {
18+
LottoResults lottoResults = new LottoResults();
19+
for (Lotto lotto : this.lottos) {
20+
lottoResults.update(winningLotto.calculatePrize(lotto));
3621
}
22+
return lottoResults;
3723
}
3824

25+
@Override
3926
public String toString() {
4027
StringBuilder sb = new StringBuilder();
4128
for (Lotto lotto : lottos) {
42-
sb.append(lotto.value().toString()).append("\n");
29+
sb.append(lotto.toString()).append("\n");
4330
}
4431
return sb.toString();
4532
}
33+
34+
private static List<Lotto> createLottos(int count) {
35+
List<Lotto> lottos = new ArrayList<>();
36+
for (int i = 0; i < count; i++) {
37+
lottos.add(new Lotto());
38+
}
39+
return lottos;
40+
}
4641
}

src/main/java/lotto/model/PurchaseAmount.java renamed to src/main/java/lotto/model/Money.java

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,45 @@
22

33
import java.util.Objects;
44

5-
public class PurchaseAmount {
6-
private final int amount;
5+
public class Money {
6+
private final long amount;
77

8-
public PurchaseAmount(int amount) {
8+
public Money(int amount) {
9+
this((long) amount);
10+
}
11+
12+
public Money(long amount) {
913
if (!isValid(amount)) {
1014
throw new IllegalArgumentException("예산은 0 이상이며 1000원 단위로만 설정 가능합니다.");
1115
}
1216
this.amount = amount;
1317
}
1418

1519
public int countLottoTickets() {
16-
return amount / 1_000;
20+
return Math.toIntExact(amount / 1_000L);
1721
}
1822

1923
public Lottos buyLottos() {
2024
return new Lottos(countLottoTickets());
2125
}
2226

23-
public double getReturnRate(Long totalPrize) {
24-
if (amount == 0) {
25-
return 0;
26-
}
27-
double rate = (double) totalPrize / amount;
28-
return Math.floor(rate * 100) / 100.0;
27+
public boolean isZero() {
28+
return amount == 0;
29+
}
30+
31+
public double divideBy(Money money) {
32+
return (double) this.amount / money.amount;
2933
}
3034

31-
private boolean isValid(int amount) {
35+
private boolean isValid(Long amount) {
3236
return amount >= 0 && amount % 1000 == 0;
3337
}
3438

3539
@Override
3640
public boolean equals(Object o) {
3741
if (o == null || getClass() != o.getClass()) return false;
38-
PurchaseAmount purchaseAmount = (PurchaseAmount) o;
39-
return amount == purchaseAmount.amount;
42+
Money money = (Money) o;
43+
return amount == money.amount;
4044
}
4145

4246
@Override

0 commit comments

Comments
 (0)