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] 2단계 - 로또(자동) #1139

Merged
merged 13 commits into from
Dec 31, 2024
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,17 @@
# kotlin-lotto
# kotlin-lotto
## 기능 요구 사항
### 1. 입력
- [ ] 구입 금액을 입력받는다.
- [ ] 지난 주 당첨 번호를 입력받는다.

### 2. 로또 발급
- [ ] 구입 금액에 따라 로또를 발급한다. (1장 = 1000원)
- [ ] 각 로또는 1~45 사이의 중복되지 않는 6개의 숫자로 구성된다.

### 3. 당첨 검증
- [ ] 각 로또의 숫자와 당첨 번호를 비교하여 일치하는 개수를 확인한다.
- [ ] 일치 개수에 따라 당첨금을 계산한다.

### 4. 결과 출력
- [ ] 당첨 내역을 출력한다. (3개, 4개, 5개, 6개 일치)
- [ ] 수익률을 계산하여 출력한다.
24 changes: 24 additions & 0 deletions src/main/kotlin/lotto/Application.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package lotto

import lotto.domain.Lotto
import lotto.domain.LottoCalculator
import lotto.domain.LottoTicket
import lotto.ui.InputView
import lotto.ui.ResultView

fun main() {
val inputView = InputView()
val resultView = ResultView()

val amount = inputView.getMoney()
val lottoTicket = LottoTicket.makeTicket(amount)
resultView.printTickets(lottoTicket.getTickets())

val winningLotto = Lotto(inputView.getWinningNumbers())

val calculator = LottoCalculator()
val results = calculator.calculateResults(lottoTicket, winningLotto)
val profitRate = calculator.calculateProfit(results)

resultView.printResults(results, profitRate)
}
16 changes: 16 additions & 0 deletions src/main/kotlin/lotto/domain/Lotto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package lotto.domain

data class Lotto(val numbers: List<Int>) {
init {
require(numbers.size == 6) { "Invalid input" }
require(numbers.all { it in 1..45 }) { "Out of Range" }
}

fun getMatchedNumberCount(winningLotto: Lotto): Int {
return numbers.count { it in winningLotto }
}

operator fun contains(number: Int): Boolean {
return numbers.contains(number)
}
}
29 changes: 29 additions & 0 deletions src/main/kotlin/lotto/domain/LottoCalculator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package lotto.domain

class LottoCalculator {
Copy link
Member

Choose a reason for hiding this comment

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

LottoCalculator라는 이름만 보고도 현재 하고 있는 행동(수익률 계산, 로또 결과 판단)을 예측할 수 있을까요?

Copy link
Member

Choose a reason for hiding this comment

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

여러 가지 해결 방법이 있겠지만, 잘 Wrapping해주신 LottoResult를 외부에서 생성해주지 말고, 객체 스스로 판단하여 Result를 생성할 수 있도록 만드는 것도 하나의 방법이 될 수 있을 것 같아요 🙂

fun calculateResults(
tickets: LottoTicket,
winningNumbers: Lotto,
): LottoResult {
return LottoResult(
tickets.getTickets()
.groupingBy { it.getMatchedNumberCount(winningNumbers) }
.eachCount(),
)
}

fun calculateProfit(lottoResult: LottoResult): Double {
val totalPrize =
lottoResult.getResults().entries.sumOf { (match, count) ->
when (match) {
3 -> 5000 * count
4 -> 50000 * count
5 -> 1500000 * count
6 -> 2000000000 * count
else -> 0
}
}
Comment on lines +17 to +25
Copy link
Member

Choose a reason for hiding this comment

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

다음 단계를 진행하시면서 아래 요구 사항에 맞게 enum 클래스를 활용해 보시고 이전과 어떻게 달라졌는지 직접 확인해보시면 더욱 의미있을 것 같아요!

Enum 클래스를 적용해 프로그래밍을 구현한다.


return totalPrize.toDouble()
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/lotto/domain/LottoMaker.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package lotto.domain

class LottoMaker(private val numberGenerator: () -> List<Int>) {
fun makeLotto(): Lotto {
return Lotto(numberGenerator().sorted())
}

companion object {
private const val START_NUMBER = 1
private const val END_NUMBER = 45
private const val LOTTO_COUNT = 6

fun defaultMaker(): LottoMaker {
return LottoMaker({ (START_NUMBER..END_NUMBER).shuffled().take(LOTTO_COUNT).sorted() })
}
}
}
11 changes: 11 additions & 0 deletions src/main/kotlin/lotto/domain/LottoResult.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package lotto.domain

data class LottoResult(private val result: Map<Int, Int>) {
fun getResults(): Map<Int, Int> {
return result
}

fun getMatchCount(match: Int): Int {
return result.getOrDefault(match, 0)
}
}
15 changes: 15 additions & 0 deletions src/main/kotlin/lotto/domain/LottoTicket.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package lotto.domain

class LottoTicket(private val tickets: List<Lotto>) {
fun size(): Int = tickets.size

fun getTickets(): List<Lotto> = tickets

companion object {
fun makeTicket(amount: Int): LottoTicket {
val lottoMaker = LottoMaker.defaultMaker()
val lottoCount = amount / 1000
return LottoTicket(List(lottoCount) { lottoMaker.makeLotto() })
Copy link
Member

Choose a reason for hiding this comment

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

리뷰를 잘 반영해서 도메인 로직을 도메인 객체에 잘 위임해주셨어요!
이러한 로직을 부생성자로 만드는 것도 충분히 고려해볼 수 있을 것 같은데요,
부생성자에 대한 아래와 같은 시각도 있으니 참고해보시면 도움이 되실 것 같습니다 🙂

Copy link
Member

Choose a reason for hiding this comment

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

더불어 이 로직도 테스트 코드로 검증해보면 어떨까요?

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

class InputView {
fun getMoney(): Int {
println("구입금액을 입력해주세요")
val money = readln()
requireNotNull(money.toInt())
return money.toInt()
}

fun getWinningNumbers(): List<Int> {
println("지난 주 당첨 번호를 입력해 주세요.")
return readln().split(",").map { it.trim().toInt() }.toList()
}
}
23 changes: 23 additions & 0 deletions src/main/kotlin/lotto/ui/ResultView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package lotto.ui

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

class ResultView {
fun printTickets(tickets: List<Lotto>) {
println("${tickets.size}개를 구매했습니다.")
tickets.forEach { println(it.numbers) }
}

fun printResults(
results: LottoResult,
profit: Double,
) {
println("당첨 통계")
println("---------")
results.getResults().forEach { (matchCount, count) ->
println("$matchCount 개 일치 - $count 개")
}
println("총 수익률은 $profit 입니다.")
}
}
45 changes: 45 additions & 0 deletions src/test/kotlin/lotto/domain/LottoCalculatorTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package lotto.domain

import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test

class LottoCalculatorTest {
private val calculator = LottoCalculator()
Copy link
Member

Choose a reason for hiding this comment

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

LottoCalculator는 커밋되지 않은 것 같아요!

Copy link
Author

Choose a reason for hiding this comment

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

빠졌었네요ㅠ 추가로 커밋했습니다!


@Test
fun testLottoCalculator() {
val tickets =
LottoTicket(
listOf(
Lotto(listOf(1, 2, 3, 4, 5, 6)),
Lotto(listOf(1, 2, 3, 7, 8, 9)),
Lotto(listOf(10, 11, 12, 13, 14, 15)),
)
)
val winningLotto = Lotto(listOf(1, 2, 3, 4, 5, 6))

val results = calculator.calculateResults(tickets, winningLotto)
assertEquals(1, results.getMatchCount(6))
assertEquals(1, results.getMatchCount(3))
assertEquals(1, results.getMatchCount(0))
}

@Test
fun testCalculateProfit() {
val results =
LottoResult(
mapOf(
6 to 1,
5 to 2,
4 to 1,
3 to 2,
)
)

val profit = calculator.calculateProfit(results)
assertEquals(
2000000000.0 * 1 + 1500000.0 * 2 + 50000.0 * 1 + 5000.0 * 2,
profit
)
}
}
31 changes: 31 additions & 0 deletions src/test/kotlin/lotto/domain/LottoMakerTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package lotto.domain

import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test

class LottoMakerTest {
@Test
fun `Lotto - size should be 6`() {
val fixedNumbers = listOf(1, 2, 3, 4, 5, 6)
val lottoMaker = LottoMaker { fixedNumbers }
val lotto = lottoMaker.makeLotto()

lotto.numbers.size shouldBe 6
}

@Test
fun `Lotto - should be same with list`() {
val fixedNumbers = listOf(1, 2, 3, 4, 5, 6)
val lottoMaker = LottoMaker { fixedNumbers }
val lotto = lottoMaker.makeLotto()

lotto.numbers shouldBe fixedNumbers
}

@Test
fun `Lotto - should be in range(1-45)`() {
val lottoMaker = LottoMaker.defaultMaker()
val lotto = lottoMaker.makeLotto()
lotto.numbers.all { it in 1..45 } shouldBe true
}
}