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등) 구현 #948

Open
wants to merge 13 commits into
base: kdh85
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
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,4 +137,36 @@
* 총 수익율을 계산하는 책임.
* 수식 : 당첨금액 / 구매비용
* 소수 2째자리까지만 허용.
* 버림 처리.
* 버림 처리.

# 로또(2등)
## 기능 요구사항
* 2등을 위해 추가 번호를 하나 더 추첨한다.
* 당첨 통계에 2등도 추가해야 한다.
![img_1.png](img_1.png)


## 프로그래밍 요구 사항
* 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외
* Enum 클래스를 적용해 프로그래밍을 구현한다.
* 일급 컬렉션을 쓴다.
* 일급 컬렉션을 사용하는 이유
* indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.
* 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
* 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
* 기능을 구현하기 전에 README.md 파일에 구현할 기능 목록을 정리해 추가한다.
* git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가한다.

## 요구사항 정리
* 보너스 번호를 추가 입력받는다.
* 이때 번호는 1~45 중에 하나인지를 검증해야 한다.
* 이미 작성된 당첨 번호 6개와 중복되지 않아야 한다.
* Rank에서 보너스 당첨에 해당하는 상금 정보를 추가해야 한다.
* 당첨 로또 객체를 구현
* 6자리의 기존 로또 객체를 보유
* 보너스 번호 1자리를 보유
* 당첨 로또에서 구매한 로또들을 비교하여 당첨여부를 알아내야 한다.
* 기존 로또 객체와의 비교
* 보너스 번호와의 비교
* 기존로또 객체가 5개 일치 할 때 보너스 여부를 판단
* 보너스가 참이면 2등보너스, 거짓이면 2등처리
Binary file added img_1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 11 additions & 6 deletions src/main/kotlin/lotto/Main.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package lotto

import lotto.domain.LotteryResult
import lotto.domain.Lotto
import lotto.domain.LottoBundle
import lotto.domain.LottoNumber
import lotto.domain.LottoRecords
import lotto.domain.Purchase
import lotto.domain.StringSplit
import lotto.enums.Rank
import lotto.domain.WinningLotto
import lotto.service.AutoNumberCreateStrategy
import lotto.view.InputView
import lotto.view.OutputView
Expand All @@ -26,10 +27,14 @@ fun main() {
val winningNumbers = StringSplit.makeNumbersBySplit(
InputView.inputWinningLottoNumbers()
)
val winningLotto = Lotto.from(winningNumbers)

val winningLotto = WinningLotto(
Lotto.from(winningNumbers),
LottoNumber.from(InputView.inputBonusNumber())
)
val recordsByRank = winningLotto.matchByRank(lottoBundle)
val lottoRecords = LottoRecords.fromRank()
// 당첨 통계 출력
val rankResult = LotteryResult.from(Rank.records())
rankResult.makeRankResult(purchase.amount, winningLotto, lottoBundle.bundle)
OutputView.printLotteryResult(rankResult)
OutputView.printWinningStatistics(lottoRecords.calculateRecords(recordsByRank))
OutputView.printRate(lottoRecords.calculateRateOfReturn(purchase.amount))
}
47 changes: 0 additions & 47 deletions src/main/kotlin/lotto/domain/LotteryResult.kt

This file was deleted.

53 changes: 32 additions & 21 deletions src/main/kotlin/lotto/domain/Lotto.kt
Original file line number Diff line number Diff line change
@@ -1,39 +1,50 @@
package lotto.domain

class Lotto private constructor(
private val lottoNumbers: Set<LottoNumber> = HashSet()
) {
import lotto.enums.Rank

val lotto: Set<LottoNumber> = lottoNumbers
class Lotto(
val lottoNumbers: Set<LottoNumber> = HashSet()
) {
init {
require(lottoNumbers.size == MAX_NUMBERS_COUNT) {
"로또를 올바르게 생성하려면 반드시 6개의 번호를 넣어주세요."
}

fun makeMatchCountByNumbers(winningLotto: Lotto): Int {
var count = 0
winningLotto.lotto.forEach { winNumber ->
count += countByMatchNumbers(winNumber)
require(lottoNumbers.groupBy { it }.size == 6) {
"중복된 숫자를 제외한 6개의 숫자를 입력해 주세요"
}
return count
}

private fun countByMatchNumbers(winNumber: LottoNumber): Int {
return lotto.count {
it == winNumber
fun makeMatchCountByNumbers(lotto: Lotto): Int {
return this.lottoNumbers.intersect(lotto.lottoNumbers)
.count()
}


fun matchCounts(bundle: List<Lotto>): List<Int> {
val counts = mutableListOf<Int>()
bundle.forEach { lotto ->
val countByNumbers = lotto.makeMatchCountByNumbers(this)//0~6개

val rank = Rank.values().firstOrNull {
it.matchCount == countByNumbers
} ?: Rank.NONE_RANK

counts.add(countByNumbers)
}
return counts
Comment on lines +24 to +35
Copy link
Member

Choose a reason for hiding this comment

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

로또 티켓이 당첨인지에 대한 검증은 당첨 티켓이 갖게해보는 건 어떨까요?

현실 세계에서는 사람이 당첨번호를 보고, 티켓이 몇등인지 알아내지만,
당첨 티켓에게 비교할 로또를 주면서 어떤 결과인지 메시지를 던져 물어보는 건 컴퓨터의 세계에서 자연스러운 일이라 생각해요!

}

fun isMatchBonus(bonusNumber: LottoNumber): Boolean {
return lottoNumbers.contains(bonusNumber)
Comment on lines +38 to +39
Copy link
Member

Choose a reason for hiding this comment

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

"보너스 번호"를 가지고 있는지는 로또 티켓의 관심사일까요?
로또 티켓은 특정 번호를 가지고있는지 정도의 역할만 가지고 있어도 충분해보여요!
특별히 보너스 번호라는 관심을 갖지 않게, hasNumber 같은 이름으로 변경해보는 건 어떨까요?

}


companion object {

private const val MAX_NUMBERS_COUNT = 6

fun from(numbers: List<Int>): Lotto {

require(numbers.size == MAX_NUMBERS_COUNT) {
"로또를 올바르게 생성하려면 반드시 6개의 번호를 넣어주세요."
}

require(numbers.groupBy { it }.size == 6) {
"중복된 숫자를 제외한 6개의 숫자를 입력해 주세요"
}

val lottoNumbers = numbers.map {
LottoNumber.from(it)
}.toSet()
Expand Down
20 changes: 10 additions & 10 deletions src/main/kotlin/lotto/domain/LottoBundle.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
package lotto.domain

import lotto.enums.Rank
import lotto.service.NumberCreateStrategy

class LottoBundle(
lottos: List<Lotto> = listOf()
val bundle: List<Lotto>
) {
fun findAllByMatchRanks(winningLotto: WinningLotto): List<Rank> {
val countAndBonusByRank = mutableListOf<Rank>()

val bundle: List<Lotto> = lottos

fun showAllPurchaseLottoNumbers(): List<List<Int>> {
return List(bundle.size) { index ->
bundle[index].lotto.map { lottoNumber ->
lottoNumber.number
}.sortedBy {
it
}
bundle.forEach { lotto ->
val countByNumbers = lotto.makeMatchCountByNumbers(winningLotto.winningLotto)//0~6개
val isHaveBonus = lotto.isMatchBonus(winningLotto.bonusNumber)
val findRank = Rank.valueOf(countByNumbers, isHaveBonus)
countAndBonusByRank.add(findRank)
}
return countAndBonusByRank
}

companion object {
Expand Down
4 changes: 1 addition & 3 deletions src/main/kotlin/lotto/domain/LottoNumber.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package lotto.domain

class LottoNumber private constructor(
private val lottoNumber: Int = 0
val lottoNumber: Int = 0
) {
var number = lottoNumber
private set

companion object {
private const val MINIMUM_LOTTO_NUMBER: Int = 1
Expand Down
33 changes: 33 additions & 0 deletions src/main/kotlin/lotto/domain/LottoRecord.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package lotto.domain

import lotto.enums.Rank

class LottoRecord(
val rank: Rank,
quantity: Int = 0,
) {
Comment on lines +5 to +8
Copy link
Member

Choose a reason for hiding this comment

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

기록에 대한 객체를 만든뒤 거기에 계속 수를 더하는 가변 객체를 만들기 보다,
계산이 모두 끝난 뒤 마지막 snapshot을 저장하도록 불변 객체로 만들어보는 건 어떨까요?
기록 객체가 중간에 참조되어서 수정되는 것을 방지할 수 있겠어요 :)

init {
require(quantity >= 0) {
"등급별 수량은 0이상 입니다."
}
}

var quantity = quantity
private set
Comment on lines +9 to +16
Copy link
Member

Choose a reason for hiding this comment

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

멤버변수는 생성자 보다 위에 위치하는 것이 코틀린 컨벤션입니다 :)


fun matchCount(): Int {
return rank.matchCount
}

fun addQuantityByCount(count: Int) {
quantity += count
}

fun reward(): Int {
return rank.reward
}

fun totalReward(): Int {
return rank.reward * quantity
}
}
43 changes: 43 additions & 0 deletions src/main/kotlin/lotto/domain/LottoRecords.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package lotto.domain

import lotto.enums.Rank
import kotlin.math.floor

class LottoRecords(
private val lottoRecords: Set<LottoRecord>
) {
fun calculateRecords(recordsByRank: List<Rank>): Set<LottoRecord> {
lottoRecords.forEach { lottoRecord ->
addQuantityByRank(recordsByRank, lottoRecord)
}
return lottoRecords
}

private fun addQuantityByRank(
recordsByRank: List<Rank>,
lottoRecord: LottoRecord
) {
val matchCount = recordsByRank.count {
it == lottoRecord.rank
}
lottoRecord.addQuantityByCount(matchCount)
}

fun calculateRateOfReturn(amount: Int): Double {
val sumTotalReward = lottoRecords.sumOf {
it.totalReward()
}.toDouble()
return floor(sumTotalReward / amount * 100.0) / 100.0
}

companion object {

fun fromRank(): LottoRecords {
val lottoRecords = mutableSetOf<LottoRecord>()
Rank.values().forEach {
lottoRecords.add(LottoRecord(it))
}
return LottoRecords(lottoRecords)
}
}
}
36 changes: 0 additions & 36 deletions src/main/kotlin/lotto/domain/Record.kt

This file was deleted.

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

import lotto.enums.Rank

class WinningLotto(
val winningLotto: Lotto,
val bonusNumber: LottoNumber
) {
init {
require(!winningLotto.lottoNumbers.contains(bonusNumber)) {
"로또 번호와 보너스번호는 중복이 될 수 없습니다. 중복된 번호 : $bonusNumber"
}
}

fun matchByRank(bundle: LottoBundle): List<Rank> {
return bundle.findAllByMatchRanks(this)
}
}
Loading