Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
40 changes: 40 additions & 0 deletions src/main/java/lotto/Lotto.java
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) {
Copy link
Contributor

Choose a reason for hiding this comment

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

중복 체크를 고려한다면 처음부터 List가 아닌 Set으로 구현하는 것을 어떨까?
Set으로 관리하다 외부에서 이 값을 접근할 때 순서를 보장해야 한다면 정렬해서 반환하는 것은 어떨까?

throw new IllegalArgumentException("로또 번호는 중복될 수 없다.");
}
boolean outOfRange = numbers.stream().anyMatch(n -> n < 1 || n > 45);
if (outOfRange) {
throw new IllegalArgumentException("로또 번호는 1~45 범위여야 한다.");
}
Copy link
Contributor

Choose a reason for hiding this comment

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

validate를 한 메서드에서 하기보다 각 성격에 따라 메서드를 분리해 보는 것은 어떨까?

}
}
20 changes: 20 additions & 0 deletions src/main/java/lotto/LottoMachine.java
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);
}
}
7 changes: 7 additions & 0 deletions src/main/java/lotto/LottoNumberGenerator.java
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();
}
32 changes: 32 additions & 0 deletions src/main/java/lotto/Money.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package lotto;

public class Money {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

private static final int UNIT_PRICE = 1000;
Copy link
Contributor

Choose a reason for hiding this comment

The 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원 단위여야 한다.");
}
}
}
37 changes: 37 additions & 0 deletions src/main/java/lotto/Rank.java
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);
Copy link
Contributor

Choose a reason for hiding this comment

The 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("당첨 등수가 없다."));
}
}
16 changes: 16 additions & 0 deletions src/main/java/lotto/ShuffleNumberGenerator.java
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);
}
}
17 changes: 17 additions & 0 deletions src/main/java/lotto/WinningNumbers.java
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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

Lotto 객체와 다를 것이 없어 보인다.
중복으로 보이는데 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);
}
}
42 changes: 42 additions & 0 deletions src/main/java/lotto/WinningStatistics.java
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);
}
}
17 changes: 17 additions & 0 deletions src/test/java/lotto/FakeNumberGenerator.java
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++);
}
}
23 changes: 23 additions & 0 deletions src/test/java/lotto/LottoMachineTest.java
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);
}
}
48 changes: 48 additions & 0 deletions src/test/java/lotto/LottoTest.java
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)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
assertThatThrownBy(() -> new Lotto(List.of(1,2,3,4,5)))
assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5)))

컨벤션 위반

.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));
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Lotto lotto = new Lotto(List.of(8, 21, 23, 41, 42, 43));
Lotto lotto = new Lotto(8, 21, 23, 41, 42, 43);

가변 인자를 지원하는 생성자를 추가해 위와 같이 구현 가능하도록 지원하면 어떨까?

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);
}
}
30 changes: 30 additions & 0 deletions src/test/java/lotto/MoneyTest.java
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);
}
}
31 changes: 31 additions & 0 deletions src/test/java/lotto/RankTest.java
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();
}
}
Loading