-
Notifications
You must be signed in to change notification settings - Fork 1.1k
lotto step 2 #4242
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: kay019
Are you sure you want to change the base?
lotto step 2 #4242
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,40 @@ | ||
| package lotto; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
|
|
||
| public class Lotto { | ||
| private static final int SIZE = 6; | ||
| private final List<Integer> numbers; | ||
|
|
||
| public Lotto(List<Integer> numbers) { | ||
| validate(numbers); | ||
| List<Integer> sorted = new ArrayList<>(numbers); | ||
| Collections.sort(sorted); | ||
| this.numbers = Collections.unmodifiableList(sorted); | ||
| } | ||
|
|
||
| public List<Integer> numbers() { | ||
| return numbers; | ||
| } | ||
|
|
||
| public int matchCount(WinningNumbers winningNumbers) { | ||
| return (int) numbers.stream() | ||
| .filter(winningNumbers::contains) | ||
| .count(); | ||
| } | ||
|
|
||
| private void validate(List<Integer> numbers) { | ||
| if (numbers.size() != SIZE) { | ||
| throw new IllegalArgumentException("로또 번호는 6개여야 한다."); | ||
| } | ||
| if (numbers.stream().distinct().count() != SIZE) { | ||
| throw new IllegalArgumentException("로또 번호는 중복될 수 없다."); | ||
| } | ||
| boolean outOfRange = numbers.stream().anyMatch(n -> n < 1 || n > 45); | ||
| if (outOfRange) { | ||
| throw new IllegalArgumentException("로또 번호는 1~45 범위여야 한다."); | ||
| } | ||
|
Contributor
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. validate를 한 메서드에서 하기보다 각 성격에 따라 메서드를 분리해 보는 것은 어떨까? |
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package lotto; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.List; | ||
|
|
||
| public class LottoMachine { | ||
| private final LottoNumberGenerator generator; | ||
|
|
||
| public LottoMachine(LottoNumberGenerator generator) { | ||
| this.generator = generator; | ||
| } | ||
|
|
||
| public List<Lotto> issue(Money money) { | ||
| List<Lotto> lottos = new ArrayList<>(); | ||
| for (int i = 0; i < money.ticketCount(); i++) { | ||
| lottos.add(new Lotto(generator.generate())); | ||
| } | ||
| return List.copyOf(lottos); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| package lotto; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public interface LottoNumberGenerator { | ||
| List<Integer> generate(); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package lotto; | ||
|
|
||
| public class Money { | ||
|
Contributor
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. 👍 |
||
| private static final int UNIT_PRICE = 1000; | ||
|
Contributor
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_000"와 같이 언더바를 통해 구분할 수 있다. |
||
| private final int amount; | ||
|
|
||
| private Money(int amount) { | ||
| validate(amount); | ||
| this.amount = amount; | ||
| } | ||
|
|
||
| public static Money of(int amount) { | ||
| return new Money(amount); | ||
| } | ||
|
|
||
| public int ticketCount() { | ||
| return amount / UNIT_PRICE; | ||
| } | ||
|
|
||
| public int amount() { | ||
| return amount; | ||
| } | ||
|
|
||
| private void validate(int amount) { | ||
| if (amount <= 0) { | ||
| throw new IllegalArgumentException("구입 금액은 0보다 커야 한다."); | ||
| } | ||
| if (amount % UNIT_PRICE != 0) { | ||
| throw new IllegalArgumentException("구입 금액은 1000원 단위여야 한다."); | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package lotto; | ||
|
|
||
| import java.util.Arrays; | ||
|
|
||
| public enum Rank { | ||
| THREE(3, 5_000), | ||
| FOUR(4, 50_000), | ||
| FIVE(5, 1_500_000), | ||
| SIX(6, 2_000_000_000); | ||
|
Contributor
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등, 2등과 같은 표현의 이름을 사용하는 것은 어떨까? |
||
|
|
||
| private final int matchCount; | ||
| private final long prize; | ||
|
|
||
| Rank(int matchCount, long prize) { | ||
| this.matchCount = matchCount; | ||
| this.prize = prize; | ||
| } | ||
|
|
||
| public int matchCount() { | ||
| return matchCount; | ||
| } | ||
|
|
||
| public long prize() { | ||
| return prize; | ||
| } | ||
|
|
||
| public static boolean isWinning(int matchCount) { | ||
| return Arrays.stream(values()).anyMatch(r -> r.matchCount == matchCount); | ||
| } | ||
|
|
||
| public static Rank from(int matchCount) { | ||
| return Arrays.stream(values()) | ||
| .filter(r -> r.matchCount == matchCount) | ||
| .findFirst() | ||
| .orElseThrow(() -> new IllegalArgumentException("당첨 등수가 없다.")); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package lotto; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Collections; | ||
| import java.util.List; | ||
| import java.util.stream.IntStream; | ||
|
|
||
| public class ShuffleNumberGenerator implements LottoNumberGenerator{ | ||
| @Override | ||
| public List<Integer> generate() { | ||
| List<Integer> numbers = IntStream.rangeClosed(1, 45) | ||
| .collect(ArrayList::new, List::add, List::addAll); | ||
| Collections.shuffle(numbers); | ||
| return numbers.subList(0, 6); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package lotto; | ||
|
|
||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| public class WinningNumbers { | ||
|
Contributor
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. Lotto 객체와 다를 것이 없어 보인다. |
||
| private final Set<Integer> numbers; | ||
|
|
||
| public WinningNumbers(List<Integer> numbers) { | ||
| new Lotto(numbers); // 규칙 재사용 | ||
| this.numbers = Set.copyOf(numbers); | ||
| } | ||
|
|
||
| public boolean contains(int number) { | ||
| return numbers.contains(number); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package lotto; | ||
|
|
||
| import java.util.EnumMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| public class WinningStatistics { | ||
| private final Map<Rank, Integer> counts = new EnumMap<>(Rank.class); | ||
|
|
||
| public WinningStatistics() { | ||
| for (Rank rank : Rank.values()) { | ||
| counts.put(rank, 0); | ||
| } | ||
| } | ||
|
|
||
| public void accumulate(List<Lotto> lottos, WinningNumbers winningNumbers) { | ||
| lottos.forEach(lotto -> accumulateOne(lotto, winningNumbers)); | ||
| } | ||
|
|
||
| public int countOf(Rank rank) { | ||
| return counts.get(rank); | ||
| } | ||
|
|
||
| public long totalPrize() { | ||
| return counts.entrySet().stream() | ||
| .mapToLong(e -> e.getKey().prize() * e.getValue()) | ||
| .sum(); | ||
| } | ||
|
|
||
| public double profitRate(Money purchase) { | ||
| return (double) totalPrize() / purchase.amount(); | ||
| } | ||
|
|
||
| private void accumulateOne(Lotto lotto, WinningNumbers winningNumbers) { | ||
| int match = lotto.matchCount(winningNumbers); | ||
| if (!Rank.isWinning(match)) { | ||
| return; | ||
| } | ||
| Rank rank = Rank.from(match); | ||
| counts.put(rank, counts.get(rank) + 1); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package lotto; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class FakeNumberGenerator implements LottoNumberGenerator{ | ||
| private final List<List<Integer>> fixed; | ||
| private int index = 0; | ||
|
|
||
| public FakeNumberGenerator(List<List<Integer>> fixed) { | ||
| this.fixed = fixed; | ||
| } | ||
|
|
||
| @Override | ||
| public List<Integer> generate() { | ||
| return fixed.get(index++); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package lotto; | ||
|
|
||
| import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; | ||
|
|
||
| import java.util.List; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| public class LottoMachineTest { | ||
| @Test | ||
| @DisplayName("구입 금액에 맞는 개수의 로또를 발급한다") | ||
| void issueLottosByMoney() { | ||
| LottoNumberGenerator generator = new FakeNumberGenerator(List.of( | ||
| List.of(1,2,3,4,5,6), | ||
| List.of(7,8,9,10,11,12) | ||
| )); | ||
|
|
||
| LottoMachine machine = new LottoMachine(generator); | ||
| List<Lotto> lottos = machine.issue(Money.of(2000)); | ||
|
|
||
| assertThat(lottos).hasSize(2); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,48 @@ | ||||||
| package lotto; | ||||||
|
|
||||||
| import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; | ||||||
| import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; | ||||||
|
|
||||||
| import java.util.List; | ||||||
| import org.junit.jupiter.api.DisplayName; | ||||||
| import org.junit.jupiter.api.Test; | ||||||
|
|
||||||
| public class LottoTest { | ||||||
| @Test | ||||||
| @DisplayName("로또 번호는 반드시 6개여야 한다") | ||||||
| void lottoMustHaveSixNumbers() { | ||||||
| assertThatThrownBy(() -> new Lotto(List.of(1,2,3,4,5))) | ||||||
|
Contributor
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
컨벤션 위반 |
||||||
| .isInstanceOf(IllegalArgumentException.class); | ||||||
| } | ||||||
|
|
||||||
| @Test | ||||||
| @DisplayName("로또 번호는 중복될 수 없다") | ||||||
| void lottoNumbersMustBeUnique() { | ||||||
| assertThatThrownBy(() -> new Lotto(List.of(1,1,2,3,4,5))) | ||||||
| .isInstanceOf(IllegalArgumentException.class); | ||||||
| } | ||||||
|
|
||||||
| @Test | ||||||
| @DisplayName("로또 번호는 1부터 45 사이여야 한다") | ||||||
| void lottoNumbersMustBeInRange() { | ||||||
| assertThatThrownBy(() -> new Lotto(List.of(0,1,2,3,4,5))) | ||||||
| .isInstanceOf(IllegalArgumentException.class); | ||||||
| } | ||||||
|
|
||||||
| @Test | ||||||
| @DisplayName("로또 번호는 오름차순으로 정렬된다") | ||||||
| void lottoNumbersAreSorted() { | ||||||
| Lotto lotto = new Lotto(List.of(8, 21, 23, 41, 42, 43)); | ||||||
|
Contributor
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
가변 인자를 지원하는 생성자를 추가해 위와 같이 구현 가능하도록 지원하면 어떨까? |
||||||
| assertThat(lotto.numbers()) | ||||||
| .containsExactly(8, 21, 23, 41, 42, 43); | ||||||
| } | ||||||
|
|
||||||
| @Test | ||||||
| @DisplayName("당첨 번호와 일치하는 개수를 계산한다") | ||||||
| void matchCountCalculation() { | ||||||
| Lotto lotto = new Lotto(List.of(1,2,3,10,11,12)); | ||||||
| WinningNumbers winning = new WinningNumbers(List.of(1,2,3,4,5,6)); | ||||||
|
|
||||||
| assertThat(lotto.matchCount(winning)).isEqualTo(3); | ||||||
| } | ||||||
| } | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package lotto; | ||
|
|
||
| import static org.assertj.core.api.AssertionsForClassTypes.assertThat; | ||
| import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; | ||
|
|
||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| public class MoneyTest { | ||
| @Test | ||
| @DisplayName("구입 금액은 0보다 커야 한다") | ||
| void amountMustBePositive() { | ||
| assertThatThrownBy(() -> Money.of(0)) | ||
| .isInstanceOf(IllegalArgumentException.class); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("구입 금액은 1000원 단위여야 한다") | ||
| void amountMustBeMultipleOf1000() { | ||
| assertThatThrownBy(() -> Money.of(1500)) | ||
| .isInstanceOf(IllegalArgumentException.class); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("구입 금액을 1000으로 나눈 값이 로또 구매 수량이다") | ||
| void ticketCountCalculation() { | ||
| Money money = Money.of(14000); | ||
| assertThat(money.ticketCount()).isEqualTo(14); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package lotto; | ||
|
|
||
| import static org.assertj.core.api.AssertionsForClassTypes.assertThat; | ||
| import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; | ||
|
|
||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| public class RankTest { | ||
| @Test | ||
| @DisplayName("일치 개수로 당첨 등수를 찾는다") | ||
| void findRankByMatchCount() { | ||
| assertThat(Rank.from(3)).isEqualTo(Rank.THREE); | ||
| assertThat(Rank.from(6)).isEqualTo(Rank.SIX); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("당첨이 아닌 개수로 조회하면 예외가 발생한다") | ||
| void invalidMatchCountThrowsException() { | ||
| assertThatThrownBy(() -> Rank.from(2)) | ||
| .isInstanceOf(IllegalArgumentException.class); | ||
| } | ||
|
|
||
| @Test | ||
| @DisplayName("당첨 여부는 3~6개만 true다") | ||
| void winningCheck() { | ||
| assertThat(Rank.isWinning(2)).isFalse(); | ||
| assertThat(Rank.isWinning(3)).isTrue(); | ||
| assertThat(Rank.isWinning(6)).isTrue(); | ||
| } | ||
| } |
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.
중복 체크를 고려한다면 처음부터 List가 아닌 Set으로 구현하는 것을 어떨까?
Set으로 관리하다 외부에서 이 값을 접근할 때 순서를 보장해야 한다면 정렬해서 반환하는 것은 어떨까?