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

4단계 - 로또(수동) #589

Open
wants to merge 13 commits into
base: 0923kdh
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@
* 구매한 n장의 로또에 대해 당첨 통계를 관리할 수 있다.
* 보너스 볼을 입력을 수 있다.
* 보너스 볼을 포함하여 당첨 통계를 낼 수 있다.
* 수동으로 로또 번호를 입력받을 수 있다.
19 changes: 11 additions & 8 deletions src/main/kotlin/lotto/LottoApplication.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
package lotto

import lotto.domain.LottoMachine
import lotto.domain.LottoTicketBundle
import lotto.domain.StatisticalResultExtractor
import lotto.domain.WinningNumbers
import lotto.domain.strategy.LottoAutoGenerateStrategy
import lotto.domain.strategy.LottoManualGenerateStrategy
import lotto.view.InputView
import lotto.view.OutputView

fun main() {
val ticketAmount = InputView.getPurchaseAmount()
val lottoTicketBundle = LottoTicketBundle(ticketAmount).lottoTickets
InputView.getNumberOfPurchases(lottoTicketBundle.size)
val winningNumber = InputView.getWinningNumber()
val lottoWinning = WinningNumberExtractor.process(
lottoTicketBundle, WinningNumbers(winningNumber.first, winningNumber.second)
)
OutputView.printOutput(StatisticalResultExtractor(lottoWinning), lottoTicketBundle.size)
val manualTicketCount = InputView.getManualTicketCount()
val ticketCount = LottoMachine.calculateTicketCount(ticketAmount, manualTicketCount)
InputView.printManualTicketNumber(ticketCount.manualTicketCount)
val lottoTicketBundle = LottoTicketBundle(ticketCount, listOf(LottoAutoGenerateStrategy(), LottoManualGenerateStrategy()))
InputView.printNumberOfPurchases(ticketCount)
val winningBallResult = InputView.getWinningBalls()
val lottoWinningResult = LottoMachine.process(lottoTicketBundle, winningBallResult)
OutputView.printOutput(StatisticalResultExtractor(lottoWinningResult), lottoTicketBundle.lottoTickets.size)
}
32 changes: 0 additions & 32 deletions src/main/kotlin/lotto/WinningNumberExtractor.kt

This file was deleted.

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

import lotto.common.LottoTicketPolicy

object LottoMachine {
fun calculateTicketCount(amount: Int, manualTicketCount: Int): LottoTicketCount {
val autoTicketCount = (amount / LottoTicketPolicy.PRICE) - manualTicketCount

return LottoTicketCount(autoTicketCount, manualTicketCount)
}

fun process(lottoTicketBundle: LottoTicketBundle, winningBallResult: WinningBallResult): LottoWinningResult {
val resultMap = mutableMapOf<LottoTicketResult, Int>()
lottoTicketBundle.lottoTickets.groupingBy { ticket ->
val intersectNumbers = ticket.intersect(winningBallResult.winningBalls)
val isBonusBallMatched = winningBallResult.bonusBall in ticket
LottoTicketResult(intersectNumbers.size, isBonusBallMatched)
}.eachCountTo(resultMap)

return LottoWinningResult(resultMap.toSortedMap(getTicketResultComparator()))
Comment on lines +13 to +20

Choose a reason for hiding this comment

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

Suggested change
val resultMap = mutableMapOf<LottoTicketResult, Int>()
lottoTicketBundle.lottoTickets.groupingBy { ticket ->
val intersectNumbers = ticket.intersect(winningBallResult.winningBalls)
val isBonusBallMatched = winningBallResult.bonusBall in ticket
LottoTicketResult(intersectNumbers.size, isBonusBallMatched)
}.eachCountTo(resultMap)
return LottoWinningResult(resultMap.toSortedMap(getTicketResultComparator()))
return lottoTicketBundle.lottoTickets.groupingBy {
val intersectNumbers = it.numbers.intersect(winningBallResult.winningBalls)
val isBonusBallMatched = winningBallResult.bonusBall in it.numbers
LottoTicketResult(intersectNumbers.size, isBonusBallMatched)
}
.eachCount()
.toSortedMap(getTicketResultComparator())
.run {
LottoWinningResult(this)
}

이렇게도 표현이 가능해보이네요~ 😊

그리고 LottoTicketResult를 만드는 부분에 대해서 LottoMachine에서 모두 해결하는 것보다는, LottoTicketResult에게 LottoTicketBundle과 WinningBallResult를 넘겨서 좀 더 객체 협력을 만드는 게 좋지 않을까요?

}

private fun getTicketResultComparator(): Comparator<LottoTicketResult> =
Comparator.comparing(LottoTicketResult::matchCount)
.thenComparing(LottoTicketResult::isBonusBallMatched)
}
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

import lotto.common.LottoTicketPolicy.END_NUMBER
import lotto.common.LottoTicketPolicy.START_NUMBER

@JvmInline
value class LottoNumber(
private val number: Int,
) {
init {
require(number in START_NUMBER..END_NUMBER) { "로또 번호는 1~45 사이의 숫자만 가능합니다." }
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
package lotto
package lotto.domain

import lotto.common.LottoTicketPolicy.END_NUMBER
import lotto.common.LottoTicketPolicy.MAX_LOTTO_NUMBER_SIZE
import lotto.common.LottoTicketPolicy.START_NUMBER
import lotto.utils.RandomNumberGenerator

object LottoNumberSelector {
fun select(): Set<Int> {
val lottoNumbers = mutableSetOf<Int>()
fun select(): Set<LottoNumber> {
val lottoNumbers = mutableSetOf<LottoNumber>()
while (lottoNumbers.size < MAX_LOTTO_NUMBER_SIZE) {
lottoNumbers.add(generateRandomNumber())
lottoNumbers.add(generateLottoNumber())
}

return lottoNumbers
}

private fun generateRandomNumber() = RandomNumberGenerator.generate(START_NUMBER..END_NUMBER)
private fun generateLottoNumber() = LottoNumber(RandomNumberGenerator.generate(START_NUMBER..END_NUMBER))
}
6 changes: 2 additions & 4 deletions src/main/kotlin/lotto/domain/LottoTicket.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package lotto.domain

import lotto.LottoNumberSelector

data class LottoTicket(
val numbers: Set<Int> = LottoNumberSelector.select(),
) : Set<Int> by numbers
val numbers: Set<LottoNumber>,
) : Set<LottoNumber> by numbers

Choose a reason for hiding this comment

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

저번 리뷰 때 kotlin delegate 패턴에 대한 단점을 물어봤었죠~

제가 생각하는 delegate 패턴의 단점은 바로 delegate에 대한 대상의 API를 모두 사용할 수 있다라고 생각해요.

일반적인 객체는 객체 캡슐화에 대해서 내부에서는 사용하고 외부에서는 필요한 메서드만 사용할 수 있지만, delegate를 해버리는 순간 의미없는, 사용하지 않는 메서드까지 모두 외부에서 사용할 수 있기 때문에 외부에서 내부적인 부분까지 사용할 수 있어 객체간의 협력이 깨질 수도 있다고 생각해요~

19 changes: 14 additions & 5 deletions src/main/kotlin/lotto/domain/LottoTicketBundle.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
package lotto.domain

import lotto.common.LottoTicketPolicy
import lotto.domain.strategy.LottoGenerateStrategy
import lotto.domain.strategy.TicketGenerateType

class LottoTicketBundle(amount: Int) {
private val count = amount / LottoTicketPolicy.PRICE
val lottoTickets: List<LottoTicket> = List(count) {
LottoTicket()
class LottoTicketBundle(
lottoTicketCount: LottoTicketCount,
lottoGenerateStrategies: List<LottoGenerateStrategy>,
) {
private val lottoGenerateStrategyMap = lottoGenerateStrategies.associateBy { it.ticketGenerateType }

val lottoTickets: List<LottoTicket> = List(lottoTicketCount.autoTicketCount) {
val autoGenerateStrategy = lottoGenerateStrategyMap.getValue(TicketGenerateType.AUTO)
autoGenerateStrategy.generate()
} + List(lottoTicketCount.manualTicketCount) {
val manualGenerateStrategy = lottoGenerateStrategyMap.getValue(TicketGenerateType.MANUAL)
manualGenerateStrategy.generate()
Comment on lines +12 to +17

Choose a reason for hiding this comment

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

👍

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

data class LottoTicketCount(
val autoTicketCount: Int,
val manualTicketCount: Int,
)
6 changes: 6 additions & 0 deletions src/main/kotlin/lotto/domain/LottoTicketResult.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package lotto.domain

data class LottoTicketResult(
val matchCount: Int,
val isBonusBallMatched: Boolean,
)
16 changes: 0 additions & 16 deletions src/main/kotlin/lotto/domain/LottoWinning.kt

This file was deleted.

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

data class LottoWinningResult(
val result: Map<LottoTicketResult, Int>,
) {
fun totalAmount(): Long {
return result.entries.sumOf {
WinningAmount.from(it.key.matchCount, it.key.isBonusBallMatched).amount * it.value
}
}
}
9 changes: 4 additions & 5 deletions src/main/kotlin/lotto/domain/StatisticalResultExtractor.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package lotto.domain

import lotto.TicketResult
import lotto.common.LottoTicketPolicy

class StatisticalResultExtractor(

Choose a reason for hiding this comment

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

해당 클래스는 LottoWinningResult 와 합쳐도 되는 부분이라고 생각되네요.

스크린샷 2023-01-01 오후 7 09 58

추가로 해당 부분은 LottoWinningResult 하나만으로도 모두 표현이 가능하지 않을까해요~

현재 의미가 중복되는 클래스가 너무 많아보이네요.

이 부분에 대해서는 충분한 설명이 필요하시다면 DM 부탁드립니다. 같이 이야기해보죠~

private val lottoWinning: LottoWinning,
private val lottoWinningResult: LottoWinningResult,
) {
fun getMatchCount(winningAmount: WinningAmount): Int {
val ticketResult = TicketResult(winningAmount.count, winningAmount.isBonusBallMatched)
return lottoWinning.result[ticketResult] ?: ZERO_COUNT
val lottoTicketResult = LottoTicketResult(winningAmount.count, winningAmount.isBonusBallMatched)
return lottoWinningResult.result[lottoTicketResult] ?: ZERO_COUNT
}

fun getTotalRateOfReturn(ticketCount: Int): Double {
return lottoWinning.totalAmount() / (ticketCount * LottoTicketPolicy.PRICE).toDouble()
return lottoWinningResult.totalAmount() / (ticketCount * LottoTicketPolicy.PRICE).toDouble()
}

companion object {
Expand Down
10 changes: 10 additions & 0 deletions src/main/kotlin/lotto/domain/WinningBallResult.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package lotto.domain

data class WinningBallResult(
val winningBalls: WinningBalls,
val bonusBall: LottoNumber,
) {
init {
require(!winningBalls.contains(bonusBall)) { "보너스 볼은 당첨 번호와 중복될 수 없습니다." }
}
}
9 changes: 9 additions & 0 deletions src/main/kotlin/lotto/domain/WinningBalls.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package lotto.domain

data class WinningBalls(
val balls: Set<LottoNumber>,
) : Set<LottoNumber> by balls {
init {
require(balls.size == 6) { "당첨 번호는 중복이 없어야 합니다." }
}
}
6 changes: 0 additions & 6 deletions src/main/kotlin/lotto/domain/WinningNumbers.kt

This file was deleted.

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

import lotto.domain.LottoNumber
import lotto.domain.LottoNumberSelector
import lotto.domain.LottoTicket

interface LottoGenerateStrategy {
val ticketGenerateType: TicketGenerateType
fun generate(): LottoTicket
}

class LottoAutoGenerateStrategy : LottoGenerateStrategy {
override val ticketGenerateType = TicketGenerateType.AUTO
override fun generate(): LottoTicket {
return LottoTicket(LottoNumberSelector.select())
}
}

class LottoManualGenerateStrategy : LottoGenerateStrategy {
override val ticketGenerateType = TicketGenerateType.MANUAL
override fun generate(): LottoTicket {
val manualLottoNumberStr = readln()

Choose a reason for hiding this comment

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

view에 대한 영역이 아닐까요?

val manualLottoNumber = manualLottoNumberStr.split(", ").map { LottoNumber(it.toInt()) }.toSet()

return LottoTicket(manualLottoNumber)
}
}
6 changes: 6 additions & 0 deletions src/main/kotlin/lotto/domain/strategy/TicketGenerateType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package lotto.domain.strategy

enum class TicketGenerateType {
AUTO,
MANUAL,
}
35 changes: 25 additions & 10 deletions src/main/kotlin/lotto/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package lotto.view

import lotto.domain.LottoNumber
import lotto.domain.LottoTicketCount
import lotto.domain.WinningBallResult
import lotto.domain.WinningBalls

object InputView {
fun getPurchaseAmount(): Int {
println("구매금액을 입력해 주세요.")
Expand All @@ -9,22 +14,32 @@ object InputView {
return amount.toInt()
}

fun getNumberOfPurchases(count: Int) {
println("$count 개를 구매했습니다.")
fun getManualTicketCount(): Int {
println("수동으로 구매할 로또 수를 입력해 주세요.")
val manualTicketCount = readln()

return manualTicketCount.toInt()
}

fun printManualTicketNumber(manualTicketCount: Int) {
when (manualTicketCount) {
0 -> return
else -> println("수동으로 구매할 번호를 입력해 주세요.")
}
}

fun getWinningNumber(): Pair<Set<Int>, Int> {
fun printNumberOfPurchases(lottoTicketCount: LottoTicketCount) {
println("수동으로 ${lottoTicketCount.manualTicketCount}장, 자동으로 ${lottoTicketCount.autoTicketCount}개를 구매했습니다.")
}

fun getWinningBalls(): WinningBallResult {
println("지난 주 당첨 번호를 입력해 주세요.")
val winningNumber = readln()
require(winningNumber.isNotEmpty()) { "당첨 번호를 입력해주세요." }

val numbers = winningNumber.split(", ").map { it.toInt() }.toSet()
require(numbers.size == 6) { " 중복없는 6개의 숫자를 입력해주세요." }

val winningBalls = WinningBalls(winningNumber.split(", ").map { LottoNumber(it.toInt()) }.toSet())
println("보너스 볼을 입력해 주세요.")
val bonusBall = readln().toInt()
require(!numbers.contains(bonusBall)) { "보너스 볼은 당첨 번호와 중복될 수 없습니다." }
val bonusBall = LottoNumber(readln().toInt())

return numbers to bonusBall
return WinningBallResult(winningBalls, bonusBall)
}
}
40 changes: 0 additions & 40 deletions src/test/kotlin/lotto/WinningNumberExtractorTest.kt

This file was deleted.

Loading