-
Notifications
You must be signed in to change notification settings - Fork 322
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
🚀 2단계 - 로또(자동) #532
base: shinseongsu
Are you sure you want to change the base?
🚀 2단계 - 로또(자동) #532
Changes from all commits
f5788f4
a1b0b77
c6d4d03
4b6e82e
15189a9
5bdf523
6eb1b81
5bfafe9
8ea40e8
2305dba
d7339b5
6e0e6bb
9bb1d43
aa8edd4
f21fa40
4cb2614
b952027
dfcee09
b0581b2
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 |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package calculator.domain | ||
|
||
object StringConvert { | ||
|
||
fun toInt(input: String): Int { | ||
val result = input.toInt() | ||
if (result < 0) { | ||
throw RuntimeException("음수는 입력할 수 없습니다.") | ||
} | ||
return result | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package lotto | ||
|
||
import lotto.application.LottoStatisticsService | ||
import lotto.controller.LottoController | ||
|
||
fun main() { | ||
val controller = LottoController(LottoStatisticsService()) | ||
controller.start() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package lotto.application | ||
|
||
import lotto.domain.Lotto | ||
import lotto.domain.LottoPrize | ||
import lotto.domain.LottoStatisticsResult | ||
import lotto.domain.LottoStatisticsTotal | ||
import lotto.domain.LottoWinner | ||
import lotto.domain.Reward | ||
|
||
class LottoStatisticsService { | ||
|
||
fun statistics(luckNumberList: List<Int>, lottoList: List<Lotto>, inputPayment: Int): LottoStatisticsTotal { | ||
val winLottoList = LottoWinner(luckNumberList, lottoList).findWinLottoList() | ||
|
||
val winLottoStatisticsResult = winLottoStatistics(winLottoList) | ||
val lottoPrize = LottoPrize(winLottoList.map { it.prizeMoney }) | ||
return LottoStatisticsTotal( | ||
totalRate = lottoPrize.totalRate(inputPayment), | ||
winLottoStatisticsResult = winLottoStatisticsResult | ||
) | ||
} | ||
|
||
private fun winLottoStatistics(winLottoList: List<Reward>): List<LottoStatisticsResult> { | ||
val hitCountMap = winLottoList.groupBy { winLottoPrize: Reward -> winLottoPrize.hitCount } | ||
|
||
return Reward.values().map { | ||
LottoStatisticsResult( | ||
winLottoPrize = it, | ||
winLottoCount = hitCountMap.getOrDefault(it.hitCount, emptyList()).size | ||
) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package lotto.controller | ||
|
||
import lotto.application.LottoStatisticsService | ||
import lotto.domain.LottoGenerator | ||
import lotto.domain.LottoShop | ||
import lotto.domain.RandomNumberGenerator | ||
import lotto.view.InputView | ||
import lotto.view.ResultView | ||
|
||
class LottoController( | ||
private val lottoStatisticsService: LottoStatisticsService | ||
) { | ||
|
||
private val lottoShop = LottoShop(LottoGenerator(RandomNumberGenerator())) | ||
|
||
fun start() { | ||
val inputPayment = InputView.inputPayment() | ||
val lottoList = lottoShop.buy(inputPayment) | ||
Comment on lines
+17
to
+18
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.
|
||
ResultView.printLotto(lottoList) | ||
|
||
val luckNumbers = InputView.inputLuckyNumbers() | ||
val statistics = lottoStatisticsService.statistics(luckNumbers, lottoList, inputPayment) | ||
Comment on lines
+21
to
+22
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. 입력된 당첨 번호가 1~45 사이 숫자가 아닐수도 있겠네요 😄 |
||
ResultView.printLottoStatistics(statistics) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package lotto.domain | ||
|
||
class Lotto( | ||
val numbers: List<Int> | ||
) { | ||
|
||
fun countHitNumbers(luckNumberList: List<Int>): Int { | ||
return numbers.count { number -> luckNumberList.contains(number) } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package lotto.domain | ||
|
||
class LottoGenerator( | ||
private val numberGenerator: NumberGenerator | ||
) { | ||
|
||
fun generate(size: Int): List<Lotto> { | ||
return List(size) { Lotto(numberGenerator.generate()) } | ||
} | ||
Comment on lines
+7
to
+9
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,18 @@ | ||
package lotto.domain | ||
|
||
import lotto.util.NumberUtil | ||
|
||
class LottoPrize( | ||
prizeList: List<Int> | ||
) { | ||
private val totalPrize: Int = prizeList.sum() | ||
|
||
fun totalRate(inputPayment: Int): Double { | ||
val totalRate = totalPrize.toDouble() / inputPayment.toDouble() | ||
return NumberUtil.floor(totalRate, EARING_RATE_DECIMAL_PLACE) | ||
} | ||
|
||
companion object { | ||
private const val EARING_RATE_DECIMAL_PLACE = 2 | ||
} | ||
Comment on lines
+12
to
+17
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,19 @@ | ||
package lotto.domain | ||
|
||
class LottoShop( | ||
private val lottoGenerator: LottoGenerator | ||
) { | ||
|
||
fun buy(inputPayment: Int): List<Lotto> { | ||
val lottoCount = calculateLottoCount(inputPayment) | ||
return lottoGenerator.generate(lottoCount) | ||
} | ||
|
||
private fun calculateLottoCount(payment: Int): Int { | ||
return payment / LOTTO_PRICE | ||
} | ||
|
||
companion object { | ||
private const val LOTTO_PRICE = 1000 | ||
} | ||
Comment on lines
+3
to
+18
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,6 @@ | ||
package lotto.domain | ||
|
||
data class LottoStatisticsResult( | ||
val winLottoPrize: Reward, | ||
val winLottoCount: Int | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package lotto.domain | ||
|
||
data class LottoStatisticsTotal( | ||
val totalRate: Double, | ||
val winLottoStatisticsResult: List<LottoStatisticsResult> | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package lotto.domain | ||
|
||
class LottoWinner( | ||
val luckNumberList: List<Int>, | ||
val lottoList: List<Lotto> | ||
) { | ||
|
||
fun findWinLottoList(): List<Reward> { | ||
return lottoList | ||
.map { it.countHitNumbers(luckNumberList) } | ||
.filter { hasPrize(it) } | ||
.map { Reward.from(it) } | ||
} | ||
|
||
private fun hasPrize(count: Int) = count >= Reward.MINIMUM_HIT_COUNT | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
package lotto.domain | ||
|
||
interface NumberGenerator { | ||
fun generate(): List<Int> | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package lotto.domain | ||
|
||
class RandomNumberGenerator : NumberGenerator { | ||
override fun generate(): List<Int> { | ||
val range = LOTTO_START_NUMBER..LOTTO_END_NUMBER | ||
val shuffled = range.shuffled() | ||
return shuffled.subList(0, LOTTO_NUMBERS_SIZE).sorted() | ||
} | ||
Comment on lines
+4
to
+8
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. 1~45 사이 6개 난수 발생로직 깔끔하네요 👍 👍 |
||
|
||
companion object { | ||
const val LOTTO_START_NUMBER = 1 | ||
const val LOTTO_END_NUMBER = 45 | ||
const val LOTTO_NUMBERS_SIZE = 6 | ||
} | ||
Comment on lines
+10
to
+14
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,20 @@ | ||
package lotto.domain | ||
|
||
enum class Reward( | ||
val hitCount: Int, | ||
val prizeMoney: Int | ||
) { | ||
|
||
FOURTH(3, 5_000), | ||
THIRD(4, 50_000), | ||
SECOND(5, 1_5000_000), | ||
FIRST(6, 2_000_000_000); | ||
|
||
companion object { | ||
val MINIMUM_HIT_COUNT: Int = values().minOf { it.hitCount } | ||
|
||
fun from(hitCount: Int): Reward { | ||
return values().first { it.hitCount == hitCount } | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package lotto.util | ||
|
||
import kotlin.math.floor | ||
import kotlin.math.pow | ||
|
||
object NumberUtil { | ||
|
||
fun floor(number: Double, decimalPlace: Int): Double { | ||
if (decimalPlace == -1) { | ||
return number | ||
} | ||
|
||
val pow = 10.0.pow(decimalPlace.toDouble()) | ||
val floor = floor(number * pow) | ||
return floor / pow | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
package lotto.util | ||
|
||
object StringValidator { | ||
|
||
fun validateNumber(string: String) { | ||
require(string.isNumeric()) { "숫자가 아닙니다." } | ||
} | ||
|
||
private fun String.isNumeric(): Boolean { | ||
return this.toCharArray().all { it in '0'..'9' } | ||
} | ||
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. wow 😮 👍 |
||
|
||
fun validateNotBlank(string: String) { | ||
require(!string.isBlank()) { "값이 비어 있습니다." } | ||
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,42 @@ | ||
package lotto.view | ||
|
||
import lotto.util.StringValidator | ||
|
||
object InputView { | ||
|
||
const val INPUT_PAYMENT_GUIDE = "# 구입금액을 입력해주세요." | ||
const val INPUT_LUCKY_NUMBERS_GUIDE = "# 지난 주 당첨 번호를 입력해 주세요." | ||
Comment on lines
+7
to
+8
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 inputPayment(): Int { | ||
println(INPUT_PAYMENT_GUIDE) | ||
val payment = readln() | ||
validatePaymentInput(payment) | ||
return payment.toInt() | ||
} | ||
|
||
private fun validatePaymentInput(payment: String) { | ||
StringValidator.validateNotBlank(payment) | ||
StringValidator.validateNumber(payment) | ||
} | ||
Comment on lines
+10
to
+20
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 inputLuckyNumbers(): List<Int> { | ||
println(INPUT_LUCKY_NUMBERS_GUIDE) | ||
val luckyNumberString = readln() | ||
val luckyNumbers = splitNumbers(luckyNumberString) | ||
validateLuckyNumbersInput(luckyNumbers) | ||
return convert(luckyNumbers) | ||
} | ||
|
||
private fun splitNumbers(input: String) = input.split(",").map { it.trim() } | ||
|
||
private fun validateLuckyNumbersInput(split: List<String>) { | ||
split.forEach { | ||
StringValidator.validateNotBlank(it) | ||
StringValidator.validateNumber(it) | ||
} | ||
} | ||
|
||
private fun convert(split: List<String>): List<Int> { | ||
return split.map { it.toInt() } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package lotto.view | ||
|
||
import lotto.domain.Lotto | ||
import lotto.domain.LottoStatisticsResult | ||
import lotto.domain.LottoStatisticsTotal | ||
|
||
object ResultView { | ||
|
||
private const val STATISTICS_GUIDE = "당첨 통계" | ||
private const val SPLIT_LINE = "---------" | ||
|
||
fun printLotto(lottoList: List<Lotto>) { | ||
println("${lottoList.size}개를 구매했습니다.") | ||
lottoList.forEach { | ||
println(it.numbers) | ||
} | ||
println() | ||
} | ||
|
||
fun printLottoStatistics(statisticsResult: LottoStatisticsTotal) { | ||
println() | ||
println(STATISTICS_GUIDE) | ||
println(SPLIT_LINE) | ||
printWinStatisticsResult(statisticsResult.winLottoStatisticsResult) | ||
printEarningRate(statisticsResult.totalRate) | ||
} | ||
|
||
private fun printWinStatisticsResult(winLottoStatisticsResult: List<LottoStatisticsResult>) { | ||
winLottoStatisticsResult.forEach { | ||
val hitCount = it.winLottoPrize.hitCount | ||
val prizeMoney = it.winLottoPrize.prizeMoney | ||
val winLottoCount = it.winLottoCount | ||
println("${hitCount}개 일치 (${prizeMoney}원) - ${winLottoCount}개") | ||
} | ||
Comment on lines
+29
to
+34
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 printEarningRate(earningRate: Double) { | ||
print("총 수익률은 ${earningRate}입니다. ") | ||
if (earningRate > 1) { | ||
println("(기준이 1이기 때문에 이익입니다.)") | ||
return | ||
} | ||
println("(기준이 1이기 때문에 손해입니다.)") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package lotto.application | ||
|
||
import io.kotest.core.spec.style.FreeSpec | ||
import io.kotest.matchers.shouldBe | ||
import lotto.domain.Lotto | ||
|
||
class LottoStatisticsServiceTest : FreeSpec({ | ||
|
||
val luckyNumber = listOf(1, 2, 3, 4, 5, 6) | ||
val lottoList = listOf( | ||
Lotto(listOf(1, 2, 3, 4, 5, 6)), | ||
Lotto(listOf(10, 11, 12, 13, 14, 15)) | ||
) | ||
|
||
"statistics" - { | ||
|
||
"수익률과 당첨 상금을 반환한다." { | ||
val lottoStatisticsService = LottoStatisticsService() | ||
|
||
val statistics = lottoStatisticsService.statistics(luckyNumber, lottoList, 2000) | ||
|
||
statistics.totalRate shouldBe 1000000.0 | ||
} | ||
} | ||
}) |
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.
LottoController 는 전반적으로 로또 프로그램의 흐름을 관제하는 객체로 보이는데요,
로또 통계 서비스 객체만 별개로 생성자 주입을 시켜주신 이유가 있을까요?
제가 로직을 이해하기엔 로또 통계 서비스가 교체대상이 되진 않을 것 같은데, 다른 의도로 주입하신걸까 궁금해서 질문드립니다!