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] 로또(자동) #724

Open
wants to merge 6 commits into
base: kyudong3
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
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,18 @@
- [x] indent는 depth가 2를 넘지 않도록 구현
- ex) while 문 안에 if 문이 있으면 들여쓰기는 2이다
- [x] 함수의 길이가 10개 Line을 넘어가지 않도록 구현
- 함수가 한 가지 일만 잘 하도록 구현
- 함수가 한 가지 일만 잘 하도록 구현



## 로또(자동)
### 기능 요구 사항
- [x] 구입한 금액에 해당하는 구매 갯수 반환
- [x] 로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급해야 한다

### 프로그래밍 요구 사항
- [x] InputView 생성
- [x] indent는 depth가 2를 넘지 않도록 구현
- [x] 함수의 길이가 15 라인을 넘어가지 않도록 구현
- [x] ResultView 생성

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

Choose a reason for hiding this comment

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

패키지를 구조를 변경하여 domain 과 view 를 분리해주세요.


object InputView {

fun requestPurchaseAmount(): Int {
printMessage("구입금액을 입력해 주세요.")
return readln().toInt()
}

fun requestLastWinnerNumbers(): List<Int> {
printMessage("\n지난 주 당첨 번호를 입력해 주세요.")
return readln().split(",").map {
it.trim().toInt()
}
Comment on lines +12 to +14
Copy link

Choose a reason for hiding this comment

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

메서드를 연속해서 호출하는 경우에는 행 구분을 해주세요!

https://kotlinlang.org/docs/coding-conventions.html#wrap-chained-calls

Copy link
Author

Choose a reason for hiding this comment

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

kotlin convention을 다시 한 번 제대로 읽어봐야겠어요 감사합니다! 🙏🏼

}

private fun printMessage(message: String) {
println(message)
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/lotto/Lottery.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package lotto

data class Lottery(
val numbers: List<Int>
Copy link

Choose a reason for hiding this comment

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

로또의 번호는 1~45까지 허용하는데요.

이를 벗어나지 않도록 방지해줄 수 있다면 더 좋지 않을까요?

LottoNumber와 같은 객체를 생성해보시는걸 추천드립니다.

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

data class LotteryResult(
val prize: Int,
val matchCount: Int,
val message: String
)
21 changes: 21 additions & 0 deletions src/main/kotlin/lotto/LotteryShop.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package lotto

class LotteryShop {

fun buy(money: Int): UserLottery {
val count = money / LOTTERY_PRICE
val lotteryTickets = mutableListOf<Lottery>()
repeat(count) {
lotteryTickets.add(
Lottery((MIN_LOTTERY_NUMBER..MAX_LOTTERY_NUMBER).shuffled().take(6).sorted())
)
}
Comment on lines +7 to +12
Copy link

Choose a reason for hiding this comment

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

가변적인 MutableList를 사용하는 것 보다 불변인 List로 생성하는 것이 더 안정적일 것 같습니다~!

아래와 같이 개선해보면 좋을것 같습니다.

Suggested change
val lotteryTickets = mutableListOf<Lottery>()
repeat(count) {
lotteryTickets.add(
Lottery((MIN_LOTTERY_NUMBER..MAX_LOTTERY_NUMBER).shuffled().take(6).sorted())
)
}
val lotteryTickets = List(count) {
Lottery((MIN_LOTTERY_NUMBER..MAX_LOTTERY_NUMBER).shuffled().take(6).sorted())
}

Copy link
Author

Choose a reason for hiding this comment

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

리턴할 때 UserLottery에 List 타입으로 선언해 추가해주었는데, buy 메소드 안에서도 가변적인 리스트보다 불변 리스트를 생성해 리턴하면 더 좋을 것 같네요!! 👍🏼

return UserLottery(count, lotteryTickets)
}

companion object {
private const val LOTTERY_PRICE = 1000
private const val MIN_LOTTERY_NUMBER = 1
private const val MAX_LOTTERY_NUMBER = 45
}
}
36 changes: 36 additions & 0 deletions src/main/kotlin/lotto/LotteryStatistic.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package lotto

object LotteryStatistic {

fun getWinStatistic(
lotteryTickets: List<Lottery>,
lastWinnerNumbers: List<Int>
): Pair<List<LotteryResult>, Double> {
val winResult = checkLotteryTickets(lotteryTickets, lastWinnerNumbers)

val lotteryResults = mutableListOf<LotteryResult>()
var sum = 0
winResult.forEach { (count, matchCount) ->
val prize = WinnerPrize.getPrize(count).money
val message = "${count}개 일치 (${prize}원) - ${matchCount}개"
Copy link

Choose a reason for hiding this comment

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

도메인 객체에서 view에 대한 책임까지 담당하고 있는것 같아보입니다.

view에 대한 요구사항이 지금은 console에 띄우는 것 말고 없지만

다른 view가 추가로 들어오게 된다면 대처하기 어려울 것 같습니다.

따라서 view에 대해 어떻게 표현할지는 view에게 일임하는것이 좋아보입니다.

domain 로직에서 view에 대한 구현상세 부분은 제거해주세요!

lotteryResults.add(LotteryResult(prize, matchCount, message))
sum += (prize * matchCount)
}

return lotteryResults.toList() to sum.toDouble()
Comment on lines +5 to +20
Copy link

Choose a reason for hiding this comment

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

List<Lottery>를 가지고 있는 일급컬렉션에게 winningNumbers를 전달하여 위 책임을 위임할 수 있을것 같습니다.

그러면 전체적인 코드가 조금 더 간결해 질 수 있을것 같습니다.

}

private fun checkLotteryTickets(
lotteryTickets: List<Lottery>,
lastWinnerNumbers: List<Int>
): List<Pair<Int, Int>> =
Copy link

Choose a reason for hiding this comment

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

PairIntInt가 쌍으로 나가는것보다 조금더 의미있는 객체로 내보내는것이 코드를 이해하기 더 수월할 것 같습니다.

lotteryTickets.map { lottery ->
lottery.numbers.intersect(
lastWinnerNumbers.sorted().toSet()
Copy link

Choose a reason for hiding this comment

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

정렬할 필요가 있을까요??

Copy link
Author

Choose a reason for hiding this comment

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

다시 생각해보니 sort할 필요는 없는 것 같네요..! 🙏🏼

).count()
}.filter { it >= 3 }
Copy link

Choose a reason for hiding this comment

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

3개가 넘게 일치하는지를 이곳에서 판별하지 않고

WinnerPrize에게 물어보는 형식으로 변경할 수 있을것 같습니다.

.groupingBy { it }
.eachCount()
.toList()
.sortedBy { it.first }
}
15 changes: 15 additions & 0 deletions src/main/kotlin/lotto/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package lotto

fun main() {
val lotteryShop = LotteryShop()
val money = InputView.requestPurchaseAmount()
val userLotteryInfo = lotteryShop.buy(money)

ResultView.printLotteryCount(userLotteryInfo.lotteryCount)
ResultView.printAllLotteries(userLotteryInfo.lotteryTickets)

val lastWinnerNumbers = InputView.requestLastWinnerNumbers()
val lotteryResults = LotteryStatistic.getWinStatistic(userLotteryInfo.lotteryTickets, lastWinnerNumbers)

ResultView.printWinStatistic(lotteryResults, money)
}
25 changes: 25 additions & 0 deletions src/main/kotlin/lotto/ResultView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package lotto

object ResultView {

fun printLotteryCount(size: Int) {
println("${size}개를 구매했습니다.")
}

fun printAllLotteries(lotteryTickets: List<Lottery>) {
lotteryTickets.forEach { lottery ->
println(lottery.numbers)
}
}

fun printWinStatistic(
lotteryResults: Pair<List<LotteryResult>, Double>,
money: Int
) {
println("\n당첨 통계\n-------------------")
lotteryResults.first.forEach { lotteryResult ->
println(lotteryResult.message)
}
println(String.format("총 수익률은 %.2f 입니다", lotteryResults.second / money))
}
}
6 changes: 6 additions & 0 deletions src/main/kotlin/lotto/UserLottery.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package lotto

data class UserLottery(
val lotteryCount: Int,
val lotteryTickets: List<Lottery>
)
18 changes: 18 additions & 0 deletions src/main/kotlin/lotto/WinnerPrize.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package lotto

enum class WinnerPrize(val money: Int) {
Copy link

Choose a reason for hiding this comment

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

1등에 여러번 당첨되면 Int로는 감당하기 힘들어 질 것 같네요 😄


FIRST(2_000_000_000),
SECOND(1_500_000),
THIRD(50_000),
LAST(5_000);

companion object {
fun getPrize(sameCount: Int): WinnerPrize = when (sameCount) {
3 -> LAST
4 -> THIRD
5 -> SECOND
else -> FIRST
Comment on lines +12 to +15
Copy link

Choose a reason for hiding this comment

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

WinnerPrizecount에 대한 정보도 가지고 있을 수 있지 않을까요?

Copy link

Choose a reason for hiding this comment

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

FIRST는 전부 맞은것으로 판별하면 되지 않을까요?

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

import io.kotest.core.spec.style.StringSpec
import io.kotest.inspectors.forAll
import io.kotest.matchers.shouldBe

class LotteryShopKoTest : StringSpec({

"구매한 금액에 해당하는 로또 갯수 반환" {
val lotteryShop = LotteryShop()
val actualInput = 14_000
lotteryShop.buy(actualInput).lotteryCount shouldBe 14
}

"일치하는 로또 갯수에 의한 당첨 금액" {
val first = WinnerPrize.getPrize(6)
val second = WinnerPrize.getPrize(5)
val third = WinnerPrize.getPrize(4)
val last = WinnerPrize.getPrize(3)

first.money shouldBe 2_000_000_000
second.money shouldBe 1_500_000
third.money shouldBe 50_000
last.money shouldBe 5_000
}

"구매한 로또들과 지난 주 당첨 번호가 일치하는지 확인" {
val lastWinnerNumbers = listOf(1, 2, 3, 4, 5, 6)
val lotteryTickets = listOf(
Lottery(listOf(1, 2, 3, 4, 39, 43)),
Lottery(listOf(9, 11, 21, 27, 28, 32)),
Lottery(listOf(4, 5, 14, 17, 31, 35)),
Lottery(listOf(11, 13, 17, 22, 25, 34)),
Lottery(listOf(1, 3, 5, 21, 41, 42)),
Lottery(listOf(6, 7, 17, 19, 32, 35)),
Lottery(listOf(2, 12, 19, 34, 35, 37)),
Lottery(listOf(1, 3, 5, 6, 39, 44)),
Lottery(listOf(2, 5, 8, 10, 13, 23)),
Lottery(listOf(7, 29, 30, 31, 34, 36))
)

val lotteryResults = LotteryStatistic.getWinStatistic(lotteryTickets, lastWinnerNumbers).first

lotteryResults.forEach {
when (it.prize) {
3 -> it.matchCount shouldBe 1
4 -> it.matchCount shouldBe 2
5 -> it.matchCount shouldBe 0
6 -> it.matchCount shouldBe 0
}
Comment on lines +28 to +50
Copy link

Choose a reason for hiding this comment

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

검증에 불필요한 테스트 데이터는 제거해서 테스트를 간소화해주시는것이 가독성을 높이는 방법이 될 수 있습니다.

이 테스트에서 구매한 당첨번호가 일치하는지를 확인하고 싶으시다면 상금의 일치하는 갯수에 맞게 Lottery를 생성해두는건 어떨까요?

}
}
})
56 changes: 56 additions & 0 deletions src/test/kotlin/lotto/LotteryShopTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package lotto

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

class LotteryShopTest {
Copy link

Choose a reason for hiding this comment

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

Junit으로 동일한 테스트를 한번 더 작성해주셨네요!

Kotest, Junit 둘 중 하나만 사용해주셔도 좋을것 같습니다.

Copy link
Author

Choose a reason for hiding this comment

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

네네! Junit과 kotest 모두 익숙하지 않아 둘 다 사용해보고 있는데 다음부터는 kotest만 사용하려고 합니다! 🙏🏼


@Test
fun `구매한 금액에 해당하는 로또 갯수 반환`() {
val lotteryShop = LotteryShop()
val actualInput = 14_000
val userLottery = lotteryShop.buy(actualInput)
assertThat(userLottery.lotteryCount).isEqualTo(14)
}

@Test
fun `일치하는 로또 갯수에 의한 당첨 금액`() {
val first = WinnerPrize.getPrize(6)
val second = WinnerPrize.getPrize(5)
val third = WinnerPrize.getPrize(4)
val last = WinnerPrize.getPrize(3)

assertThat(first.money).isEqualTo(2_000_000_000)
assertThat(second.money).isEqualTo(1_500_000)
assertThat(third.money).isEqualTo(50_000)
assertThat(last.money).isEqualTo(5_000)
}

@Test
fun `구매한 로또들과 지난 주 당첨 번호가 일치하는지 확인`() {
val lastWinnerNumbers = listOf(1, 2, 3, 4, 5, 6)
val lotteryTickets = listOf(
Lottery(listOf(1, 2, 3, 4, 39, 43)),
Lottery(listOf(9, 11, 21, 27, 28, 32)),
Lottery(listOf(4, 5, 14, 17, 31, 35)),
Lottery(listOf(11, 13, 17, 22, 25, 34)),
Lottery(listOf(1, 3, 5, 21, 41, 42)),
Lottery(listOf(6, 7, 17, 19, 32, 35)),
Lottery(listOf(2, 12, 19, 34, 35, 37)),
Lottery(listOf(1, 3, 5, 6, 39, 44)),
Lottery(listOf(2, 5, 8, 10, 13, 23)),
Lottery(listOf(7, 29, 30, 31, 34, 36))
)

val lotteryResults = LotteryStatistic.getWinStatistic(lotteryTickets, lastWinnerNumbers).first

lotteryResults.forEach {
when (it.prize) {
3 -> assertThat(it.matchCount).isEqualTo(1)
4 -> assertThat(it.matchCount).isEqualTo(2)
5 -> assertThat(it.matchCount).isEqualTo(0)
6 -> assertThat(it.matchCount).isEqualTo(0)
}
}
}
}