-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat: step2 구현 #4188
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
base: sundongkim-dev
Are you sure you want to change the base?
feat: step2 구현 #4188
Changes from all commits
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,24 @@ | ||
package step2; | ||
|
||
import step2.domain.*; | ||
import step2.view.InputView; | ||
import step2.view.ResultView; | ||
|
||
import java.util.List; | ||
|
||
public class LottoGame { | ||
public static void main(String[] args) { | ||
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. LottoGame자체는 테스트가 불가능할까요? |
||
long amount = InputView.purchaseAmount(); | ||
LottoMachine machine = new LottoMachine(); | ||
List<LottoNumbers> purchased = machine.buy(amount); | ||
ResultView.showPurchasedLottos(purchased); | ||
|
||
String lastWinning = InputView.inputLastWinningLottoNumbers(); | ||
LottoNumbers winningNumbers = LottoNumbers.fromText(lastWinning); | ||
|
||
LottoNumberMatcher matcher = new LottoNumberMatcher(purchased, winningNumbers); | ||
LottoWinningRecord record = matcher.result(); | ||
|
||
ResultView.showLottoWinningResult(record, purchased.size()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package step2.domain; | ||
|
||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.IntStream; | ||
|
||
public class LottoMachine { | ||
public static final int PRICE_OF_LOTTO = 1_000; | ||
|
||
public List<LottoNumbers> buy(long money) { | ||
validate(money); | ||
return IntStream.range(0, (int) money / PRICE_OF_LOTTO) | ||
.mapToObj(i -> LottoNumberGenerator.generate()) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
private void validate(long money) { | ||
if (money < PRICE_OF_LOTTO) { | ||
throw new IllegalArgumentException("최소 1,000원 이상 입력해야 합니다."); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package step2.domain; | ||
|
||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
public class LottoNumber implements Comparable<LottoNumber> { | ||
private static final int MIN_NUMBER = 1; | ||
private static final int MAX_NUMBER = 45; | ||
private static final Map<Integer, LottoNumber> CACHE = new HashMap<>(); | ||
|
||
static { | ||
for (int i = MIN_NUMBER; i <= MAX_NUMBER; i++) { | ||
CACHE.put(i, new LottoNumber(i)); | ||
} | ||
} | ||
Comment on lines
+11
to
+15
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. cache 좋네요~! 👍 |
||
|
||
private final int number; | ||
|
||
private LottoNumber(int number) { | ||
this.number = number; | ||
} | ||
|
||
public static LottoNumber from(int number) { | ||
if (number < MIN_NUMBER || number > MAX_NUMBER) { | ||
throw new IllegalArgumentException( | ||
String.format("숫자는 %d보다 크거나 같고, %d보다 작아야 합니다.", MIN_NUMBER, MAX_NUMBER)); | ||
} | ||
return CACHE.get(number); | ||
} | ||
|
||
public int getNumber() { | ||
return number; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o) { | ||
if (this == o) return true; | ||
if (!(o instanceof LottoNumber)) return false; | ||
LottoNumber that = (LottoNumber) o; | ||
return number == that.number; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
return Integer.hashCode(number); | ||
} | ||
|
||
@Override | ||
public int compareTo(LottoNumber other) { | ||
return Integer.compare(this.number, other.number); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return String.valueOf(number); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package step2.domain; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.stream.Collectors; | ||
import java.util.stream.IntStream; | ||
|
||
public class LottoNumberGenerator { | ||
public static final int NUMBER_COUNT = 6; | ||
public static final int FINAL_NUMBER = 45; | ||
|
||
private LottoNumberGenerator() {} | ||
|
||
public static LottoNumbers generate() { | ||
List<LottoNumber> all = IntStream.rangeClosed(1, FINAL_NUMBER) | ||
.mapToObj(LottoNumber::from) | ||
.collect(Collectors.toList()); | ||
Collections.shuffle(all); | ||
return new LottoNumbers(all.subList(0, NUMBER_COUNT)); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package step2.domain; | ||
|
||
import java.util.EnumMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
public class LottoNumberMatcher { | ||
private final List<LottoNumbers> purchased; | ||
private final LottoNumbers winningNumbers; | ||
|
||
public LottoNumberMatcher(List<LottoNumbers> purchased, LottoNumbers winningNumbers) { | ||
this.purchased = purchased; | ||
this.winningNumbers = winningNumbers; | ||
} | ||
|
||
public LottoWinningRecord result() { | ||
Map<LottoRank, Integer> rankMap = new EnumMap<>(LottoRank.class); | ||
for (LottoRank rank : LottoRank.values()) { | ||
rankMap.put(rank, 0); | ||
} | ||
purchased.forEach(ticket -> { | ||
LottoRank rank = ticket.lottoRank(winningNumbers); | ||
rankMap.put(rank, rankMap.get(rank) + 1); | ||
}); | ||
return new LottoWinningRecord(rankMap); | ||
} | ||
Comment on lines
+16
to
+26
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. 아무런 매개변수 없는 메서드를 이용하는 것이라면 matcher라는 클래스가 반드시 필요할까요? |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
package step2.domain; | ||
|
||
import java.util.*; | ||
import java.util.stream.Collectors; | ||
|
||
import static step2.domain.LottoNumberGenerator.NUMBER_COUNT; | ||
|
||
public class LottoNumbers { | ||
private final List<LottoNumber> numbers; | ||
|
||
public LottoNumbers(List<LottoNumber> numbers) { | ||
validate(numbers); | ||
this.numbers = Collections.unmodifiableList( | ||
numbers.stream().sorted().collect(Collectors.toList())); | ||
} | ||
|
||
public static LottoNumbers from(List<Integer> numbers) { | ||
List<LottoNumber> lottoNumbers = numbers.stream() | ||
.map(LottoNumber::from) | ||
.collect(Collectors.toList()); | ||
return new LottoNumbers(lottoNumbers); | ||
} | ||
|
||
public static LottoNumbers fromText(String text) { | ||
List<Integer> numbers = Arrays.stream(text.split(",")) | ||
.map(String::trim) | ||
.map(Integer::parseInt) | ||
.collect(Collectors.toList()); | ||
return from(numbers); | ||
} | ||
Comment on lines
+24
to
+30
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. view와 밀접한 로직인것 같습니다. |
||
|
||
private void validate(List<LottoNumber> numbers) { | ||
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. 호출되는 위치에 가깝게 정의해주면 좋을것 같습니다. |
||
if (numbers == null || numbers.size() != NUMBER_COUNT) { | ||
throw new IllegalArgumentException("숫자가 빈값이거나, 개수가 맞지 않습니다."); | ||
} | ||
Set<LottoNumber> unique = new HashSet<>(numbers); | ||
if (unique.size() != numbers.size()) { | ||
throw new IllegalArgumentException("중복된 숫자가 존재합니다."); | ||
} | ||
} | ||
|
||
public List<LottoNumber> numbers() { | ||
return numbers; | ||
} | ||
|
||
public LottoRank lottoRank(LottoNumbers winningNumbers) { | ||
Set<LottoNumber> winningSet = new HashSet<>(winningNumbers.numbers()); | ||
long matchCount = numbers.stream().filter(winningSet::contains).count(); | ||
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. 한줄에 하나의 |
||
return LottoRank.fromMatch((int) matchCount); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package step2.domain; | ||
|
||
import java.util.Arrays; | ||
import java.util.Map; | ||
import java.util.function.Function; | ||
import java.util.stream.Collectors; | ||
|
||
public enum LottoRank { | ||
MATCH_3(3, 5_000, "3개 일치 (5000원)"), | ||
MATCH_4(4, 50_000, "4개 일치 (50000원)"), | ||
MATCH_5(5, 1_500_000, "5개 일치 (1500000원)"), | ||
MATCH_6(6, 2_000_000_000, "6개 일치 (2000000000원)"), | ||
NO_MATCH(0, 0, "NO_MATCH"); | ||
|
||
private static final Map<Integer, LottoRank> MATCH_MAP = Arrays.stream(values()) | ||
.collect(Collectors.toMap(rank -> rank.match, Function.identity())); | ||
Comment on lines
+15
to
+16
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. map으로 별도의 구현체를 만들어두신 이유가 있나요? |
||
|
||
private final int match; | ||
private final int money; | ||
private final String description; | ||
|
||
LottoRank(int match, int money, String description) { | ||
this.match = match; | ||
this.money = money; | ||
this.description = description; | ||
} | ||
|
||
public static LottoRank fromMatch(int match) { | ||
return MATCH_MAP.getOrDefault(match, NO_MATCH); | ||
} | ||
|
||
public int money() { | ||
return money; | ||
} | ||
|
||
public String description() { | ||
return description; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package step2.domain; | ||
|
||
import java.util.Collections; | ||
import java.util.Map; | ||
|
||
public class LottoWinningRecord { | ||
private final Map<LottoRank, Integer> rankMap; | ||
|
||
public LottoWinningRecord(Map<LottoRank, Integer> rankMap) { | ||
this.rankMap = Collections.unmodifiableMap(rankMap); | ||
} | ||
|
||
public double totalLottoPrizeRatio(int totalSpent) { | ||
long totalPrize = rankMap.entrySet().stream() | ||
.mapToLong(e -> e.getKey().money() * e.getValue()) | ||
.sum(); | ||
return totalSpent == 0 ? 0 : (double) totalPrize / totalSpent; | ||
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. 삼항연산자는 사용하지 말아주세요. |
||
} | ||
|
||
public Map<LottoRank, Integer> rankMap() { | ||
return rankMap; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package step2.view; | ||
|
||
import java.util.Scanner; | ||
|
||
public class InputView { | ||
private static final Scanner scanner = new Scanner(System.in); | ||
|
||
public static long purchaseAmount() { | ||
System.out.println("구입금액을 입력해주세요."); | ||
return scanner.nextLong(); | ||
} | ||
|
||
public static String inputLastWinningLottoNumbers() { | ||
System.out.println("\n지난 주 당첨 번호를 입력해 주세요."); | ||
scanner.nextLine(); // flush | ||
return scanner.nextLine(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package step2.view; | ||
|
||
import step2.domain.LottoMachine; | ||
import step2.domain.LottoNumbers; | ||
import step2.domain.LottoRank; | ||
import step2.domain.LottoWinningRecord; | ||
|
||
import java.util.List; | ||
import java.util.Map; | ||
|
||
public class ResultView { | ||
public static void showPurchasedLottos(List<LottoNumbers> lottoNumbers) { | ||
System.out.println(lottoNumbers.size() + "개를 구매했습니다."); | ||
lottoNumbers.forEach(number -> System.out.println(number.numbers())); | ||
} | ||
|
||
public static void showLottoWinningResult(LottoWinningRecord record, int purchasedLottoCount) { | ||
System.out.println("\n당첨 통계\n---------"); | ||
Map<LottoRank, Integer> rankMap = record.rankMap(); | ||
rankMap.entrySet().stream() | ||
.filter(entry -> entry.getKey() != LottoRank.NO_MATCH) | ||
.forEach(entry -> | ||
System.out.printf("%s - %d개%n", entry.getKey().description(), entry.getValue())); | ||
|
||
double ratio = record.totalLottoPrizeRatio(purchasedLottoCount * LottoMachine.PRICE_OF_LOTTO); | ||
System.out.printf("총 수익률은 %.2f입니다.", ratio); | ||
if (ratio < 1) { | ||
System.out.println("(기준이 1이기 때문에 결과적으로 손해라는 의미임)"); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package step2.domain; | ||
|
||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
|
||
import java.util.List; | ||
|
||
import static org.assertj.core.api.Assertions.*; | ||
|
||
public class LottoMachineTest { | ||
|
||
@Test | ||
@DisplayName("정상적인 금액으로 로또를 구매하면 금액 / 1000 만큼 생성된다") | ||
void buyLotto_success() { | ||
LottoMachine machine = new LottoMachine(); | ||
long amount = 5000; | ||
|
||
List<LottoNumbers> lottos = machine.buy(amount); | ||
|
||
assertThat(lottos).hasSize(5); | ||
assertThat(lottos).allSatisfy(lotto -> assertThat(lotto.numbers()).hasSize(6)); | ||
} | ||
|
||
@Test | ||
@DisplayName("1000원 미만의 금액으로는 로또를 구매할 수 없다") | ||
void buyLotto_failUnderMinimum() { | ||
LottoMachine machine = new LottoMachine(); | ||
|
||
assertThatThrownBy(() -> machine.buy(500)) | ||
.isInstanceOf(IllegalArgumentException.class) | ||
.hasMessageContaining("최소 1,000원 이상 입력해야 합니다."); | ||
} | ||
|
||
@Test | ||
@DisplayName("정확히 1000원을 입력하면 로또 1장을 구매할 수 있다") | ||
void buyLotto_exactMinimum() { | ||
LottoMachine machine = new LottoMachine(); | ||
|
||
List<LottoNumbers> lottos = machine.buy(1000); | ||
|
||
assertThat(lottos).hasSize(1); | ||
} | ||
} |
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.
ai를 이용해 바이브 코딩을 하셨으면 TDD를 하기 더 쉬웠을것 같은데요. 전체적인 클래스에 비해 테스트 코드가 빈약한것 같습니다. 😢
ai를 활용하는 것은 좋지만 지금 교육 프로그램에 참여하신 목적에 더 알맞게 객체지향적으로 TDD 사이클을 이용해 생각하는 연습을 하시면 더 도움이 되지 않을까 싶습니다.
지금은 너무 시간에 쫓겨 미션을 수행하기 위해 급히 바이브 코딩을 하신것 같은데 그러면 이 과정에서 얻어가시는게 너무 적지 않으실까 싶습니다.