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] 로또 제출합니다. #1132

Open
wants to merge 4 commits into
base: 2chang5
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
7 changes: 5 additions & 2 deletions src/main/kotlin/lotto/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ fun main() {
val winningNumbers = inputView.getWinningNumbers() ?: return
val winningLottoNumbers = winningNumbers.map { LottoNumber.get(it) }

val bonusNumber = inputView.getBonusNumber() ?: return
val bonusLottoNumber = LottoNumber.get(bonusNumber)

resultView.showResultInterface()
resultView.showMatchLottoResult(lottoBunch.getMatchLottoResult(winningLottoNumbers))
resultView.showYield(lottoBunch.getYield(winningLottoNumbers, purchaseAmount))
resultView.showMatchLottoResult(lottoBunch.getMatchLottoResult(winningLottoNumbers, bonusLottoNumber))
resultView.showYield(lottoBunch.getYield(winningLottoNumbers, bonusLottoNumber, purchaseAmount))
}
7 changes: 7 additions & 0 deletions src/main/kotlin/lotto/domain/BonusMatchResult.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package lotto.domain

enum class BonusMatchResult {
MATCH,
NOT_MATCH,
NO_EFFECT,
}
40 changes: 33 additions & 7 deletions src/main/kotlin/lotto/domain/Lotto.kt
Original file line number Diff line number Diff line change
@@ -1,22 +1,48 @@
package lotto.domain

class Lotto(private val lottoNumberGenerator: LottoNumberGenerator) {
var lottoNumbers: Set<LottoNumber> = getByAuto()
var lottoNumbers: Set<LottoNumber>
private set
var bonusNumber: LottoNumber
private set
Comment on lines 3 to +7
Copy link
Member

Choose a reason for hiding this comment

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

우리가 일반적으로 로또를 구매할 때 보너스 번호를 같이 들고 있나요?

https://dhlottery.co.kr/common.do?method=main


init {
getByAuto().run {
lottoNumbers = first
bonusNumber = second
}
}

private fun getByAuto(): Set<LottoNumber> {
lottoNumberGenerator ?: return setOf()
return buildSet { while (size < 6) add(LottoNumber.get(lottoNumberGenerator.generateLottoNumber())) }
private fun getByAuto(): Pair<Set<LottoNumber>, LottoNumber> {
Copy link
Member

Choose a reason for hiding this comment

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

다른 개발자들도 Pair<Set<LottoNumber>, LottoNumber>을 보고 어떤 의미를 갖는 자료형인지 한눈에 이해할 수 있을까요?

val normal = buildSet { while (size < 6) add(LottoNumber.get(lottoNumberGenerator.generateLottoNumber())) }
val bonus = getBonus(normal)
return Pair(normal, bonus)
}

fun setLottoByManual(vararg lottoNumber: Int) {
private fun getBonus(normal: Set<LottoNumber>): LottoNumber {
while (true) {
val stepNumber = LottoNumber.get(lottoNumberGenerator.generateLottoNumber())
if (stepNumber !in normal) return stepNumber
}
}

fun setLottoByManual(
Copy link
Member

Choose a reason for hiding this comment

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

저는 개인적으로 private 함수보다는 public 함수를 먼저 위치시키는 것을 선호합니다. 다른 개발자들이 이 클래스의 역할을 파악할 때 public으로 외부에 드러난 함수를 먼저 보고, 그 다음에 숨겨진 함수를 보는 것이 자연스럽다고 느껴질 것 같아요.

(이 내용을 정리한 레퍼런스가 있었는데 못찾겠네요 😂

bonus: Int,
vararg lottoNumber: Int,
) {
require(lottoNumber.size == LOTTO_NUMBER_COUNT) { LOTTO_NUMBER_COUNT_EXCEPTION_MESSAGE }
require(lottoNumber.distinct().size == LOTTO_NUMBER_COUNT) { LOTTO_NUMBER_DISTINCT_MESSAGE }
require(bonus !in lottoNumber) { LOTTO_NUMBER_DISTINCT_MESSAGE }
lottoNumbers = lottoNumber.map { LottoNumber.get(it) }.toSet()
bonusNumber = LottoNumber.get(bonus)
}

fun match(winningNumber: List<LottoNumber>): MatchingResult? =
MatchingResult.fromMatchNumber(lottoNumbers.intersect(winningNumber).size)
fun match(
winningNumber: List<LottoNumber>,
bonusNumber: LottoNumber,
): MatchingResult? {
return MatchingResult.getResult(lottoNumbers.intersect(winningNumber).size, bonusNumber == this.bonusNumber)
}
Comment on lines +40 to +45
Copy link
Member

Choose a reason for hiding this comment

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

데이터를 꺼내어(get) 조작하지 않고, 메세지를 던져 객체가 일하도록 구조를 잘 만들어주셨네요!


companion object {
private const val LOTTO_NUMBER_COUNT = 6
Expand Down
10 changes: 7 additions & 3 deletions src/main/kotlin/lotto/domain/LottoBunch.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,20 @@ class LottoBunch(val value: List<Lotto>) {
lottoNumberGenerator: LottoNumberGenerator = RandomGenerator,
) : this(List(purchaseCount) { Lotto(lottoNumberGenerator) })

fun getMatchLottoResult(winningNumbers: List<LottoNumber>): Map<MatchingResult, Int> {
val matchResults = value.mapNotNull { lotto -> lotto.match(winningNumbers) }
fun getMatchLottoResult(
winningNumbers: List<LottoNumber>,
bonusNumber: LottoNumber,
): Map<MatchingResult, Int> {
val matchResults = value.mapNotNull { lotto -> lotto.match(winningNumbers, bonusNumber) }
return MatchingResult.getMatchLottoResult(matchResults)
}

fun getYield(
winningNumbers: List<LottoNumber>,
bonusNumber: LottoNumber,
purchaseAmount: Int,
): Double {
val matchResults: Map<MatchingResult, Int> = getMatchLottoResult(winningNumbers)
val matchResults: Map<MatchingResult, Int> = getMatchLottoResult(winningNumbers, bonusNumber)
Copy link
Member

Choose a reason for hiding this comment

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

다른 개발자들도 어떤 의미인지 한 눈에 이해할 수 있도록 Kotlin의 named arguments를 활용해보시면 어떨까요~?

val totalPrize =
matchResults.entries.fold(0) { acc, (matchingResult, winCount) ->
acc + (matchingResult.prizeAmount * winCount)
Expand Down
36 changes: 28 additions & 8 deletions src/main/kotlin/lotto/domain/MatchingResult.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
package lotto.domain

enum class MatchingResult(val prizeAmount: Int, val matchNumber: Int) {
MATCHED_THREE(5_000, 3),
MATCHED_FOUR(50_000, 4),
MATCHED_FIVE(1_500_000, 5),
MATCHED_SIX(2_000_000_000, 6), ;
typealias MatchValues = Pair<Int, BonusMatchResult>

enum class MatchingResult(val prizeAmount: Int, val matchNumber: Int, val bonusMatchResult: BonusMatchResult) {
MATCHED_THREE(5_000, 3, BonusMatchResult.NO_EFFECT),
MATCHED_FOUR(50_000, 4, BonusMatchResult.NO_EFFECT),
MATCHED_FIVE(1_500_000, 5, BonusMatchResult.NOT_MATCH),
Copy link
Member

Choose a reason for hiding this comment

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

NOT_MATCH와 NO_EFFECT가 의미있게 구분되어야 하는 이유가 있을까요?

MATCHED_FIVE_WITH_BONUS(30_000_000, 5, BonusMatchResult.MATCH),
MATCHED_SIX(2_000_000_000, 6, BonusMatchResult.NO_EFFECT), ;

companion object {
private const val MATCH_NUMBER_TRANSFER_ERROR_MESSAGE = "로또 결과 이넘값 변환 오류가 발생하였습니다."
private val matchNumberToMatchResultMap = entries.associateBy { it.matchNumber }
private const val RELATED_BONUS_MATCH_NUMBER = 5
Comment on lines +8 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.

5라는 숫자가 이렇게 3번 선언될 필요가 있을까요? 정답은 없을 것 같은데 어떻게 생각하시나요?

private val matchValuesToMatchResultMap =
entries.associateBy { result ->
MatchValues(result.matchNumber, result.bonusMatchResult)
}

fun getResult(
matchNumber: Int,
isBonusMatched: Boolean,
): MatchingResult? {
val bonusMatchResult = getBonusMatchResult(matchNumber, isBonusMatched)
return matchValuesToMatchResultMap[MatchValues(matchNumber, bonusMatchResult)]
}

fun fromMatchNumber(matchNumber: Int): MatchingResult? = matchNumberToMatchResultMap[matchNumber]
private fun getBonusMatchResult(
matchNumber: Int,
isBonusMatched: Boolean,
): BonusMatchResult {
if (matchNumber != RELATED_BONUS_MATCH_NUMBER) return BonusMatchResult.NO_EFFECT
return if (isBonusMatched) BonusMatchResult.MATCH else BonusMatchResult.NOT_MATCH
}

fun getMatchLottoResult(matchResults: List<MatchingResult>): Map<MatchingResult, Int> =
entries.associateWith { matchResult ->
Expand Down
14 changes: 14 additions & 0 deletions src/main/kotlin/lotto/view/InputView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,18 @@ class InputView {
}
return slicedInput.map { it.toInt() }
}

fun getBonusNumber(): Int? {
println("보너스 볼을 입력해 주세요.")
val input = readlnOrNull()
if (input.isNullOrEmpty()) {
println("아무값도 입력되지 않았습니다.")
return null
}
if (input.toIntOrNull() == null) {
println("숫자만 입력 가능합니다.")
return null
}
return input.toInt()
}
}
8 changes: 7 additions & 1 deletion src/main/kotlin/lotto/view/ResultView.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package lotto.view

import lotto.domain.BonusMatchResult
import lotto.domain.LottoBunch
import lotto.domain.MatchingResult

Expand All @@ -21,7 +22,12 @@ class ResultView {

fun showMatchLottoResult(result: Map<MatchingResult, Int>) {
result.forEach { key, value ->
println("${key.matchNumber}개 일치 (${key.prizeAmount}원)- ${value}개")
val bonusResultMessage =
when (key.bonusMatchResult) {
BonusMatchResult.MATCH -> ", 보너스 볼 일치"
BonusMatchResult.NOT_MATCH, BonusMatchResult.NO_EFFECT -> ""
}
println("${key.matchNumber}개 일치$bonusResultMessage (${key.prizeAmount}원)- ${value}개")
}
}

Expand Down
9 changes: 5 additions & 4 deletions src/main/kotlin/lotto/기능목록.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
- [x] 몇등 당첨인지 확인한다.
- [x] 수익률을 계산한다.
- [x] 로또 번호는 1~45사이로 한정한다.
- [ ] 보너스 점수가 있다.


## 뷰
### 입력
- [ ] 구입금액을 입력받는다.
- [ ] 로또 당첨 번호를 입력받는다.
- [x] 구입금액을 입력받는다.
- [x] 로또 당첨 번호를 입력받는다.
### 출력
- [ ] 구매 내역을 출력해준다.(구매 갯수,로또 내역)
- [ ] 결과를 출력해준다.
- [x] 구매 내역을 출력해준다.(구매 갯수,로또 내역)
- [x] 결과를 출력해준다.
55 changes: 37 additions & 18 deletions src/test/kotlin/lotto/domain/LottoBunchTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,48 +8,67 @@ class LottoBunchTest : StringSpec({
val classUnderTest: LottoBunch =
getLottoBunch(
listOf(
listOf(1, 2, 3, 4, 5, 6),
listOf(1, 2, 3, 4, 5, 6),
listOf(1, 2, 3, 4, 5, 6),
listOf(1, 2, 3, 4, 5, 6),
listOf(1, 2, 3, 4, 5, 45),
listOf(1, 2, 3, 4, 5, 45),
listOf(1, 2, 3, 4, 5, 45),
listOf(1, 2, 3, 4, 44, 45),
listOf(1, 2, 3, 4, 44, 45),
listOf(1, 2, 3, 43, 44, 45),
Pair(listOf(1, 2, 3, 4, 5, 6), 30),
Pair(listOf(1, 2, 3, 4, 5, 6), 30),
Pair(listOf(1, 2, 3, 4, 5, 6), 30),
Pair(listOf(1, 2, 3, 4, 5, 6), 30),
Pair(listOf(1, 2, 3, 4, 5, 45), 30),
Pair(listOf(1, 2, 3, 4, 5, 45), 30),
Pair(listOf(1, 2, 3, 4, 5, 45), 30),
Pair(listOf(1, 2, 3, 4, 44, 45), 30),
Pair(listOf(1, 2, 3, 4, 44, 45), 30),
Pair(listOf(1, 2, 3, 43, 44, 45), 30),
Pair(listOf(1, 2, 3, 4, 5, 45), 15),
Pair(listOf(1, 2, 3, 4, 5, 45), 15),
),
)
val winningNumbers = listOf(1, 2, 3, 4, 5, 6).map { LottoNumber.get(it) }
val bonusNumber = LottoNumber.get(15)
val expected =
mapOf(
MatchingResult.MATCHED_THREE to 1,
MatchingResult.MATCHED_FOUR to 2,
MatchingResult.MATCHED_FIVE to 3,
MatchingResult.MATCHED_SIX to 4,
MatchingResult.MATCHED_FIVE_WITH_BONUS to 2,
)
classUnderTest.getMatchLottoResult(winningNumbers) shouldBe expected
classUnderTest.getMatchLottoResult(winningNumbers, bonusNumber) shouldBe expected
}

"수익률을 계산한다." {
val classUnderTest: LottoBunch =
getLottoBunch(
listOf(
listOf(1, 2, 3, 4, 5, 6),
listOf(1, 2, 3, 4, 5, 45),
listOf(1, 2, 3, 4, 44, 45),
listOf(1, 2, 3, 43, 44, 45),
Pair(listOf(1, 2, 3, 4, 5, 6), 30),
Pair(listOf(1, 2, 3, 4, 5, 45), 15),
Pair(listOf(1, 2, 3, 4, 5, 45), 30),
Pair(listOf(1, 2, 3, 4, 44, 45), 30),
Pair(listOf(1, 2, 3, 43, 44, 45), 30),
),
)
val winningNumbers = listOf(1, 2, 3, 4, 5, 6).map { LottoNumber.get(it) }
val bonusNumber = LottoNumber.get(15)
val purchaseAmount = 10000
val totalPrize = MatchingResult.entries.map { it.prizeAmount }.sum()

classUnderTest.getYield(winningNumbers, purchaseAmount) shouldBe totalPrize.toDouble() / purchaseAmount
classUnderTest.getYield(
winningNumbers,
bonusNumber,
purchaseAmount,
) shouldBe totalPrize.toDouble() / purchaseAmount
}
}) {
private companion object {
fun getLottoBunch(numbers: List<List<Int>>): LottoBunch =
LottoBunch(numbers.map { Lotto(RandomGenerator).apply { setLottoByManual(*it.toIntArray()) } })
fun getLottoBunch(numbers: List<Pair<List<Int>, Int>>): LottoBunch =
Copy link
Member

Choose a reason for hiding this comment

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

다른 개발자들도 List<Pair<List<Int>, Int>>을 보고 어떤 의미를 갖는 자료형인지 한눈에 이해할 수 있을까요?

LottoBunch(
numbers.map {
Lotto(RandomGenerator).apply {
setLottoByManual(
it.second,
*it.first.toIntArray(),
)
}
},
)
}
}
46 changes: 26 additions & 20 deletions src/test/kotlin/lotto/domain/LottoTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,14 @@ class LottoTest : StringSpec({
beforeTest {
sequentialNumberGenerator =
object : LottoNumberGenerator {
val lottoNumberRanger = (1..45).toMutableList()

override fun generateLottoNumber(): Int = lottoNumberRanger.removeFirst()
var lottoNumberRanger = (1..45).toMutableList()

override fun generateLottoNumber(): Int {
if (lottoNumberRanger.isEmpty()) {
lottoNumberRanger = (1..45).toMutableList()
}
return lottoNumberRanger.removeFirst()
}
}
}

Expand All @@ -24,14 +29,14 @@ class LottoTest : StringSpec({
}
}

"로또를 직접 초기화할때 중복되지 않은 6개의 숫자를 입력해야한다." {
"로또를 직접 초기화할때 중복되지 않은 6개의 숫자와 1개의 보너스 숫자를 입력해야한다." {
listOf(
listOf(1, 2, 3, 4),
listOf(1, 1, 2, 3, 4, 5),
listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10),
).forAll { numberList ->
Pair(listOf(1, 2, 3, 4), 45),
Pair(listOf(1, 1, 2, 3, 4, 5), 45),
Pair(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), 45),
).forAll { (numberList, bonus) ->
shouldThrowAny {
Lotto(sequentialNumberGenerator).setLottoByManual(*numberList.toIntArray())
Lotto(sequentialNumberGenerator).setLottoByManual(bonus, *numberList.toIntArray())
}
}
}
Expand All @@ -40,18 +45,19 @@ class LottoTest : StringSpec({
Lotto(sequentialNumberGenerator).lottoNumbers.size shouldBe 6
}

"각 로또의 번호를 매칭하여 결과를 도출한다." {
"각 로또의 번호 및 보너스 번호를 매칭하여 결과를 도출한다." {
listOf(
Pair(listOf(45, 44, 43, 42, 41, 40), null),
Pair(listOf(1, 45, 44, 43, 42, 41), null),
Pair(listOf(1, 2, 45, 44, 43, 42), null),
Pair(listOf(1, 2, 3, 45, 44, 43), MatchingResult.MATCHED_THREE),
Pair(listOf(1, 2, 3, 4, 45, 44), MatchingResult.MATCHED_FOUR),
Pair(listOf(1, 2, 3, 4, 5, 45), MatchingResult.MATCHED_FIVE),
Pair(listOf(1, 2, 3, 4, 5, 6), MatchingResult.MATCHED_SIX),
).forAll { (winningNumbers, matchingResult) ->
Lotto(sequentialNumberGenerator).apply { setLottoByManual(1, 2, 3, 4, 5, 6) }
.match(winningNumbers.map { LottoNumber.get(it) }) shouldBe matchingResult
Triple(30, listOf(45, 44, 43, 42, 41, 40), null),
Triple(30, listOf(1, 45, 44, 43, 42, 41), null),
Triple(30, listOf(1, 2, 45, 44, 43, 42), null),
Triple(30, listOf(1, 2, 3, 45, 44, 43), MatchingResult.MATCHED_THREE),
Triple(30, listOf(1, 2, 3, 4, 45, 44), MatchingResult.MATCHED_FOUR),
Triple(30, listOf(1, 2, 3, 4, 5, 45), MatchingResult.MATCHED_FIVE),
Triple(30, listOf(1, 2, 3, 4, 5, 45), MatchingResult.MATCHED_FIVE),
Triple(30, listOf(1, 2, 3, 4, 5, 6), MatchingResult.MATCHED_SIX),
).forAll { (bonusNumber, winningNumbers, matchingResult) ->
Lotto(sequentialNumberGenerator).apply { setLottoByManual(15, 1, 2, 3, 4, 5, 6) }
.match(winningNumbers.map { LottoNumber.get(it) }, LottoNumber.get(bonusNumber)) shouldBe matchingResult
}
}
})