-
Notifications
You must be signed in to change notification settings - Fork 357
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
[Step2] 로또(자동) #705
base: qktlf789456
Are you sure you want to change the base?
[Step2] 로또(자동) #705
Changes from all commits
9b43eae
052868f
a17df7d
d3e93ed
2e9965a
e0a0e3c
2099d6b
4580dd6
c10110f
09a0b12
2dc324a
7f1fde6
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 |
---|---|---|
@@ -1 +1,9 @@ | ||
# kotlin-lotto | ||
# kotlin-lotto | ||
|
||
## TODO | ||
- [x] 로또를 생성할 수 있다. | ||
- [x] 중복된 숫자가 포함된 로또를 생성할 수 없다. | ||
- [x] 생성된 로또 숫자들을 오름차순으로 조회할 수 있다. | ||
- [x] 로또 라운드에서 당첨번호를 통해 결과를 도출할 수 있다. | ||
- [x] 해당 라운드에서 발행된 로또중 당첨번호와 일치하는 숫자 개수별로 구분할 수 있다. | ||
- [x] 일치하는 숫자 개수별로 수익률을 알 수 있다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package lotto.domain | ||
class Lotto(lottoNumbers: List<LottoNumber>) { | ||
val lottoNumbers: List<LottoNumber> | ||
|
||
init { | ||
require(lottoNumbers.size == LOTTO_SIZE) | ||
hasNoDuplicatedNumbers(lottoNumbers) | ||
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. 자료구조를 잘 활용하면 이 부분은 제거해도 될거같아요. |
||
|
||
this.lottoNumbers = lottoNumbers.sortedBy { it.number } | ||
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. 이건 화면의 중요한 로직이겠네요~ |
||
} | ||
|
||
fun getSameNumberCount(lotto: Lotto): Int { | ||
return (LOTTO_SIZE * 2) - (lottoNumbers + lotto.lottoNumbers).toSet().size | ||
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 fun hasNoDuplicatedNumbers(lottoNumbers: List<LottoNumber>) { | ||
require(lottoNumbers.toSet().size == LOTTO_SIZE) | ||
} | ||
|
||
override fun equals(other: Any?): Boolean { | ||
if (this === other) return true | ||
if (other !is Lotto) return false | ||
|
||
if (lottoNumbers != other.lottoNumbers) return false | ||
|
||
return true | ||
} | ||
|
||
override fun hashCode(): Int { | ||
return lottoNumbers.hashCode() | ||
} | ||
Comment on lines
+20
to
+31
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. 어떻게 하면 제거할 수 있을까요? |
||
|
||
companion object { | ||
const val LOTTO_SIZE = 6 | ||
|
||
fun of(lottoNumbers: List<Int>): Lotto = Lotto(lottoNumbers.map { LottoNumber(it) }) | ||
} | ||
} | ||
|
||
data class LottoNumber(val number: Int) { | ||
init { | ||
require(MIN_LOTTO_NUMBER <= number) | ||
require(number <= MAX_LOTTO_NUMBER) | ||
Comment on lines
+42
to
+43
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.
|
||
} | ||
|
||
companion object { | ||
const val MIN_LOTTO_NUMBER = 1 | ||
const val MAX_LOTTO_NUMBER = 45 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package lotto.domain | ||
|
||
import lotto.domain.Lotto.Companion.LOTTO_SIZE | ||
import lotto.domain.LottoNumber.Companion.MAX_LOTTO_NUMBER | ||
import lotto.domain.LottoNumber.Companion.MIN_LOTTO_NUMBER | ||
|
||
interface LottoGenerator { | ||
fun generate(): Lotto | ||
} | ||
|
||
class RandomLottoGenerator : LottoGenerator { | ||
override fun generate(): Lotto = LOTTO_NUMBERS.shuffled().subList(0, LOTTO_SIZE).let { Lotto(it) } | ||
|
||
companion object { | ||
private val LOTTO_NUMBERS = (MIN_LOTTO_NUMBER..MAX_LOTTO_NUMBER).map { LottoNumber(it) } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package lotto.domain | ||
|
||
import lotto.domain.Money.Companion.toMoney | ||
|
||
data class LottoResult( | ||
val lotto: Lotto, | ||
val winningLotto: Lotto | ||
) { | ||
val sameNumberCount: Int = winningLotto.getSameNumberCount(lotto) | ||
|
||
val reward: LottoReward? = LottoReward.getReward(sameNumberCount) | ||
Comment on lines
+9
to
+11
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. 이것도 순서를 위아래 바꾸면 좋겟네요~! |
||
} | ||
|
||
enum class LottoReward(val prize: Long, val sameNumberCount: Int) { | ||
WINNER_1ST(2000L.millionWon(), 6), | ||
WINNER_2ST(1500L.thousandWon(), 5), | ||
WINNER_3ST(50L.thousandWon(), 4), | ||
WINNER_4ST(5L.thousandWon(), 3); | ||
|
||
fun toMoney(): Money = prize.toMoney() | ||
|
||
companion object { | ||
fun getReward(sameNumberCount: Int): LottoReward? = values().firstOrNull { sameNumberCount == it.sameNumberCount } | ||
} | ||
} | ||
|
||
private fun Long.thousandWon(): Long = this * 1000L | ||
|
||
private fun Long.millionWon(): Long = this.thousandWon().thousandWon() | ||
Comment on lines
+27
to
+29
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.
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package lotto.domain | ||
class LottoRound(private val lottoGenerator: LottoGenerator) { | ||
constructor(lottoRoundElements: LottoRoundElements) : this(lottoRoundElements.lottoGenerator) | ||
|
||
private val lottos: MutableList<Lotto> = mutableListOf() | ||
|
||
fun addNewLottos(newLottoSize: Int) { | ||
repeat(newLottoSize) { | ||
lottos.add(newLotto()) | ||
} | ||
} | ||
Comment on lines
+5
to
+11
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.
|
||
|
||
fun getLottos(): List<Lotto> = lottos.toList() | ||
|
||
fun lotteryDraw(winningLotto: Lotto): LottoRoundStatistics { | ||
return LottoRoundStatistics(lottos, winningLotto) | ||
} | ||
|
||
private fun newLotto() = lottoGenerator.generate() | ||
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. 좋네요~ 이런 명확한 함수는 좋습니다~ |
||
} | ||
|
||
data class LottoRoundElements( | ||
val lottoGenerator: LottoGenerator = RandomLottoGenerator() | ||
) | ||
Comment on lines
+22
to
+24
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. 해당 클래스는 무엇일까요? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package lotto.domain | ||
|
||
import lotto.domain.Money.Companion.NO_MONEY | ||
import lotto.domain.Money.Companion.plus | ||
|
||
class LottoRoundStatistics( | ||
lottos: List<Lotto>, | ||
private val winningLotto: Lotto | ||
) { | ||
val lottoResults: List<LottoResult> = lottos.map { LottoResult(it, winningLotto) } | ||
|
||
val totalPrize: Money = lottoResults.mapNotNull { it.reward } | ||
.fold(NO_MONEY) { money, lottoReward -> | ||
money + lottoReward.toMoney() | ||
} | ||
|
||
fun getLottoRewardOf(lottoReward: LottoReward): List<LottoResult> = lottoResults.filter { it.reward == lottoReward }.toList() | ||
} | ||
|
||
@JvmInline value class Money(val value: Long) { | ||
companion object { | ||
fun Long.toMoney(): Money = Money(this) | ||
|
||
operator fun Money.plus(other: Money): Money = (value + other.value).toMoney() | ||
|
||
val NO_MONEY: Money = 0L.toMoney() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package lotto.domain | ||
|
||
import lotto.domain.Money.Companion.toMoney | ||
|
||
class LottoServiceRound { | ||
private val lottoRound = LottoRound(LottoRoundElements()) | ||
|
||
fun buyLottos(payment: Long): List<Lotto> { | ||
lottoRound.addNewLottos(payment.buyableCount()) | ||
return lottoRound.getLottos() | ||
} | ||
|
||
fun allPayment(): Long = (lottoRound.getLottos().size * LOTTO_BUY_PRIZE.value) | ||
|
||
fun lotteryDraw(numbers: List<Int>): LottoRoundStatistics { | ||
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. 함수명은 동사로 해볼까요? |
||
val winningLotto = Lotto.of(numbers) | ||
return lottoRound.lotteryDraw(winningLotto) | ||
} | ||
|
||
private fun Long.buyableCount(): Int = (this / LOTTO_BUY_PRIZE.value).toInt() | ||
|
||
companion object { | ||
private val LOTTO_BUY_PRIZE = 1000L.toMoney() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package lotto.view | ||
|
||
import java.lang.IllegalArgumentException | ||
|
||
class LottoInputView { | ||
fun inputLottoBuy(): Int { | ||
println(LOTTO_BUY_COMMENT) | ||
return readLineOrThrows().toInt() | ||
} | ||
|
||
fun inputWinningLotto(): List<Int> { | ||
println(WINNING_LOTTO_COMMENT) | ||
return readLineOrThrows().replace(" ", "").split(",").map { it.toInt() } | ||
} | ||
|
||
private fun readLineOrThrows(): String = readLine() ?: throw IllegalArgumentException() | ||
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.
|
||
|
||
companion object { | ||
private const val LOTTO_BUY_COMMENT = "구입금액을 입력해 주세요." | ||
private const val WINNING_LOTTO_COMMENT = "지난 주 당첨 번호를 입력해 주세요." | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
package lotto.view | ||
|
||
import lotto.domain.Lotto | ||
import lotto.domain.LottoReward | ||
import lotto.domain.LottoRoundStatistics | ||
|
||
class LottoOutputView { | ||
fun currentLottos(lottos: List<Lotto>) { | ||
val lottoSize = lottos.size | ||
println("${lottoSize}개를 구매했습니다.") | ||
repeat(lottoSize) { index -> | ||
lottos[index].lottoNumbers.map { it.number }.joinToString(prefix = "[", separator = ", ", postfix = "]").also { println(it) } | ||
} | ||
} | ||
|
||
fun result(payment: Long, lottoRoundStatistics: LottoRoundStatistics) { | ||
println(WINNING_C0MMENT) | ||
|
||
with(LottoReward.WINNER_4ST) { | ||
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.
|
||
lottoRoundStatistics.getLottoRewardOf(this).run { | ||
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. 가독성을 위해 개인적으로 |
||
println("${sameNumberCount}개 일치 (${prize}원)- $size") | ||
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.
|
||
} | ||
} | ||
|
||
with(LottoReward.WINNER_3ST) { | ||
lottoRoundStatistics.getLottoRewardOf(this).run { | ||
println("${sameNumberCount}개 일치 (${prize}원)- $size") | ||
} | ||
} | ||
|
||
with(LottoReward.WINNER_2ST) { | ||
lottoRoundStatistics.getLottoRewardOf(this).run { | ||
println("${sameNumberCount}개 일치 (${prize}원)- $size") | ||
} | ||
} | ||
|
||
with(LottoReward.WINNER_1ST) { | ||
lottoRoundStatistics.getLottoRewardOf(this).run { | ||
println("${sameNumberCount}개 일치 (${prize}원)- $size") | ||
} | ||
} | ||
|
||
println("총 수익률은 0.${(lottoRoundStatistics.totalPrize.value * 100 / payment)}입니다.") | ||
} | ||
|
||
companion object { | ||
private const val WINNING_C0MMENT = "당첨 통계\n---------" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import lotto.domain.LottoServiceRound | ||
import lotto.view.LottoInputView | ||
import lotto.view.LottoOutputView | ||
|
||
fun main() = lotto() | ||
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. 메인을 바로 사용하는것은 어떨까요? |
||
|
||
fun lotto() { | ||
val lottoServiceRound = LottoServiceRound() | ||
val lottoInputView = LottoInputView() | ||
val lottoOutputView = LottoOutputView() | ||
|
||
val payment = lottoInputView.inputLottoBuy() | ||
lottoServiceRound.buyLottos(payment.toLong()).also { lottoOutputView.currentLottos(it) } | ||
|
||
val winningLottoNumbers = lottoInputView.inputWinningLotto() | ||
val lottoRoundStatistics = lottoServiceRound.lotteryDraw(winningLottoNumbers) | ||
lottoOutputView.result(lottoServiceRound.allPayment(), lottoRoundStatistics) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package lotto.domain | ||
|
||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.assertThrows | ||
import java.lang.RuntimeException | ||
|
||
class LottoNumberTest { | ||
@Test | ||
fun `로또 숫자의 범위는 1 ~ 45 까지만 허용된다`() { | ||
// given | ||
// when | ||
// then | ||
(1..45).forEach { | ||
assertThat(LottoNumber(it).number).isEqualTo(it) | ||
} | ||
Comment on lines
+14
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. 테스트코드 내부에서 반복을 사용하면 복잡해질 수 있어요. |
||
} | ||
|
||
@Test | ||
fun `1 ~ 45 범위 이외에 로또번호는 생성될 수 없다`() { | ||
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. 46에 대한 검즈은 없네요! |
||
// given | ||
|
||
// when | ||
|
||
// then | ||
assertThrows<RuntimeException> { | ||
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. 정확한 |
||
LottoNumber(0).number | ||
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.
|
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package lotto.domain | ||
|
||
import lotto.domain.Money.Companion.toMoney | ||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
|
||
class LottoRoundStatisticsTest { | ||
|
||
@Test | ||
fun `당첨된 로또를 기준으로 추첨 상세결과를 확인할 수 있다`() { | ||
// given | ||
val lotto = (1..6).map { LottoNumber(it) }.toList().let { Lotto(it) } | ||
val winningLotto = (1..6).map { LottoNumber(it) }.toList().let { Lotto(it) } | ||
val sut = LottoRoundStatistics(listOf(lotto), winningLotto) | ||
|
||
// when | ||
// then | ||
assertThat(sut.lottoResults.size).isEqualTo(1) | ||
assertThat(sut.lottoResults.first().lotto).isEqualTo(lotto) | ||
assertThat(sut.lottoResults.first().winningLotto).isEqualTo(winningLotto) | ||
assertThat(sut.lottoResults.first().sameNumberCount).isEqualTo(6) | ||
assertThat(sut.lottoResults.first().reward).isEqualTo(LottoReward.WINNER_1ST) | ||
assertThat(sut.totalPrize).isEqualTo(LottoReward.WINNER_1ST.prize.toMoney()) | ||
Comment on lines
+18
to
+23
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. 여기서 생성자를 테스트 하는 코드도 조금은 보이네요! 테스트를 조금 더 분리해볼까요? |
||
} | ||
|
||
@Test | ||
fun `LottoReward 에 해당하는 로또를 가져올 수 있다`() { | ||
// given | ||
val lotto = (1..6).map { LottoNumber(it) }.toList().let { Lotto(it) } | ||
val winningLotto = (1..6).map { LottoNumber(it) }.toList().let { Lotto(it) } | ||
val sut = LottoRoundStatistics(listOf(lotto), winningLotto) | ||
|
||
// when | ||
// then | ||
assertThat(sut.getLottoRewardOf(LottoReward.WINNER_1ST).size).isEqualTo(1) | ||
assertThat(sut.getLottoRewardOf(LottoReward.WINNER_2ST).size).isEqualTo(0) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package lotto.domain | ||
|
||
import org.assertj.core.api.Assertions.assertThat | ||
import org.junit.jupiter.api.Test | ||
|
||
class LottoRoundTest { | ||
|
||
@Test | ||
fun `새로운 로또를 개수만큼 추가할 수 있다`() { | ||
// given | ||
val sut = LottoRound(LottoRoundElements()) | ||
val newLottoSize = 3 | ||
|
||
// when | ||
sut.addNewLottos(newLottoSize) | ||
|
||
// then | ||
assertThat(sut.getLottos().size).isEqualTo(newLottoSize) | ||
} | ||
|
||
@Test | ||
fun `로또 추첨결과를 얻을 수 있다`() { | ||
// given | ||
val winningLotto = (1..6).map { LottoNumber(it) }.toList().let { Lotto(it) } | ||
val sut = LottoRound(LottoRoundElements()) | ||
val newLottoSize = 3 | ||
sut.addNewLottos(3) | ||
|
||
// when | ||
val result = sut.lotteryDraw(winningLotto) | ||
|
||
// then | ||
assertThat(result.lottoResults.size).isEqualTo(newLottoSize) | ||
} | ||
} |
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.
정렬이라면
view
의 로직이기에init
에서 제거하고, 생성자를 통해lottoNumber
를 상태로 정의하는건 어떨까요?