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

Step2, 로또(자동) #1138

Open
wants to merge 5 commits into
base: minsu-lee
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
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,35 @@
- num1, num2 => + 연산
- ExpressionEvaluator : 연산 대상 (숫자)목록을 속성으로 가지고 있고, operator 를 호출하여 **연산된 결과를 반환** 담당
- 연산은 생성자로 전달받을 Operator 로 진행

### 2단계 기능목록
- 로또 구입 금액을 입력 받는다
- 입력 받은 금액으로 구매할 수 있는 로또 개수를 계산한다
- 로또 개수만큼 로또를 발행한다
- 로또는 숫자 6개를 중복 없이 추출한다
- 발행한 로또는 오름차순으로 정렬한다
- 지난 주 당첨 번호를 입력받는다
- 지난 주 당첨 번호를 기준으로 발행한 로또에서 당첨 개수를 계산한다
- 총 수익률을 계산한다


[ Lotto ]
- LottoMachine
- LottoNumbers
- 6개 로또 번호 생성
- Keyboard
- 구입금액 입력
- 지난 주 당첨 번호 입력
- MachineProcess
- LottoPrice
- 로또 구매 가능 개수 계산
- LottoRank
- 당첨 개수 계산
- LottoStatistics
- 총 당첨금액 계산
- 총 수익률 계산
- LottoMonitor
- 로또 구매 개수 출력
- 구매한 로또 출력
- 당첨 통계 출력
- 총 수익률 출력
6 changes: 6 additions & 0 deletions src/main/kotlin/calculator/model/input/DelimiterAndNumbers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package calculator.model.input

data class DelimiterAndNumbers(
val delimiter: String?,
val numbers: String,
)
19 changes: 13 additions & 6 deletions src/main/kotlin/calculator/model/input/InputParser.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package calculator.model.input

object InputParser {
const val NON_NUMERIC_DEFAULT_VALUE = -1
private val DEFAULT_DELIMITERS = arrayOf(",", ":")
private val customDelimiterRegex: Regex by lazy {
Regex("//(.)\n(.*)")
}
private val nonNumericCharacterRegex: Regex by lazy {
Regex("-?\\d+")
}

fun parse(input: String?): List<Int> {
if (input.isNullOrEmpty()) return listOf(0)
Expand All @@ -15,14 +22,14 @@ object InputParser {
return numbers
}

private fun extractDelimiterAndNumbers(input: String): Pair<String?, String> {
val result = Regex("//(.)\n(.*)").find(input)
private fun extractDelimiterAndNumbers(input: String): DelimiterAndNumbers {
val result = customDelimiterRegex.find(input)
return if (result != null) {
val customDelimiter = result.groupValues[1]
val numbers = result.groupValues[2]
customDelimiter to numbers
DelimiterAndNumbers(customDelimiter, numbers)
} else {
null to input
DelimiterAndNumbers(null, input)
}
}

Expand All @@ -41,12 +48,12 @@ object InputParser {

private fun parseNonNegativeNumbers(tokens: List<String>): List<Int> {
return tokens.map { token ->
token.toIntOrNull() ?: -1
token.toIntOrNull() ?: NON_NUMERIC_DEFAULT_VALUE
}
}

private fun extractNonNumericCharacters(input: String): List<String> {
return input.replace(Regex("-?\\d+"), "").trim()
return input.replace(nonNumericCharacterRegex, "").trim()
.map { "$it" }
}
}
2 changes: 1 addition & 1 deletion src/main/kotlin/calculator/model/input/InputValidator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package calculator.model.input
object InputValidator {
fun validateNumbers(numbers: List<Int>) {
numbers.forEach { number ->
if (number < 0) throw IllegalArgumentException("음수는 계산할 수 없습니다.")
if (number == InputParser.NON_NUMERIC_DEFAULT_VALUE) throw IllegalArgumentException("음수는 계산할 수 없습니다.")

Choose a reason for hiding this comment

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

숫자가 -1이 아닌 음수값이 오면 어떻게 될까요?

}
}

Expand Down
17 changes: 17 additions & 0 deletions src/main/kotlin/lotto/LottoApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package lotto

import lotto.model.price.Lotto2024Price
import lotto.model.process.LottoMachineProcess
import lotto.model.rank.LottoWinningRank
import lotto.view.keyboard.MachineKeyboard
import lotto.view.monitor.LottoMonitor

fun main() {
val price = Lotto2024Price()
val lottoRank = LottoWinningRank()
val process = LottoMachineProcess(price, lottoRank)
val keyboard = MachineKeyboard()
val monitor = LottoMonitor()
val machine = LottoMachine(process, keyboard, monitor)
machine.issuanceLottoNumber()
}
31 changes: 31 additions & 0 deletions src/main/kotlin/lotto/LottoMachine.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package lotto

import lotto.model.process.MachineProcess
import lotto.view.keyboard.Keyboard
import lotto.view.monitor.Monitor

class LottoMachine(
private val process: MachineProcess,
private val keyboard: Keyboard,
private val monitor: Monitor,
) : Machine {
override fun issuanceLottoNumber() {
monitor.displayLottoPurchaseAmount()
val totalPurchaseAmount = keyboard.inputLottoPrice()
val lottoCount = process.calculateLottoCount(totalPurchaseAmount)
monitor.displayLottoPurchasesCount(lottoCount)

val lottoTickets = process.generateLottoTickets(lottoCount)
monitor.displayIssuedLottoTickets(lottoTickets)

monitor.displayInputLastWeekLottoWinningNumbers()
val lastWeekNumbers = keyboard.inputLastWeekWinningNumbers()
val lottoStatistics =
process.calculateWinningStatistics(
lottoTickets,
lastWeekNumbers,
totalPurchaseAmount,
)
monitor.displayLottoStatistics(lottoStatistics)
Comment on lines +13 to +29

Choose a reason for hiding this comment

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

프로그래밍 요구사항에는 아래와 같은 내용이 있습니다.

함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.

이 요구사항을 반영해보면 어떨까요?

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

interface Machine {
fun issuanceLottoNumber()
}
44 changes: 44 additions & 0 deletions src/main/kotlin/lotto/model/number/LottoNumbers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package lotto.model.number

class LottoNumbers(
private val lottoNumbers: List<Int>,
) : List<Int> by lottoNumbers {
init {
require(lottoNumbers.size == DEFAULT_LOTTO_COUNT) {
"로또 번호는 ${DEFAULT_LOTTO_COUNT}개여야 합니다."
}
require(lottoNumbers.all { it in LOTTO_MIN_NUMBER..LOTTO_MAX_NUMBER }) {
"로또 번호는 $LOTTO_MIN_NUMBER ~ $LOTTO_MAX_NUMBER 사이여야 합니다."
}
require(lottoNumbers.distinct().size == lottoNumbers.size) {
"로또 번호는 중복될 수 없습니다."
}
}

override fun toString(): String {
return "$lottoNumbers"
}

companion object {
const val LOTTO_MIN_NUMBER = 1
const val LOTTO_MAX_NUMBER = 45
const val DEFAULT_LOTTO_COUNT = 6
private val NUMBERS = (LOTTO_MIN_NUMBER..LOTTO_MAX_NUMBER)

fun issuanceLottoNumbers(lottoCount: Int = DEFAULT_LOTTO_COUNT): LottoNumbers {
val lottoNumbers =
NUMBERS.shuffled().take(lottoCount)
.sorted()
return LottoNumbers(lottoNumbers)
}

fun issuanceLottoTickets(
ticketCount: Int,
lottoCount: Int = DEFAULT_LOTTO_COUNT,
): List<LottoNumbers> {
return List(ticketCount) {
issuanceLottoNumbers(lottoCount)
}
}
}
}
15 changes: 15 additions & 0 deletions src/main/kotlin/lotto/model/price/Lotto2024Price.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package lotto.model.price

class Lotto2024Price(

Choose a reason for hiding this comment

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

클래스명에 년도를 명시해야하는 이유가 있을까요?
그렇다면 해마다 클래스를 추가해야하는걸까요?

override val price: Int = 1000,
) : LottoPrice {
init {
require(price > 0) {

Choose a reason for hiding this comment

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

로또를 1원 이상 천원 미만 입력하게 되면 어떻게 될까요?

"로또 금액 설정이 잘못되었습니다"
}
}

override fun calculateLottoCount(purchaseAmount: Int): Int {
return purchaseAmount.div(price)
}
}
7 changes: 7 additions & 0 deletions src/main/kotlin/lotto/model/price/LottoPrice.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package lotto.model.price

interface LottoPrice {
val price: Int

fun calculateLottoCount(purchaseAmount: Int): Int
}
33 changes: 33 additions & 0 deletions src/main/kotlin/lotto/model/process/LottoMachineProcess.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package lotto.model.process

import lotto.model.number.LottoNumbers
import lotto.model.price.LottoPrice
import lotto.model.rank.LottoRank
import lotto.model.statistics.DefaultLottoStatistics
import lotto.model.statistics.LottoStatistics

class LottoMachineProcess(
private val lottoPrice: LottoPrice,
private val lottoRank: LottoRank,
) : MachineProcess {
override fun calculateLottoCount(purchaseAmount: Int): Int {
return lottoPrice.calculateLottoCount(purchaseAmount)
}

override fun generateLottoTickets(lottoCount: Int): List<LottoNumbers> {
return LottoNumbers.issuanceLottoTickets(lottoCount)
}

override fun calculateWinningStatistics(
lottoTickets: List<LottoNumbers>,
winningNumbers: List<Int>,
totalPurchaseAmount: Int,
): LottoStatistics {
val winningRank =
lottoRank.calculateWinningCounts(
lottoTickets,
winningNumbers,
)
return DefaultLottoStatistics(winningRank, totalPurchaseAmount)
}
}
16 changes: 16 additions & 0 deletions src/main/kotlin/lotto/model/process/MachineProcess.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package lotto.model.process

import lotto.model.number.LottoNumbers
import lotto.model.statistics.LottoStatistics

interface MachineProcess {
fun calculateLottoCount(purchaseAmount: Int): Int

fun generateLottoTickets(lottoCount: Int): List<LottoNumbers>

fun calculateWinningStatistics(
lottoTickets: List<LottoNumbers>,
winningNumbers: List<Int>,
totalPurchaseAmount: Int,
): LottoStatistics
}
10 changes: 10 additions & 0 deletions src/main/kotlin/lotto/model/rank/LottoRank.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package lotto.model.rank

import lotto.model.number.LottoNumbers

interface LottoRank {
fun calculateWinningCounts(
lottoTickets: List<LottoNumbers>,
winningNumbers: List<Int>,
): Map<Int, Int>
}
33 changes: 33 additions & 0 deletions src/main/kotlin/lotto/model/rank/LottoWinningRank.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package lotto.model.rank

import lotto.model.number.LottoNumbers

class LottoWinningRank : LottoRank {
override fun calculateWinningCounts(
lottoTickets: List<LottoNumbers>,
winningNumbers: List<Int>,
): Map<Int, Int> {
return lottoTickets.groupingBy { lottoNumbers ->
val winningCounts = matchWinningLottoNumbersCount(lottoNumbers, winningNumbers)
winningCounts
}.eachCount().filter { it.key >= 3 }
}

private fun matchWinningLottoNumbersCount(
lottoNumbers: LottoNumbers,
winningNumbers: List<Int>,
): Int {
val winningSets = winningNumbers.toSet()
return lottoNumbers.count(winningSets::contains)
}

companion object {
val DEFAULT_RANK_PRICE =
mapOf(
6 to 2_000_000_000,
5 to 1_500_000,
4 to 50_000,
3 to 5_000,
)
Comment on lines +25 to +31

Choose a reason for hiding this comment

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

로또 번호가 일치하는 것에 대한 금액을 enum으로 관리해보면 어떨까요?

}
}
29 changes: 29 additions & 0 deletions src/main/kotlin/lotto/model/statistics/DefaultLottoStatistics.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package lotto.model.statistics

import lotto.model.rank.LottoWinningRank

class DefaultLottoStatistics(
override val winningRank: Map<Int, Int>,
private val totalPurchaseAmount: Int,
) : LottoStatistics {
override val profitRate: Double

init {
profitRate = calculateProfitRate()
}

private fun calculateProfitRate(): Double {
val totalPrize = calculateTotalPrize()
return if (totalPrize > 0) {
totalPrize.toDouble().div(totalPurchaseAmount)
} else {
0.0
}
}

private fun calculateTotalPrize(): Long {
return winningRank.map { (key, count) ->
(LottoWinningRank.DEFAULT_RANK_PRICE[key]?.toLong() ?: 0).times(count)
}.sum()
}
}
6 changes: 6 additions & 0 deletions src/main/kotlin/lotto/model/statistics/LottoStatistics.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package lotto.model.statistics

interface LottoStatistics {
val winningRank: Map<Int, Int>
val profitRate: Double
}
7 changes: 7 additions & 0 deletions src/main/kotlin/lotto/view/keyboard/Keyboard.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package lotto.view.keyboard

interface Keyboard {

Choose a reason for hiding this comment

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

Keyboard 인터페이스를 구현하는 곳은 MarchineKeyboard 클래스만 존재하네요.
민수님은 언제 인터페이스 - 구현체 구조를 사용하시나요?
현재 구조에서는 Keyboard 인터페이스의 존재 의미가 적어보이는데 민재님은 어떻게 생각하시나요?
(Monitor도 동일)

fun inputLottoPrice(): Int

fun inputLastWeekWinningNumbers(): List<Int>
}
26 changes: 26 additions & 0 deletions src/main/kotlin/lotto/view/keyboard/MachineKeyboard.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package lotto.view.keyboard

class MachineKeyboard : Keyboard {
override fun inputLottoPrice(): Int {
val input = readlnOrNull()?.toIntOrNull()
require(input != null && input > 0) { "구입금액이 잘못 입력되었습니다" }
return input
}

override fun inputLastWeekWinningNumbers(): List<Int> {
val input = readlnOrNull()
require(!input.isNullOrEmpty()) { "당첨 번호 입력이 잘못되었습니다." }
return parseLottoNumbers(input)
}

private fun parseLottoNumbers(input: String): List<Int> {
return input.split(",")
.map(::validateAndParseNumber)
}

private fun validateAndParseNumber(numberString: String): Int {
val number = numberString.trim().toIntOrNull()
require(number != null) { "당첨 번호 입력이 잘못되었습니다." }
return number
}
}
Loading