Skip to content
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

🚀 3단계 - 로또(2등) #1141

Open
wants to merge 12 commits into
base: sjjeong
Choose a base branch
from
26 changes: 0 additions & 26 deletions src/main/kotlin/lotto/Lotto.kt

This file was deleted.

12 changes: 0 additions & 12 deletions src/main/kotlin/lotto/LottoNumber.kt

This file was deleted.

31 changes: 0 additions & 31 deletions src/main/kotlin/lotto/LottoResult.kt

This file was deleted.

7 changes: 5 additions & 2 deletions src/main/kotlin/lotto/LottoVendingMachine.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package lotto

import lotto.domain.Lotto
import lotto.domain.Lottos

object LottoVendingMachine {
private const val LOTTO_PRICE = 1000

fun buyLotto(money: Int?): List<Lotto> {
fun buyLotto(money: Int?): Lottos {
requireNotNull(money) {
"구매 금액은 null이 아니어야 함"
}
require(money >= LOTTO_PRICE) {
"구매 금액은 1000 보다 커야 함"
}

return List(money / LOTTO_PRICE) { Lotto() }
return Lottos(List(money / LOTTO_PRICE) { Lotto() })
}
}
Comment on lines 6 to 19
Copy link
Member

Choose a reason for hiding this comment

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

외부에서 Lottos를 직접 생성해주지 말고, Lottos 스스로 생성할 수 있도록 해보면 어떨까요?

이 과정에서 팩토리 함수나 부 생성자를 사용해볼 수 있겠네요.

다음 글이 도움되길 바랍니다!
https://kt.academy/article/ek-factory-functions

17 changes: 0 additions & 17 deletions src/main/kotlin/lotto/Prize.kt

This file was deleted.

13 changes: 7 additions & 6 deletions src/main/kotlin/lotto/application/LottoApplication.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package lotto.application

import lotto.Lotto
import lotto.LottoResult
import lotto.domain.Lotto
import lotto.domain.LottoResult
import lotto.LottoVendingMachine
import lotto.domain.LottoNumber
import lotto.view.InputView
import lotto.view.OutputView

Expand All @@ -12,17 +13,17 @@ class LottoApplication(
) {

fun run() {
outputView.showInputMoneyMessage()
val money = inputView.inputMoney()
val lotto = LottoVendingMachine.buyLotto(money)
outputView.showLottoCount(lotto)
outputView.showLotto(lotto)

outputView.showInputWinningNumbersMessage()
val winningNumbers: List<Int> = inputView.inputWinningNumbers()
val winningLotto: Lotto = inputView.inputWinningNumbers()
val bonusNumber: LottoNumber = inputView.inputBonusNumber()

val lottoResult= LottoResult.makeLottoResult(
winningLotto = Lotto(*winningNumbers.toIntArray()),
winningLotto = winningLotto,
bonusLottoNumber = bonusNumber,
lottos = lotto,
)
outputView.showResult(lottoResult, money)
Expand Down
22 changes: 22 additions & 0 deletions src/main/kotlin/lotto/domain/Lotto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package lotto.domain

class Lotto private constructor(val lottoNumbers: Set<LottoNumber>) {

init {
val lottoNumbersSize = lottoNumbers.size
require(lottoNumbersSize == LOTTO_SIZE)
}

constructor() : this(LottoPreset.shuffled().take(6).sortedBy { it.number }.toSet())

constructor(vararg number: Int) : this(number.map { LottoNumber(it) }.toSet())
Copy link
Member

Choose a reason for hiding this comment

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

saso1

이러한 구문을 reference로 넘길 수 있습니다.

Suggested change
constructor(vararg number: Int) : this(number.map { LottoNumber(it) }.toSet())
constructor(vararg number: Int) : this(number.map(::LottoNumber).toSet())

lambda로 작성하는 것과 어떤 차이가 있는지 참고할 수 있는 글을 소개해드릴게요 🙂
https://proandroiddev.com/kotlin-lambda-vs-method-reference-fdbd175f6845

참고로 저는 가능한 곳에서는 꼭 함수 reference를 활용하는 것을 지향합니다. lambda로 가져온 데이터를 별도로 조작하지 않고 그대로 함수 시그니처로 넘긴다는 것을 드러낼 수 있기 때문입니다.


fun countMatch(winningLotto: Lotto): Int {
return lottoNumbers.intersect(winningLotto.lottoNumbers).count()
Copy link
Member

Choose a reason for hiding this comment

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

saso2

https://kotlinlang.org/docs/coding-conventions.html#wrap-chained-calls
코틀린 공식 문서에 따르면 함수 체인의 경우 아래와 같이 체인별 개행을 해주어야 합니다.
(일반적으로 체인의 첫 번째 호출에는 개행이 있어야 하지만 개행하지 않는 편이 더 합리적이라면 생략해도 괜찮습니다)

Suggested change
return lottoNumbers.intersect(winningLotto.lottoNumbers).count()
return lottoNumbers
.intersect(winningLotto.lottoNumbers)
.count()

}

companion object {
private val LottoPreset = List(LottoNumber.END_LOTTO_NUMBER) { LottoNumber(it + 1) }
Copy link
Member

Choose a reason for hiding this comment

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

여러 번 인스턴스화하지 않아도 되는 객체에 대해 고려해주셨네요 👏

private const val LOTTO_SIZE = 6
}
}
13 changes: 13 additions & 0 deletions src/main/kotlin/lotto/domain/LottoNumber.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package lotto.domain

@JvmInline
value class LottoNumber(val number: Int) {
init {
require(number in START_LOTTO_NUMBER..END_LOTTO_NUMBER)
}

companion object {
const val START_LOTTO_NUMBER = 1
const val END_LOTTO_NUMBER = 45
}
}
Comment on lines +3 to +13
Copy link
Member

Choose a reason for hiding this comment

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

LottoNumber 객체가 매번 생성될 때마다 1~45까지의 Range 객체가 매번 생성되어야 할까요?

43 changes: 43 additions & 0 deletions src/main/kotlin/lotto/domain/LottoResult.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package lotto.domain

class LottoResult private constructor(private val result: Map<Prize, Int>) : Map<Prize, Int> by result {
private val totalPrizeMoney: Long
get() {
return result.map {
it.key.money.toLong() * it.value
}.sum()
}

override fun get(key: Prize): Int {
return result[key] ?: 0
}

fun getRateOfReturn(money: Int): Float {
return totalPrizeMoney.toFloat() / money
}

companion object {
fun makeLottoResult(
winningLotto: Lotto,
bonusLottoNumber: LottoNumber,
lottos: Lottos,
): LottoResult {
val result = lottos.lottos.groupingBy { lotto ->
Copy link
Member

Choose a reason for hiding this comment

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

일급 컬렉션 구현을 의도하신 것으로 보이는데요,
Lottos 객체에게도 적절한 역할과 책임을 할당할 수 있을 것 같아요 🙂
lottos.lottos.groupingBy와 같이 데이터를 꺼내어(get) 조작하기보다, 메세지를 던져 객체가 일하도록 해보면 어떨까요?
다음 글이 도움되길 바랍니다.
https://jojoldu.tistory.com/412

val matchCount = lotto.countMatch(winningLotto)
when (matchCount) {
6 -> Prize.FIRST
5 -> if (bonusLottoNumber in lotto.lottoNumbers) {
Prize.SECOND
} else {
Prize.THIRD
}

4 -> Prize.FOURTH
3 -> Prize.FIFTH
else -> Prize.NONE
Copy link
Member

Choose a reason for hiding this comment

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

3단계 힌트를 참고하여 Prize에 메세지만 보내서 어떤 Prize인지 가져올 수 있도록 개선해보면 어떨까요? 외부에서 Prize를 판단하지 말고 메세지만 보내 보세요!

entries를 활용하시는걸 추천드립니다 🙂
https://engineering.teknasyon.com/kotlin-enums-replace-values-with-entries-bbc91caffb2a

}
}.eachCount()
return LottoResult(result)
}
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/lotto/domain/Lottos.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package lotto.domain

class Lottos(lottos: List<Lotto>) {
val lottos: List<Lotto> = lottos.toList()
}
11 changes: 11 additions & 0 deletions src/main/kotlin/lotto/domain/Prize.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package lotto.domain

enum class Prize(val money: Int, val count: Int) {
FIRST(2_000_000_000, 6),
SECOND(30_000_000, 5),
THIRD(1_500_000, 5),
FOURTH(50_000, 4),
FIFTH(5_000, 3),
NONE(0, 0),
;
}
15 changes: 13 additions & 2 deletions src/main/kotlin/lotto/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,24 @@
package lotto.view

import lotto.domain.Lotto
import lotto.domain.LottoNumber

class InputView {
fun inputMoney(): Int {
println("구입금액을 입력해 주세요.")
val input = readlnOrNull()?.toIntOrNull()
requireNotNull(input)
return input
}

fun inputWinningNumbers(): List<Int> {
return readlnOrNull()?.split(",")?.map { it.trim().toInt() } ?: emptyList()
fun inputWinningNumbers(): Lotto {
println("지난 주 당첨 번호를 입력해 주세요.")
val winningNumbers: List<Int> = readlnOrNull()?.split(",")?.map { it.trim().toInt() } ?: emptyList()
return Lotto(*winningNumbers.toIntArray())
}

fun inputBonusNumber(): LottoNumber {
println("보너스 볼을 입력해 주세요.")
return LottoNumber(readlnOrNull()?.toIntOrNull() ?: 0)
}
}
33 changes: 15 additions & 18 deletions src/main/kotlin/lotto/view/OutputView.kt
Original file line number Diff line number Diff line change
@@ -1,36 +1,33 @@
package lotto.view

import lotto.Lotto
import lotto.LottoResult
import lotto.Prize
import lotto.domain.LottoResult
import lotto.domain.Lottos
import lotto.domain.Prize

class OutputView {
fun showInputMoneyMessage() {
println("구입금액을 입력해 주세요.")
fun showLottoCount(lottos: Lottos) {
println("${lottos.lottos.size}개를 구매했습니다.")
}

fun showLottoCount(lotto: List<Lotto>) {
println("${lotto.size}개를 구매했습니다.")
}

fun showLotto(lotto: List<Lotto>) {
lotto.forEach {
println(it)
fun showLotto(lottos: Lottos) {
lottos.lottos.forEach {
println("[${it.lottoNumbers.map { lottoNumber -> lottoNumber.number }.joinToString(", ")}]")
}
}

fun showInputWinningNumbersMessage() {
println("지난 주 당첨 번호를 입력해 주세요.")
}

fun showResult(lottoResult: LottoResult, money: Int) {
println("당첨 통계")
println("---------")
Prize.entries
.filter { it != Prize.NONE }
.sortedByDescending { it.count }
.reversed()
.forEach { prize ->
println("${prize.count}개 일치 (${prize.money}원) - ${lottoResult[prize]}개")
val title = if (prize == Prize.SECOND) {
"${prize.count}개 일치, 보너스 볼 일치 (${prize.money}원)"
} else {
"${prize.count}개 일치 (${prize.money}원)"
}
println("$title - ${lottoResult[prize]}개")
}

println(String.format("총 수익률은 %.2f입니다.", lottoResult.getRateOfReturn(money)))
Expand Down
1 change: 1 addition & 0 deletions src/test/kotlin/lotto/LottoNumberTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package lotto

import io.kotest.assertions.throwables.shouldNotThrow
import io.kotest.assertions.throwables.shouldThrow
import lotto.domain.LottoNumber
import org.junit.jupiter.api.Test

class LottoNumberTest {
Expand Down
Loading