diff --git a/README.md b/README.md index a3a40656fe..4bf744d21b 100644 --- a/README.md +++ b/README.md @@ -10,5 +10,18 @@ + [x] 커스텀 구분자를 지정할 수 있도록 구현 + [x] 커스텀 구분자는 문자열 앞부분의 `//`와 `\n` 사이에 위치 + [x] 커스텀 구분자는 한 개 지정 가능 - + [] 커스텀 구분자와 기본 구분자를 함께 사용할 수 있음 - + [x] 구분자를 기준으로 분리한 각 숫자의 합을 반환 \ No newline at end of file + + [x] 커스텀 구분자와 기본 구분자를 함께 사용할 수 있음 + + [x] 구분자를 기준으로 분리한 각 숫자의 합을 반환 + +### 로또 (자동) + +[] 로또 자동 생성기 구현 + [x] 로또 한장의 가격은 1000원 + [x] 로또 1000원 단위로만 구매 가능 + [x] 로또는 1부터 45까지의 숫자 중 6개를 랜덤으로 뽑는다. + [x] 로또는 6개의 숫자가 모두 달라야 한다. + [x] 로또 한장은 6개의 숫자를 가진다. + [x] 로또 한장은 숫자를 오름차순으로 정렬한다. + [] 당첨 로또를 입력 받는다. + [] 당첨 로또와 구매한 로또를 비교하여 등수를 출력한다. + [] 당첨 로또와 구매한 로또를 비교하여 수익률을 출력한다. diff --git a/src/main/kotlin/calculator/RegexUtil.kt b/src/main/kotlin/calculator/RegexUtil.kt new file mode 100644 index 0000000000..254eadad7a --- /dev/null +++ b/src/main/kotlin/calculator/RegexUtil.kt @@ -0,0 +1,9 @@ +package calculator + +fun isNumber(number: String): Boolean { + return Regex("[0-9]+").matches(number) +} + +fun findCustomDelimiter(text: String): String { + return Regex("//(.)\n(.*)").find(text)?.groupValues?.get(1) ?: "" +} \ No newline at end of file diff --git a/src/main/kotlin/calculator/StringAddCalculator.kt b/src/main/kotlin/calculator/StringAddCalculator.kt index e17df93e62..8457f65b3b 100644 --- a/src/main/kotlin/calculator/StringAddCalculator.kt +++ b/src/main/kotlin/calculator/StringAddCalculator.kt @@ -1,34 +1,13 @@ package calculator -class StringAddCalculator { +object StringAddCalculator { fun add(text: String): Int { if (text.isBlank()) return 0 - if (text.matches(Regex("[0-9]+"))) { + if (isNumber(text)) { return text.toInt() } - val customDelimiterRegex = Regex("//(.)\n(.*)").find(text) - val customDelimiter = customDelimiterRegex?.groupValues?.get(1) ?: "" - val tokens = parseTokens(text, customDelimiter) - - return tokens.sum() - } - - private fun parseTokens(text: String, customDelimiter: String): List { - require(text.contains("-").not()) { "음수를 입력할 수 없습니다." } - require(text.contains(Regex("[,:${customDelimiter}]"))) { "숫자와 지정된 구분자만 입력할 수 있습니다." } - - if (customDelimiter.isNotBlank()) { - val replacedText = text.replace("//$customDelimiter\n", "") - return replacedText.split(Regex("[,:${customDelimiter}\n]")).map { - require(it.matches(Regex("[0-9]+"))) { "숫자와 지정된 구분자만 입력할 수 있습니다." } - it.toInt() - } - } - - return text.split(Regex("[,:\n]")).map { - require(it.matches(Regex("[0-9]+"))) { "숫자와 지정된 구분자만 입력할 수 있습니다." } - it.toInt() - } + val inputNumbers = StringParser().parseString(text) + return inputNumbers.sum() } } diff --git a/src/main/kotlin/calculator/StringParser.kt b/src/main/kotlin/calculator/StringParser.kt new file mode 100644 index 0000000000..b91056bdf6 --- /dev/null +++ b/src/main/kotlin/calculator/StringParser.kt @@ -0,0 +1,24 @@ +package calculator + +class StringParser { + fun parseString(text: String): List { + + val customDelimiter = findCustomDelimiter(text) + + require(text.contains("-").not()) { "음수를 입력할 수 없습니다." } + require(text.contains(Regex("[,:${customDelimiter}]"))) { "숫자와 지정된 구분자만 입력할 수 있습니다." } + + if (customDelimiter.isNotBlank()) { + val replacedText = text.replace("//$customDelimiter\n", "") + return replacedText.split(Regex("[,:${customDelimiter}\n]")).map { + require(isNumber(it)) { "숫자와 지정된 구분자만 입력할 수 있습니다." } + it.toInt() + } + } + + return text.split(Regex("[,:\n]")).map { + require(isNumber(it)) { "숫자와 지정된 구분자만 입력할 수 있습니다." } + it.toInt() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/Lotto.kt b/src/main/kotlin/lotto/Lotto.kt new file mode 100644 index 0000000000..0b57bdb06a --- /dev/null +++ b/src/main/kotlin/lotto/Lotto.kt @@ -0,0 +1,15 @@ +package lotto + +class Lotto( + val numbers: List = generateLottoNumbers(), + var matchCount: Int = 0 +) { + + companion object { + const val PRICE: Int = 1000 + } + + fun match(target: Lotto) { + matchCount = numbers.intersect(target.numbers).count() + } +} diff --git a/src/main/kotlin/lotto/LottoBuyer.kt b/src/main/kotlin/lotto/LottoBuyer.kt new file mode 100644 index 0000000000..b13a5dbf9c --- /dev/null +++ b/src/main/kotlin/lotto/LottoBuyer.kt @@ -0,0 +1,11 @@ +package lotto + +class LottoBuyer( + private val money: Int +) { + + fun buyLottoFrom(lottoStore: LottoStore): List { + require(money >= Lotto.PRICE) { "돈이 부족합니다." } + return lottoStore.sell(money) + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/LottoNumber.kt b/src/main/kotlin/lotto/LottoNumber.kt new file mode 100644 index 0000000000..e9062b889f --- /dev/null +++ b/src/main/kotlin/lotto/LottoNumber.kt @@ -0,0 +1,5 @@ +package lotto + +fun generateLottoNumbers(): List { + return (1..45).shuffled().take(6).sorted() +} diff --git a/src/main/kotlin/lotto/LottoStore.kt b/src/main/kotlin/lotto/LottoStore.kt new file mode 100644 index 0000000000..539937298c --- /dev/null +++ b/src/main/kotlin/lotto/LottoStore.kt @@ -0,0 +1,13 @@ +package lotto + +class LottoStore { + + fun sell(money: Int): List { + val count = money / Lotto.PRICE + val lottos = mutableListOf() + for (i in 1..count) { + lottos.add(Lotto()) + } + return lottos + } +} diff --git a/src/main/kotlin/lotto/Main.kt b/src/main/kotlin/lotto/Main.kt new file mode 100644 index 0000000000..c3e6c4fdac --- /dev/null +++ b/src/main/kotlin/lotto/Main.kt @@ -0,0 +1,29 @@ +package lotto + +fun main(args: Array) { + println("구매금액을 입력해주세요.") + var inputMoney = readLine()!!.toInt() + + val lottoBuyer = LottoBuyer(inputMoney) + val lottos = lottoBuyer.buyLottoFrom(LottoStore()) + + println(String.format("%s개를 구매했습니다.", lottos.size)) + lottos.forEach { lotto -> + println(lotto.numbers) + } + + println("지난 주 당첨 번호를 입력해주세요.") + val lastWeekWinningNumbers = readLine()!!.split(", ").map { it.toInt() } + + lottos.forEach { lotto -> + lotto.match(Lotto(lastWeekWinningNumbers)) + } + + println("당첨 통계") + println("---------") + println("3개 일치 (5000원) - ${lottos.count { it.matchCount == 3 }}개") + println("4개 일치 (50000원) - ${lottos.count { it.matchCount == 4 }}개") + println("5개 일치 (1500000원) - ${lottos.count { it.matchCount == 5 }}개") + println("6개 일치 (2000000000원) - ${lottos.count { it.matchCount == 6 }}개") + println("총 수익률은 ${lottos.size * Lotto.PRICE / inputMoney}입니다.") +} \ No newline at end of file diff --git a/src/test/kotlin/calculator/StringAddCalculatorTest.kt b/src/test/kotlin/calculator/StringAddCalculatorTest.kt index 62a548932a..c622d7d668 100644 --- a/src/test/kotlin/calculator/StringAddCalculatorTest.kt +++ b/src/test/kotlin/calculator/StringAddCalculatorTest.kt @@ -2,7 +2,6 @@ package calculator import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatExceptionOfType -import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.CsvSource @@ -11,24 +10,17 @@ import org.junit.jupiter.params.provider.ValueSource class StringAddCalculatorTest { - private lateinit var calculator: StringAddCalculator - - @BeforeEach - fun setUp() { - calculator = StringAddCalculator(); - } - @ParameterizedTest @EmptySource fun `빈 문자열 입력할 경우 0을 반환해야 한다`(text: String) { - val result = calculator.add(text) + val result = StringAddCalculator.add(text) assertThat(result).isEqualTo(0) } @ParameterizedTest @CsvSource(value = ["1,1", "2,2", "3,3"]) fun `숫자 하나를 문자열로 입력할 경우 해당 숫자를 반환한다`(text: String, expected: Int) { - val result = calculator.add(text) + val result = StringAddCalculator.add(text) assertThat(result).isEqualTo(expected) } @@ -36,27 +28,27 @@ class StringAddCalculatorTest { @ValueSource(strings = ["안녕", "$", "1,2[3"]) fun `잘못된 문자열을 입력할 경우 RuntimeException`(text: String) { assertThatExceptionOfType(RuntimeException::class.java) - .isThrownBy { calculator.add(text) } + .isThrownBy { StringAddCalculator.add(text) } .withMessageMatching("숫자와 지정된 구분자만 입력할 수 있습니다.") } @Test fun `음수를 입력하면 RuntimeException`() { assertThatExceptionOfType(RuntimeException::class.java) - .isThrownBy { calculator.add("-1") } + .isThrownBy { StringAddCalculator.add("-1") } .withMessageMatching("음수를 입력할 수 없습니다.") } @ParameterizedTest @ValueSource(strings = ["1,2:3"]) fun `쉼표 또는 콜론 구분자로 숫자를 입력할 경우 정상적으로 합을 반환한다`(text: String) { - val result = calculator.add(text) + val result = StringAddCalculator.add(text) assertThat(result).isEqualTo(6) } @ParameterizedTest @ValueSource(strings = ["//;\n1;2;3", "//<\n1<2<3", "//<\n1<2,3"]) fun `custom 구분자로 입력값을 구분`(text: String) { - assertThat(calculator.add(text)).isSameAs(6); + assertThat(StringAddCalculator.add(text)).isSameAs(6); } } diff --git a/src/test/kotlin/lotto/LottoStoreTest.kt b/src/test/kotlin/lotto/LottoStoreTest.kt new file mode 100644 index 0000000000..d2a74a8207 --- /dev/null +++ b/src/test/kotlin/lotto/LottoStoreTest.kt @@ -0,0 +1,26 @@ +package lotto + +import io.kotest.matchers.collections.haveSize +import io.kotest.matchers.should +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.CsvSource + +class LottoStoreTest { + + @ParameterizedTest + @CsvSource(value = ["1000,1", "2000,2", "3000,3"]) + fun `로또를 여라장 구매한다`(money: Int, expected: Int) { + val lotto = LottoStore().sell(money) + lotto should haveSize(expected) + } + + @Test + fun `로또를 구매할 돈이 부족하면 예외가 발생한다`() { + val lottoStore = LottoStore() + val money = 900 + val lotto = lottoStore.sell(money) + lotto shouldBe emptyList() + } +} diff --git a/src/test/kotlin/lotto/LottoTest.kt b/src/test/kotlin/lotto/LottoTest.kt new file mode 100644 index 0000000000..ad0ecc4042 --- /dev/null +++ b/src/test/kotlin/lotto/LottoTest.kt @@ -0,0 +1,69 @@ +package lotto + +import io.kotest.matchers.collections.shouldBeOneOf +import io.kotest.matchers.ints.shouldBeInRange +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +class LottoTest { + + @Test + fun `로또는 6자리 숫자를 가진다`() { + val lotto = Lotto() + lotto.numbers.size shouldBe 6 + } + + @Test + fun `로또는 1부터 45까지의 숫자를 가진다`() { + val lotto = Lotto() + lotto.numbers.forEach { + it.shouldBeInRange(1..45) + } + } + + @Test + fun `로또는 중복되지 않는 숫자를 가진다`() { + val lotto = Lotto() + lotto.numbers.toSet().size shouldBe 6 + } + + @Test + fun `로또는 숫자는 오름차순 정렬`() { + val lotto = Lotto() + lotto.numbers shouldBe lotto.numbers.sorted() + } + + @Test + fun `로또 숫자는 중복되지 않는다`() { + val lotto = Lotto() + lotto.numbers.forEach { + it shouldBeOneOf lotto.numbers + } + } + + @Test + fun `test lotto matching`() { + + val targetLottoNumbers = listOf(1, 2, 3, 4, 5, 6) + val targetLotto = Lotto(targetLottoNumbers) + + val countAndLotto = listOf( + Pair(1, listOf(1, 7, 8, 9, 10, 11)), + Pair(2, listOf(1, 2, 8, 9, 10, 11)), + Pair(3, listOf(1, 2, 3, 9, 10, 11)), + Pair(4, listOf(1, 2, 3, 4, 10, 11)), + Pair(5, listOf(1, 2, 3, 4, 5, 11)), + Pair(6, listOf(1, 2, 3, 4, 5, 6)) + ) + + + for ((expectedMatchCount, lottoNumbers) in countAndLotto) { + + val lotto = Lotto(lottoNumbers) + targetLotto.match(lotto) + + val actualMatchCount = targetLotto.matchCount + actualMatchCount shouldBe expectedMatchCount + } + } +}