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

Step3: 보너스 볼 추가 #996

Open
wants to merge 5 commits into
base: baek0318
Choose a base branch
from
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
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
## lotto

### 기능 구현 사항
1. 금액에 맞게 로또를 뽑아야한다 (ex. 14000원 14개를 뽑아야함)
2. 1 ~ 45 사이의 숫자를 랜덤으로 6개를 만들어서 로또를 만들어야한다
3. 정답지와 얼마나 일치하는지에 대한 결과를 만들어야 한다
4. 수익률 계산을 해야한다
5. 수익이 얼마가 발생했는지 알아야 한다
- 금액에 맞게 로또를 뽑아야한다 (ex. 14000원 14개를 뽑아야함)
- 1 ~ 45 사이의 숫자를 랜덤으로 6개를 만들어서 로또를 만들어야한다
- 정답지와 얼마나 일치하는지에 대한 결과를 만들어야 한다
- 당첨번호를 맞출때 보너스 번호가 맞는지도 확인해야함
- 수익률 계산을 해야한다
- 수익이 얼마가 발생했는지 알아야 한다
- 당첨번호 5개 일치 + 보너스 번호 일치인 경우에 대해서 수익을 계산해야한다
12 changes: 10 additions & 2 deletions src/main/kotlin/lotto/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package lotto

import lotto.domain.LottoAnswer
import lotto.domain.LottoService
import lotto.domain.MatchCount
import lotto.view.InputView
import lotto.view.OutputView

Expand All @@ -11,8 +12,15 @@ fun main() {
OutputView.outputBuyResult(service.lottoCount, service.lotteries)

val answer = InputView.getLottoAnswer()
val lottoAnswer = LottoAnswer(answer)
val earningStrategy = mapOf(3 to 5000, 4 to 50000, 5 to 1500000, 6 to 2000000000)
val bonusNumber = InputView.getBonusNumber()
val lottoAnswer = LottoAnswer(answer, bonusNumber)
val earningStrategy = mapOf(
MatchCount.THREE to 5000,
MatchCount.FOUR to 50000,
MatchCount.FIVE to 1500000,
MatchCount.FIVE_WITH_BONUS to 30000000,
MatchCount.SIX to 2000000000
)
Comment on lines +17 to +23
Copy link

Choose a reason for hiding this comment

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

이렇게 매핑을 하기보다 힌트에도 있듯이 Enum으로 관리해 보면 어떨까요?
그리고 지금처럼 컨트롤러에 이 로직이 있는 것이 적합한지도 고민해 보시면 좋을 것 같아요.

val result = service.play(lottoAnswer, earningStrategy)

OutputView.outputLottoResult(result, earningStrategy)
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/lotto/domain/Earning.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ package lotto.domain

@JvmInline
value class Earning(
private val strategy: Map<Int, Int>
private val strategy: Map<MatchCount, Int>
) {

fun calculate(result: Map<Int, Int>): Int = result
fun calculate(result: Map<MatchCount, Int>): Int = result
.filter { strategy.containsKey(it.key) }
.map { (strategy[it.key]?.times(it.value) ?: 0) }
.reduceOrNull { acc, i -> acc + i } ?: 0
Expand Down
20 changes: 13 additions & 7 deletions src/main/kotlin/lotto/domain/LottoAnswer.kt
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
package lotto.domain

@JvmInline
value class LottoAnswer(
private val answer: List<Int>
data class LottoAnswer(
Copy link

Choose a reason for hiding this comment

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

LottoAnswer라고 하니 어떤 역할인지 떠오르지 않았습니다.
저희는 이 도메인에 대해 지속적으로 고민하니 사용자에게 입력받은 값이라는 것을 압니다.
하지만 당첨번호를 의미하는 말로 바꾸면 좀 더 명확할 것 같다는 생각이 드네요.

private val answer: List<Int>,
private val bonusNumber: Int
Comment on lines +4 to +5
Copy link

Choose a reason for hiding this comment

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

입력받은 값(당첨번호)은 이렇게 별도로 관리할 필요는 없을 것 같습니다.
현재 Lotto 클래스가 대체할 수 있지 않을까요?
당첨번호나 로또나 똑같이 6개의 번호를 가지는 동일한 형태니까요.
현재 객체들의 역할에 대해 조금 더 고민하시면서 변경해 보면 좋을 것 같아요.

) {

fun match(inputLottos: List<Lotto>): Map<Int, Int> {
fun match(inputLottos: List<Lotto>): Map<MatchCount, Int> {
return inputLottos
.map { innerMatch(it) }
.groupingBy { it }
.eachCount()
}

private fun innerMatch(inputLotto: Lotto): Int {
return inputLotto.numbers.count { outer -> isAnswerNumberMatch(outer) }
private fun innerMatch(inputLotto: Lotto): MatchCount {
val count = inputLotto.numbers.count { outer -> isAnswerNumberMatch(outer) }
val isBonusMatch = isBonusMatch(inputLotto)
return MatchCount.of(count, isBonusMatch)
}

private fun isBonusMatch(inputLotto: Lotto): Boolean {
return inputLotto.numbers.find { bonusNumber == it } != null
}

private fun isAnswerNumberMatch(outer: Int): Boolean {
return answer.find { outer == it } != null
}

companion object {
fun create(answer: List<Int>) = LottoAnswer(answer)
fun create(answer: List<Int>, bonusNumber: Int) = LottoAnswer(answer, bonusNumber)
}
}
2 changes: 1 addition & 1 deletion src/main/kotlin/lotto/domain/LottoResult.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ import java.math.BigDecimal

data class LottoResult(
val earningRate: BigDecimal,
val earnResult: Map<Int, Int>
val earnResult: Map<MatchCount, Int>
)
2 changes: 1 addition & 1 deletion src/main/kotlin/lotto/domain/LottoService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class LottoService(
) {
val lottoCount = lotteries.size

fun play(answer: LottoAnswer, earningStrategy: Map<Int, Int>): LottoResult {
fun play(answer: LottoAnswer, earningStrategy: Map<MatchCount, Int>): LottoResult {
val result = answer.match(lotteries)
val earning = Earning(earningStrategy).calculate(result)
val earningRate = EarningRate { earningRate ->
Expand Down
25 changes: 25 additions & 0 deletions src/main/kotlin/lotto/domain/MatchCount.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package lotto.domain

enum class MatchCount(
val matchCount: Int,
val isMatchBonus: Boolean
) {
ZERO(0, false),
ONE(1, false),
TWO(2, false),
THREE(3, false),
FOUR(4, false),
FIVE(5, false),
FIVE_WITH_BONUS(5, true),
SIX(6, false);

companion object {
fun of(count: Int, isMatchBonus: Boolean): MatchCount {
val matchCount = values().find { it.matchCount == count } ?: throw IllegalArgumentException("해당하는 매치 카운트가 없습니다.")
if (matchCount == FIVE && isMatchBonus) {
return FIVE_WITH_BONUS
}
return matchCount
}
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/lotto/view/InputView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ object InputView {
println("지난 주 당첨 번호를 입력해 주세요.")
return readln().split(", ").map { it.toInt() }
}

fun getBonusNumber(): Int {
println("보너스 볼을 입력해 주세요.")
return readln().toInt()
}
}
16 changes: 12 additions & 4 deletions src/main/kotlin/lotto/view/OutputView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,33 @@ package lotto.view

import lotto.domain.Lotto
import lotto.domain.LottoResult
import lotto.domain.MatchCount

object OutputView {

fun outputBuyResult(lottoCount: Int, lotteries: List<Lotto>) {
println("${lottoCount}개를 구매했습니다.")
lotteries.forEach {
println(it.numbers)
println(it.numbers.sorted())
Copy link

Choose a reason for hiding this comment

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

로또 번호에 대한 중복 검사도 없는 것 같습니다.
확인 부탁드려요.

}
println()
}

fun outputLottoResult(result: LottoResult, strategy: Map<Int, Int>) {
fun outputLottoResult(result: LottoResult, strategy: Map<MatchCount, Int>) {
println()
println("당첨 통계")
println("---------")
strategy.entries
.sortedBy { it.key }
.map { "${it.key}개 일치 (${it.value}원)- ${result.earnResult[it.key] ?: 0}개" }
.sortedBy { it.key.matchCount }
.map { outputLottoResultSeperate(it.key, it.value, result.earnResult) }
.forEach { println(it) }
println("총 수익률은 ${result.earningRate}입니다.")
}

private fun outputLottoResultSeperate(matchCount: MatchCount, amount: Int, earnResult: Map<MatchCount, Int>): String {
Copy link

Choose a reason for hiding this comment

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

함수명은 보통 동사를 앞에 두죠.

if (matchCount.isMatchBonus) {
return "${matchCount.matchCount}개 일치, 보너스 볼 일치(${amount}원)- ${earnResult[matchCount] ?: 0}개"
}
return "${matchCount.matchCount}개 일치 (${amount}원)- ${earnResult[matchCount] ?: 0}개"
}
}
15 changes: 12 additions & 3 deletions src/test/kotlin/lotto/EarningTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,23 @@ import io.kotest.data.Row2
import io.kotest.data.forAll
import io.kotest.matchers.shouldBe
import lotto.domain.Earning
import lotto.domain.MatchCount

class EarningTest : StringSpec({

"정답 결과에 따라서 알맞은 수익이 발생해야한다" {
val earning = Earning(mapOf(3 to 5000, 4 to 50000, 5 to 1500000, 6 to 2000000000))
val earning = Earning(
mapOf(
MatchCount.THREE to 5000,
MatchCount.FOUR to 50000,
MatchCount.FIVE to 1500000,
MatchCount.FIVE_WITH_BONUS to 30000000,
MatchCount.SIX to 2000000000
)
)
forAll(
Row2(mapOf(3 to 1), 5000),
Row2(mapOf(3 to 1, 4 to 2), 105000)
Row2(mapOf(MatchCount.THREE to 1), 5000),
Row2(mapOf(MatchCount.THREE to 1, MatchCount.FOUR to 2), 105000)
) { lottoResult, actual ->
val result = earning.calculate(lottoResult)
result shouldBe actual
Expand Down
13 changes: 11 additions & 2 deletions src/test/kotlin/lotto/LottoAnswerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,21 @@ import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe
import lotto.domain.Lotto
import lotto.domain.LottoAnswer
import lotto.domain.MatchCount

class LottoAnswerTest : StringSpec({

"정답지와 3개가 일치하는 로또가 1개만 존재할 경우 { 3 : 1 } 결과가 나와야 한다" {
val answer = LottoAnswer.create(listOf(1, 2, 3, 4, 5, 6))
val bonusNumber = 0
val answer = LottoAnswer.create(listOf(1, 2, 3, 4, 5, 6), bonusNumber)
val inputLotto = Lotto.create(TestNumberGenerator)
answer.match(listOf(inputLotto)) shouldBe mapOf(3 to 1)
answer.match(listOf(inputLotto)) shouldBe mapOf(MatchCount.THREE to 1)
}

"정답지와 5개가 일치하고 보너스 번호가 일치하는 로또가 존재할 경우 { FIVE_WITH_BONUS : 1} 결과가 나와야 한다" {
val bonusNumber = 7
val answer = LottoAnswer.create(listOf(1, 2, 3, 4, 5, 6), bonusNumber)
val inputLotto = Lotto.create(TestNumberGeneratorFive)
answer.match(listOf(inputLotto)) shouldBe mapOf(MatchCount.FIVE_WITH_BONUS to 1)
}
})
7 changes: 7 additions & 0 deletions src/test/kotlin/lotto/TestNumberGeneratorFive.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package lotto

import lotto.domain.strategy.NumberGenerator

object TestNumberGeneratorFive : NumberGenerator {
override fun generate(size: Int): List<Int> = listOf(1, 2, 3, 4, 5, 7)
}