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

[step3] 로또 2등 #998

Open
wants to merge 32 commits into
base: celine-yerim
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
14e5ae7
feat: 문자열 계산기 기능 구현
Nov 9, 2023
ed1c53b
test: 문자열 계산기 테스트 작성
Nov 9, 2023
67bfa77
docs: 요구사항 작성
Nov 9, 2023
378fc2b
feat: 코드 수정
Nov 9, 2023
b451d7f
feat: 입력 값 추가
Nov 12, 2023
7ae5bc3
feat: 결과 값 추가
Nov 12, 2023
f5bc3d3
feat: 로또 실행 추가
Nov 12, 2023
e4fe855
docs: 로또 readme 추가
Nov 12, 2023
7c0d888
feat: 로또 print 추가
Nov 13, 2023
8d16723
test: 로또 test 추가
Nov 13, 2023
9874e32
feat: 입력 값 수정
Nov 13, 2023
2964592
feat: 출력 값 수정
Nov 13, 2023
58b2f89
feat: 로또 수정
Nov 13, 2023
516f215
test: 로또 테스트 수정
Nov 13, 2023
cd93689
docs: 로또 readme 수정
Nov 13, 2023
d950581
feat: 코드리뷰 수정
Nov 16, 2023
dc68610
docs: readme 이동
Nov 16, 2023
ae9e7f0
style: 패키지 이동
Nov 16, 2023
5f64c54
style: 패키지 이동
Nov 20, 2023
c156b0f
feat: 리팩토링 - input, result 리팩토링
Nov 26, 2023
b1d52e3
feat: 리팩토링 - Lotto, LottoResult
Nov 26, 2023
b1a8de9
test: 로또 test 수정
Nov 26, 2023
af1ce8e
Merge branch 'lotto-auto' into lotto-second-place
Nov 26, 2023
7a086ec
feat: result 수정
Nov 26, 2023
dbc9835
Merge branch 'lotto-auto' into lotto-second-place
Nov 26, 2023
567da32
feat: 로또 수정
Nov 26, 2023
11b4ceb
test: 로또 테스트 수정
Nov 26, 2023
af1e32e
Merge remote-tracking branch 'origin/celine-yerim' into celine-yerim
Nov 26, 2023
0465f2b
Merge branch 'lotto-auto' into celine-yerim
Nov 26, 2023
0a8037d
feat: 리팩토링
Nov 28, 2023
e5bf420
test: 로또 테스트 수정
Nov 28, 2023
aad00c4
Merge remote-tracking branch 'origin/celine-yerim' into lotto-second-…
Nov 29, 2023
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
26 changes: 26 additions & 0 deletions src/main/kotlin/lotto/Lotto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package lotto

data class Lotto(
val numbers: Set<Int>,
) {
fun match(winningLotto: Lotto, bonus: Int): Pair<Int, Boolean> {
val count = numbers.intersect(winningLotto.numbers).count()
val hasBonus = if (count == 5) hasMatchBonusBall(bonus) else false
return count to hasBonus
}

private fun hasMatchBonusBall(bonus: Int): Boolean {
return numbers.contains(bonus)
}

companion object {
fun getAutoNumbers(): Lotto {
val numbers = (LOTTO_MIN_VALUE..LOTTO_MAX_VALUE).toList().shuffled().take(LOTTO_COUNT).sorted().toSet()
return Lotto(numbers)
}

private const val LOTTO_MIN_VALUE = 1
private const val LOTTO_MAX_VALUE = 45
private const val LOTTO_COUNT = 6
}
}
26 changes: 26 additions & 0 deletions src/main/kotlin/lotto/LottoApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package lotto

import lotto.view.InputType
import lotto.view.InputView
import lotto.view.ResultView

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

val amount = inputView.inputNumber(InputType.PURCHASE_AMOUNT)
val count = LottoGame().getLottoCount(amount)

resultView.printCount(count)

val lottoList = lottoGame.getLottoList(count)
resultView.printLottoList(lottoList)

val winningLotto = inputView.inputWinningLotto()
val bonus = inputView.inputNumber(InputType.BONUS_BALL)

val result = lottoGame.result(lottoList, winningLotto, bonus)
val totalWinningMoney = lottoGame.getTotalWinningMoney(result)
resultView.printResult(amount, result, totalWinningMoney)
}
28 changes: 28 additions & 0 deletions src/main/kotlin/lotto/LottoGame.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package lotto

class LottoGame {

fun getLottoCount(amount: Int): Int {
return amount / PRICE_PER_LOTTO
}

fun getLottoList(count: Int): List<Lotto> {
return mutableListOf<Lotto>().apply {
repeat(count) { this.add(Lotto.getAutoNumbers()) }
}
}

fun result(lottoList: List<Lotto>, winningLotto: Lotto, bonus: Int): Map<Rank, Int> {
return lottoList.mapNotNull { lotto ->
lotto.match(winningLotto, bonus).let { (count, hasBonus) -> Rank.valueOf(count, hasBonus) }
}.groupingBy { it }.eachCount()
}

fun getTotalWinningMoney(result: Map<Rank, Int>): Int {
return result.toList().sumOf { it.first.getWinningMoney(it.second) }
}

companion object {
private const val PRICE_PER_LOTTO = 1000
}
}
16 changes: 16 additions & 0 deletions src/main/kotlin/lotto/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# kotlin-lotto

## 기능 요구 사항
- 로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급해야 한다.
- 로또 1장의 가격은 1000원이다.
- 2등을 위해 추가 번호를 하나 더 추첨한다.
- 당첨 통계에 2등도 추가해야 한다.

## 기능 목록
- [x] 구입 금액 입력
- [x] 구입 금액에 해당하는 로또 개수 결과 출력
- [x] 구입 금액에 해당하는 로또 발급
- [x] 지난주 당첨 번호 입력
- [x] 당첨 통계 출력
- [x] 2등을 위한 추가 번호 추첨
- [x] 2등 당첨 통계 출력
20 changes: 20 additions & 0 deletions src/main/kotlin/lotto/Rank.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package lotto

enum class Rank(val count: Int, private val winningMoney: Int) {
FIRST(6, 2000000000),
SECOND(5, 30000000),
THIRD(5, 1500000),
FOURTH(4, 50000),
FIFTH(3, 5000),
;

fun getWinningMoney(size: Int): Int {
return winningMoney * size
}

companion object {
fun valueOf(count: Int, matchBonus: Boolean): Rank? {
return values().find { if (count == 5 && matchBonus) it == SECOND else it.count == count }
}
}
}
6 changes: 6 additions & 0 deletions src/main/kotlin/lotto/view/InputType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package lotto.view
enum class InputType(val question: String) {
PURCHASE_AMOUNT("구입금액을 입력해 주세요."),
LAST_WEEK_WINNING_NUMBER("\n지난 주 당첨 번호를 입력해 주세요."),
BONUS_BALL("보너스 볼을 입력해 주세요."),
}
17 changes: 17 additions & 0 deletions src/main/kotlin/lotto/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package lotto.view

import lotto.Lotto
import lotto.view.InputType.LAST_WEEK_WINNING_NUMBER

class InputView {
fun inputNumber(inputType: InputType): Int {
println(inputType.question)
return readln().toInt()
}

fun inputWinningLotto(): Lotto {
println(LAST_WEEK_WINNING_NUMBER.question)
val numbers = readln().split(",").map { it.trim().toInt() }.toSet()
return Lotto(numbers)
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/lotto/view/ResultType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package lotto.view

import lotto.Rank

enum class ResultType(val message: String) {
PURCHASE_COUNT("%d개를 구매했습니다."),
PROFIT_RATE("총 수익률은 %.2f입니다.(기준이 1이기 때문에 결과적으로 손해라는 의미임)"),
}

enum class LottoMatchResult(val message: String, val rank: Rank) {
THREE_MATCH("3개 일치 (5000원) - %d개", Rank.FIFTH),
FOUR_MATCH("4개 일치 (50000원) - %d개", Rank.FOURTH),
FIVE_MATCH("5개 일치 (1500000원) - %d개", Rank.THIRD),
FIVE_AND_BONUS_MATCH("5개 일치, 보너스 볼 일치(30000000원) - %d개", Rank.SECOND),
SIX_MATCH("6개 일치 (2000000000원) - %d개", Rank.FIRST),
;
}
30 changes: 30 additions & 0 deletions src/main/kotlin/lotto/view/ResultView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package lotto.view

import lotto.Lotto
import lotto.Rank
import lotto.view.ResultType.PURCHASE_COUNT

class ResultView {

fun printCount(count: Int) {
println(PURCHASE_COUNT.message.format(count))
}

fun printLottoList(lottoList: List<Lotto>) {
lottoList.forEach {
println(it.numbers)
}
}

fun printResult(amount: Int, result: Map<Rank, Int>, totalWinningMoney: Int) {
println(INIT_RESULT)
LottoMatchResult.values().forEach {
println(it.message.format(result[it.rank] ?: 0))
}
println(ResultType.PROFIT_RATE.message.format(totalWinningMoney.toDouble() / amount))
}

companion object {
private const val INIT_RESULT = "\n당첨 통계\n---------"
}
}
19 changes: 9 additions & 10 deletions src/main/kotlin/stringsumcalculator/Calculator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,28 @@ package stringsumcalculator
class Calculator {

fun execute(text: String?): Int {
return if (text.isNullOrBlank()) {
0
} else {
getDestructured(text)?.let {
sum(it.component2(), it.component1())
} ?: sum(text, DEFAULT_DELIMITER)
if (text.isNullOrBlank()) {
return 0
}
return getDestructured(text)?.let { (delimiter, value) ->
sum(value, delimiter)
} ?: sum(text, DEFAULT_DELIMITER)
}

fun getDestructured(text: String): MatchResult.Destructured? {
private fun getDestructured(text: String): MatchResult.Destructured? {
return CUSTOM_SEARCH_PATTERN.find(text)?.destructured
}

fun sum(value: String, delimiter: String): Int {
return value.split(delimiter.toRegex()).sumOf {
it.toNumber()
it.toPositiveNumber()
}
}

private fun String.toNumber(): Int {
private fun String.toPositiveNumber(): Int {
val value = this.toIntOrNull()
if (value == null || value < 0) {
throw RuntimeException()
throw RuntimeException("숫자 이외의 값 또는 음수이므로 계산을 할 수 없습니다.")
}
return value
}
Expand Down
File renamed without changes.
56 changes: 56 additions & 0 deletions src/test/kotlin/lotto/LottoGameTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import lotto.Lotto
import lotto.LottoGame
import lotto.Rank
import org.assertj.core.api.Assertions.assertThat
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

class LottoGameTest {
private var lottoGame: LottoGame = LottoGame()

@DisplayName(value = "구입 금액에 해당하는 로또 개수 결과 출력")
@ParameterizedTest
@ValueSource(ints = [14000])
fun getLottoCount(count: Int) {
assertThat(lottoGame.getLottoCount(count)).isEqualTo(14)
}

@DisplayName(value = "로또 발급")
@ParameterizedTest
@ValueSource(ints = [1])
fun start(count: Int) {
assertThat(lottoGame.getLottoList(count).size).isEqualTo(count)
}

@DisplayName(value = "로또 결과")
@ParameterizedTest
@ValueSource(ints = [3, 4, 5, 6])
fun result(count: Int) {
val lottoList = listOf(Lotto.getAutoNumbers())
val winningLotto = Lotto(lottoList.first().numbers.take(count).toSet())
val result = lottoGame.result(lottoList, winningLotto, 0).toList()
result.first().let { (rank, count) ->
assertThat(rank.count).isEqualTo(count)
}
}

@DisplayName(value = "로또 결과 - 보너스 볼 일치")
@Test
fun resultBonus() {
val lottoList = listOf(Lotto.getAutoNumbers())
val winningLotto = Lotto(lottoList.first().numbers.take(5).toSet())
val bonus = lottoList.first().numbers.last()
val result = lottoGame.result(lottoList, winningLotto, bonus).toList()
assertThat(result.first().first).isEqualTo(Rank.SECOND)
}

@DisplayName(value = "로또 번호 범위 (1 ~ 45) 테스트")
@Test
fun rangeLottoNumbers() {
Lotto.getAutoNumbers().numbers.forEach {
assertThat(it).isIn(1..45)
}
}
}
15 changes: 7 additions & 8 deletions src/test/kotlin/stringsumcalculator/CalculatorTest.kt
Original file line number Diff line number Diff line change
@@ -1,35 +1,34 @@
package stringsumcalculator

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
import org.junit.jupiter.params.provider.ValueSource

class CalculatorTest {
private val calculator = Calculator()

@ParameterizedTest
@CsvSource(value = ["=0", "1=1", "1,2=3", "1,2,3=6", "1,2:3=6"], delimiter = '=')
fun `쉼표 또는 콜론을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리`(value: String?, result: Int) {
val sumValue = if (!value.isNullOrBlank()) {
Calculator().sum(value, ",|:")
calculator.sum(value, ",|:")
} else 0
assertThat(sumValue).isEqualTo(result)
}

@Test
fun `문자를 커스텀 구분자로`() {
fun `문자를 커스텀 구분자로 사용해서 계산`() {
val text = "//;\n1;2;3"
val result = Calculator().getDestructured(text)?.let {
Calculator().sum(it.component2(), it.component1())
}
val result = calculator.execute(text)
assertThat(result).isEqualTo(6)
}

@ValueSource(strings = ["//;\n1;2;-3", "1,2,a"])
@ParameterizedTest
fun `문자열 계산기에 숫자 이외의 값 또는 음수를 전달`(value: String) {
assertThrows(RuntimeException::class.java) { Calculator().execute(value) }
fun `문자열 계산기에 숫자 이외의 값 또는 음수를 전달 후 RuntimeException`(value: String) {
assertThrows<RuntimeException> { calculator.execute(value) }
}
}