-
Notifications
You must be signed in to change notification settings - Fork 357
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 로또 #596
base: mia6111
Are you sure you want to change the base?
Step2 로또 #596
Changes from all commits
08cf7e3
2efcfbc
38ed957
828ec8a
83fce92
b902602
d1854d7
2a75875
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,18 @@ | ||
package lotto | ||
|
||
import lotto.domain.Lottery | ||
import lotto.domain.LotteryMachine | ||
import lotto.view.LotteryMachineInputView | ||
import lotto.view.LotteryMachineOutputView | ||
|
||
fun main() { | ||
|
||
val payAmount = LotteryMachineInputView.inputPayAmount() | ||
val lotteries = LotteryMachine.buyLotteries(payAmount) | ||
LotteryMachineOutputView.printLotteries(lotteries) | ||
|
||
val lastWinningLottery = Lottery(LotteryMachineInputView.inputLastWinningNumbers()) | ||
|
||
val result = LotteryMachine.getMatchCount(lotteries, lastWinningLottery) | ||
LotteryMachineOutputView.printResult(result, LotteryMachine.calculateReturnRate(payAmount, result)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Step2. 로또 기능 요구사항 | ||
* 구입금액을 입력받는다 | ||
* 구입 금액에 해당하는 로또를 발급한다 (로또 1장의 가격은 1000원이다) | ||
* 발급한 로또를 출력한다 | ||
* 지난 주 당첨번호를 입력받는다 | ||
* 발급한 로또의 당첨 통계를 구한다 | ||
* 발급한 로또의 총 수익률을 구한다 | ||
* 당첨 통계와 수익률을 출력한다 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package lotto.domain | ||
|
||
import java.math.BigDecimal | ||
import java.math.RoundingMode | ||
|
||
object LotteryMachine { | ||
private const val PRICE = 1_000 | ||
|
||
private val LOTTO_RETURN_MAP = mapOf( | ||
3 to 5_000, | ||
4 to 50_000, | ||
5 to 1_500_000, | ||
6 to 2_000_000_000 | ||
) | ||
|
||
fun buyLotteries(payAmount: Int): Lotteries { | ||
val howMany = (payAmount / PRICE) | ||
val lotteryList = List(howMany) { Lottery(randomLottoNumbers()) } | ||
return Lotteries(lotteryList) | ||
} | ||
Comment on lines
+16
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.
Comment on lines
+6
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.
모두 |
||
|
||
private fun randomLottoNumbers() = LottoNumber.allNumbers().shuffled().subList(0, Lottery.COUNT) | ||
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 getMatchCount(lotteries: Lotteries, lastWinningLottery: Lottery): LotteryMatchCount { | ||
val matchCount = lotteries.lotteries | ||
.groupingBy { it.countSameLottoNumbers(lastWinningLottery) } | ||
.eachCount() | ||
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.
|
||
|
||
return LotteryMatchCount(matchCount) | ||
} | ||
|
||
fun calculateReturnRate(payAmount: Int, lotteryResult: LotteryMatchCount): BigDecimal { | ||
val totalPrize = LOTTO_RETURN_MAP.entries.map { | ||
it.value * (lotteryResult.matchCount[it.key] ?: 0) | ||
}.sum() | ||
Comment on lines
+32
to
+35
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.
val totalPrize = LOTTO_RETURN_MAP.entries.sumOf { (matchCount, prize) ->
...
} |
||
|
||
return BigDecimal.valueOf(totalPrize / payAmount.toDouble()) | ||
.setScale(2, RoundingMode.FLOOR) | ||
} | ||
Comment on lines
+37
to
+39
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,3 @@ | ||
package lotto.domain | ||
|
||
data class LotteryMatchCount(val matchCount: Map<Int, Int>) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package lotto.domain | ||
|
||
class Lotteries(val lotteries: List<Lottery>) { | ||
constructor(vararg lotteries: Lottery) : this(lotteries.toList()) | ||
|
||
fun count(): Int { | ||
return lotteries.size | ||
} | ||
} | ||
Comment on lines
+3
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.
|
||
|
||
class Lottery(numbers: List<Int>) { | ||
|
||
val numbers: List<LottoNumber> | ||
|
||
constructor(vararg inputNumbers: Int) : this(inputNumbers.toList()) | ||
|
||
init { | ||
require(numbers.size == COUNT) | ||
this.numbers = numbers.map { LottoNumber.of(it) }.sorted() | ||
} | ||
Comment on lines
+11
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 countSameLottoNumbers(other: Lottery): Int { | ||
return this.numbers.count { other.numbers.contains(it) } | ||
} | ||
companion object { | ||
const val COUNT = 6 | ||
} | ||
} | ||
|
||
class LottoNumber private constructor(val value: Int) : Comparable<LottoNumber> { | ||
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. 값 객체인 LottoNumber 를 미리 생성해서 상수로 정의(아래 NUMBERS)해놓고 재활용하도록 하고 싶었는데요
값 객체로 만들기 위해 data 클래스로 만들경우 일단 data 클래스를 사용하지 않고, toString, equals, hashcode 를 구현하도록했습니다 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. 기본 생성자와 팩터리 메서드 동작이 완전히 동일한 것 같은데, 팩터리 메서드를 사용하고 싶으신 이유가 있으실까요? 팩터리 메서드를 사용하는 이유에 따라 답변이 달라질 것 같습니다. |
||
init { | ||
require(value in (MIN_LOTTO_NUMBER..MAX_LOTTO_NUMBER)) | ||
} | ||
|
||
override fun toString(): String { | ||
return "LottoNumber(number=$value)" | ||
} | ||
|
||
override fun equals(other: Any?): Boolean { | ||
if (this === other) return true | ||
if (javaClass != other?.javaClass) return false | ||
|
||
other as LottoNumber | ||
|
||
if (value != other.value) return false | ||
|
||
return true | ||
} | ||
|
||
override fun hashCode(): Int { | ||
return value | ||
} | ||
|
||
override fun compareTo(other: LottoNumber): Int { | ||
return this.value.compareTo(other.value) | ||
} | ||
|
||
companion object { | ||
private const val MIN_LOTTO_NUMBER = 1 | ||
private const val MAX_LOTTO_NUMBER = 45 | ||
private val NUMBERS = List(MAX_LOTTO_NUMBER) { LottoNumber(it + MIN_LOTTO_NUMBER) } | ||
fun allNumbers(): List<Int> { | ||
return NUMBERS.map { it.value } | ||
} | ||
|
||
fun of(number: Int): LottoNumber { | ||
require(number - MIN_LOTTO_NUMBER in (NUMBERS.indices)) | ||
|
||
return NUMBERS[number - MIN_LOTTO_NUMBER] | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package lotto.view | ||
|
||
object LotteryMachineInputView { | ||
|
||
fun inputPayAmount(): Int { | ||
println("구입금액을 입력해 주세요.") | ||
return readLine()?.toIntOrNull() ?: throw IllegalArgumentException() | ||
} | ||
|
||
fun inputLastWinningNumbers(): List<Int> { | ||
println("지난 주 당첨 번호를 입력해 주세요.") | ||
|
||
return readLine().orEmpty() | ||
.split(",") | ||
.map { it.toIntOrNull() ?: throw IllegalArgumentException() } | ||
} | ||
Comment on lines
+4
to
+16
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,29 @@ | ||
package lotto.view | ||
|
||
import lotto.domain.Lotteries | ||
import lotto.domain.LotteryMatchCount | ||
import java.math.BigDecimal | ||
|
||
object LotteryMachineOutputView { | ||
fun printLotteries(lotteries: Lotteries) { | ||
println("${lotteries.count()}개를 구매했습니다.") | ||
|
||
for (it in lotteries.lotteries) { | ||
println("[${it.numbers.map { it.value }.toList().joinToString(", ")}]") | ||
Comment on lines
+11
to
+12
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 printResult(result: LotteryMatchCount, returnRate: BigDecimal) { | ||
println( | ||
""" | ||
|당첨 통계 | ||
|--------- | ||
|3개 일치 (5000원)- ${result.matchCount[3] ?: 0}개 | ||
|4개 일치 (50000원)- ${result.matchCount[4] ?: 0}개 | ||
|5개 일치 (1500000원)- ${result.matchCount[5] ?: 0}개 | ||
|6개 일치 (2000000000원)- ${result.matchCount[6] ?: 0}개 | ||
|총 수익률은 ${returnRate}입니다. | ||
""".trimIndent() | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package lotto.domain | ||
|
||
import io.kotest.matchers.comparables.shouldBeEqualComparingTo | ||
import io.kotest.matchers.maps.shouldNotHaveKey | ||
import io.kotest.matchers.shouldBe | ||
import org.junit.jupiter.api.DisplayName | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.api.assertAll | ||
import org.junit.jupiter.params.ParameterizedTest | ||
import org.junit.jupiter.params.provider.Arguments | ||
import org.junit.jupiter.params.provider.MethodSource | ||
import java.math.BigDecimal | ||
import java.util.stream.Stream | ||
|
||
internal class LotteryMachineTest { | ||
|
||
@DisplayName("구입 금액에 해당하는 로또를 발급한다") | ||
@Test | ||
fun buyLottery() { | ||
listOf( | ||
1000 to 1, | ||
1500 to 1, | ||
3900 to 3, | ||
10000 to 10 | ||
).map { (payAmount, howManyBought) -> | ||
{ | ||
val result = LotteryMachine.buyLotteries(payAmount) | ||
|
||
result.count() shouldBe howManyBought | ||
} | ||
} | ||
} | ||
|
||
@Test | ||
fun getMatchResult() { | ||
|
||
val result = LotteryMachine.getMatchCount( | ||
Lotteries( | ||
Lottery(1, 2, 13, 14, 15, 16), | ||
Lottery(11, 12, 13, 4, 5, 6), | ||
Lottery(1, 2, 3, 14, 15, 16) | ||
), | ||
Lottery(1, 2, 3, 4, 5, 6) | ||
) | ||
assertAll( | ||
{ result.matchCount[2] shouldBe 1 }, | ||
{ result.matchCount[3] shouldBe 2 }, | ||
{ listOf(0, 1, 4, 5).forEach { result.matchCount shouldNotHaveKey it } } | ||
) | ||
} | ||
|
||
@DisplayName("수익률을 구한다 (소숫점 2자리수에서 반올림한다)") | ||
@ParameterizedTest | ||
@MethodSource("calculateReturnRateProvider") | ||
fun calculateReturnRate(payAmount: Int, countSameLottoNumber: Map<Int, Int>, expected: BigDecimal) { | ||
|
||
LotteryMachine.calculateReturnRate( | ||
payAmount, | ||
LotteryMatchCount(countSameLottoNumber) | ||
) shouldBeEqualComparingTo expected | ||
} | ||
|
||
companion object { | ||
@JvmStatic | ||
fun calculateReturnRateProvider(): Stream<Arguments> = Stream.of( | ||
Arguments.of(14000, mapOf(3 to 1, 2 to 1), BigDecimal.valueOf(0.35)), | ||
Arguments.of(10000, mapOf(1 to 3), BigDecimal.ZERO), | ||
Arguments.of(30000, mapOf(4 to 1, 3 to 1, 2 to 1), BigDecimal.valueOf(1.83)), | ||
Arguments.of(20000, mapOf(5 to 1, 4 to 1), BigDecimal.valueOf(77.5)) | ||
) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package lotto.domain | ||
|
||
import io.kotest.assertions.throwables.shouldThrow | ||
import io.kotest.matchers.shouldBe | ||
import org.junit.jupiter.api.DisplayName | ||
import org.junit.jupiter.api.Test | ||
|
||
internal class LotteryTest { | ||
|
||
@DisplayName("6개의 로또 번호를 포함해야한다") | ||
@Test | ||
fun create() { | ||
listOf( | ||
intArrayOf(1, 2), | ||
intArrayOf(1, 2, 3, 4, 5), | ||
intArrayOf(1, 2, 3, 4, 5, 6, 7) | ||
).map { numbers -> | ||
shouldThrow<IllegalArgumentException> { Lottery(*numbers) } | ||
} | ||
} | ||
|
||
@DisplayName("일치하는 로또 번호 갯수를 구한다") | ||
@Test | ||
fun countSameLottoNumbers() { | ||
val sut = Lottery(1, 2, 3, 4, 5, 6) | ||
listOf( | ||
Lottery(1, 12, 13, 14, 15, 16) to 1, | ||
Lottery(1, 2, 13, 14, 15, 16) to 2, | ||
Lottery(1, 2, 3, 4, 5, 6) to 6 | ||
).map { (other, expected) -> | ||
sut.countSameLottoNumbers(other) shouldBe expected | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package lotto.domain | ||
|
||
import io.kotest.assertions.throwables.shouldThrow | ||
import io.kotest.matchers.comparables.shouldBeEqualComparingTo | ||
import io.kotest.matchers.shouldBe | ||
import io.kotest.matchers.types.shouldBeSameInstanceAs | ||
import org.junit.jupiter.api.Assertions.assertAll | ||
import org.junit.jupiter.api.DisplayName | ||
import org.junit.jupiter.api.Test | ||
import org.junit.jupiter.params.ParameterizedTest | ||
import org.junit.jupiter.params.provider.ValueSource | ||
|
||
internal class LottoNumberTest { | ||
|
||
@DisplayName("특정 로또 번호로 생성할 수 있다") | ||
@ParameterizedTest | ||
@ValueSource(ints = [1, 2, 3, 4, 43, 44, 45]) | ||
fun number(number: Int) { | ||
LottoNumber.of(number).value shouldBe number | ||
} | ||
|
||
@DisplayName("로또 번호는 1-45 사이의 값이여야 한다") | ||
@ParameterizedTest | ||
@ValueSource(ints = [0, -1, 46, 50, 100]) | ||
fun getInstanceFailIfNotLottoNumber(number: Int) { | ||
shouldThrow<IllegalArgumentException> { LottoNumber.of(number) } | ||
} | ||
|
||
@DisplayName("값 객체이다") | ||
@ParameterizedTest | ||
@ValueSource(ints = [1, 2, 3, 4, 43, 44, 45]) | ||
fun getInstance(number: Int) { | ||
val sut = LottoNumber.of(number) | ||
val other = LottoNumber.of(number) | ||
|
||
assertAll( | ||
{ sut shouldBe other }, | ||
{ sut shouldBeSameInstanceAs other }, | ||
{ sut shouldBeEqualComparingTo other } | ||
) | ||
} | ||
|
||
@DisplayName("로또 번호 값으로 비교할 수 있다") | ||
@Test | ||
fun compare() { | ||
listOf( | ||
1 to 1, | ||
2 to 0, | ||
3 to -1, | ||
).map { (compareTo, compareResult) -> | ||
{ | ||
val sut = LottoNumber.of(2) | ||
val other = LottoNumber.of(compareTo) | ||
|
||
sut.compareTo(other) shouldBe compareResult | ||
} | ||
} | ||
} | ||
} |
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.
마크다운 체크박스 기능을 이용하면 작업 내용을 중간중간 체크하기 용이합니다 😄