-
Notifications
You must be signed in to change notification settings - Fork 92
[그리디] 서현진 로또 미션 3, 4, 5단계 제출합니다. #145
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
base: main
Are you sure you want to change the base?
Changes from all commits
8ba883a
8ae5422
8dd8277
4c65cb7
128dee6
daa8759
28b7703
f63efd3
52ef26b
af6c2f5
dfb1e77
bcdad0a
ab6a0ee
201caa7
04a10e1
165964f
70d4cbb
68ce019
1e7dc53
793e9a3
2358d22
29060b4
a472ddf
1bc5b7d
bb86d4f
4659d1a
46091b5
53d1b38
28ce4b9
ee3d545
517dcee
a8751ae
6cc626c
43b220a
418292f
166a97a
fbcd282
52e393b
dae1be8
c341fa7
acc8b99
36937b0
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,66 @@ | ||
# Java Lotto Clean Playground | ||
|
||
## 📖 개요 | ||
|
||
## ✨ 주요 기능 | ||
|
||
- **로또 구매**: 입력한 금액에 맞춰 1,000원 단위로 로또를 구매합니다. | ||
- **수동/자동 구매**: 원하는 개수만큼 로또 번호를 직접 선택하는 수동 구매와 나머지 개수를 자동으로 발급받는 기능을 지원합니다. | ||
- **구매 내역 출력**: 구매한 모든 로또(수동, 자동)의 번호를 출력합니다. | ||
- **당첨 통계**: 사용자가 입력한 당첨 번호 및 보너스 번호를 기반으로 당첨 내역을 계산합니다. | ||
- 1등: 6개 번호 일치 | ||
- 2등: 5개 번호 + 보너스 번호 일치 | ||
- 3등: 5개 번호 일치 | ||
- 4등: 4개 번호 일치 | ||
- 5등: 3개 번호 일치 | ||
- **수익률 계산**: 총 구매 금액 대비 당첨금 총액을 기반으로 한 수익률을 소수점 둘째 자리까지 계산하여 출력합니다. | ||
|
||
``` | ||
. | ||
└── src | ||
└── main | ||
└── java | ||
├── Application.java # 애플리케이션 시작점 | ||
├── Controller | ||
│ └── LottoController.java # 게임 흐름 제어 | ||
├── Model | ||
│ ├── Lotto.java # 로또 1장 | ||
│ ├── Lottos.java # 발급된 전체 로또 | ||
│ ├── Jackpot.java # 당첨 번호와 보너스 번호 | ||
│ ├── LottoMatcher.java # 로또 당첨 여부 확인 및 통계 계산 | ||
│ ├── PrizeMoney.java # 등수별 상금 Enum | ||
│ ├── AutoLotto.java # 자동 로또 번호 생성 | ||
│ └── ManualLotto.java # 수동 로또 번호 관리 | ||
└── View | ||
├── InputView.java # 사용자 입력 처리 | ||
└── OutView.java # 결과 출력 담당 | ||
|
||
``` | ||
|
||
## ⚙️ 핵심 로직 상세 | ||
|
||
### 1. 로또 생성 (`Lottos.java`) | ||
|
||
- `Lottos` 클래스는 사용자가 구매한 모든 로또 티켓을 관리합니다. | ||
- `InputView`를 통해 받은 수동 구매 개수와 번호 문자열 리스트를 `ManualLotto`를 통해 `Lotto` 객체 리스트로 변환합니다. | ||
- 총 구매 개수에서 수동 구매 개수를 뺀 만큼 `AutoLotto`를 통해 자동으로 로또 번호를 생성합니다. | ||
- `AutoLotto`는 `Collections.shuffle`을 활용하여 1부터 45까지의 숫자 중 6개를 무작위로 선택하여 중복 없는 번호를 생성합니다. | ||
|
||
### 2. 당첨 확인 (`LottoMatcher.java`) | ||
|
||
`LottoMatcher`는 로또 게임의 당첨 결과를 판별하고 통계를 내는, 가장 중요한 비즈니스 로직을 담당하는 클래스입니다. 객체가 생성되는 시점에 모든 핵심 연산을 수행하여 내부에 결과를 저장하고, 외부에는 요청에 따라 계산된 결과를 즉시 제공하는 방식으로 설계되었습니다. | ||
|
||
#### 동작 원리 | ||
|
||
1. **생성 및 초기화**: `LottoController`가 `new LottoMatcher(lottos, jackpot)` 코드로 객체를 생성하면, `LottoMatcher`의 생성자가 실행되며 모든 계산이 즉시 이루어집니다. | ||
- **데이터 준비**: 생성자는 사용자가 구매한 모든 로또 티켓 묶음(`lottos`)과 당첨 번호 정보(`jackpot`)를 인자로 받습니다. 그리고 당첨 통계를 저장할 `matchCounts`라는 `Map`을 초기화합니다. 이 맵은 `{일치 개수: 당첨된 티켓 수}` 형태로 통계를 저장하게 됩니다. | ||
- **개별 티켓 비교**: `for` 루프를 통해 구매한 로또 티켓을 한 장씩 순회하며 각 티켓별로 당첨 등수를 판별합니다. | ||
- 먼저 해당 티켓의 번호 6개가 당첨 번호 6개와 몇 개나 일치하는지 계산합니다. | ||
- 만약 일치 개수가 정확히 **5개**라면, 2등 당첨 가능성이 생깁니다. 이때, 해당 로또 티켓이 **보너스 번호를 포함하고 있는지** 추가로 확인하여 2등과 3등을 구분합니다. | ||
- 2등(5개 일치 + 보너스 번호 포함)으로 확인되면, `matchCounts` 맵에 `7`이라는 특별한 키값으로 당첨 횟수를 1 증가시킵니다. | ||
- 그 외의 경우(3, 4, 5, 6개 일치), 계산된 일치 개수를 키로 사용하여 `matchCounts` 맵의 카운트를 1 증가시킵니다. | ||
|
||
2. **결과 제공**: 모든 계산이 완료된 `LottoMatcher` 객체는 `LottoController`의 요청에 따라 아래 메소드들을 통해 결과를 제공합니다. | ||
- `getMatchCounts()`: 모든 계산이 완료된 `matchCounts` 맵을 반환합니다. `OutView`는 이 맵을 받아 최종 당첨 통계를 화면에 출력합니다. | ||
- `calculateTotalEarnings()`: `matchCounts` 맵과 `PrizeMoney` Enum을 기반으로 총상금을 계산합니다. | ||
- `getRate()`: `calculateTotalEarnings()`로 계산된 총상금을 사용자의 총 구매 금액으로 나누어 최종 수익률을 계산합니다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import Controller.LottoController; | ||
|
||
public class Application { | ||
public static void main(String[] args) { | ||
LottoController lottoController = new LottoController(); | ||
lottoController.startLotto(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package Controller; | ||
|
||
import Model.*; | ||
import View.InputView; | ||
import View.OutView; | ||
|
||
import java.util.List; | ||
|
||
public class LottoController { | ||
public void startLotto() { | ||
InputView inputView = new InputView(); | ||
OutView outView = new OutView(); | ||
|
||
|
||
int money = getValidMoney(inputView, outView); | ||
|
||
outView.getManualLottoCountHelp(); | ||
int manualLottoCount = inputView.getManualLottoCount(); | ||
|
||
outView.promptForManualLotto(); | ||
Lottos lottos = new Lottos(); | ||
|
||
inputView.resetBuffer(); | ||
for (int i = 0; i < manualLottoCount; i++) { | ||
Lotto validManualLotto = getValidManualLotto(inputView); | ||
lottos.addLotto(validManualLotto); | ||
} | ||
|
||
int autoLottoCount = lottos.getAutoLottoCount(money, manualLottoCount); | ||
lottos.makeAutoLotto(autoLottoCount); | ||
|
||
outView.printLottosStaus(autoLottoCount, manualLottoCount); | ||
|
||
outView.printLottos(lottos); | ||
|
||
outView.promptForJackpot(); | ||
String[] winningNumber = inputView.getJackpotNumber(); | ||
|
||
outView.promptForBonus(); | ||
int bonusNumber = inputView.getBonus(); | ||
|
||
try { | ||
Jackpot jackpot = new Jackpot(winningNumber, bonusNumber); | ||
|
||
LottoMatcher lottoMatcher = new LottoMatcher(lottos, jackpot); | ||
|
||
outView.printLottoMatcher(lottoMatcher.getMatchCounts(), lottoMatcher.getRate(money)); | ||
} catch (IllegalArgumentException e) { | ||
System.err.println("[ERROR] " + e.getMessage()); | ||
} | ||
|
||
} | ||
|
||
private int getValidMoney(InputView inputView, OutView outView) { | ||
while (true) { | ||
try { | ||
outView.getMoneyHelp(); | ||
int money = inputView.getMoney(); | ||
validate(money); | ||
return money; | ||
} catch (IllegalArgumentException e) { | ||
System.err.println("[ERROR] " + e.getMessage()); | ||
} | ||
} | ||
} | ||
|
||
private Lotto getValidManualLotto(InputView inputView){ | ||
while(true){ | ||
try{ | ||
return new ManualLotto(inputView.manualLotto()); | ||
} catch (IllegalArgumentException e) { | ||
System.err.println("[ERROR] " + e.getMessage()); | ||
} | ||
} | ||
} | ||
|
||
private void validate(int money) { | ||
if (money < 0) | ||
throw new IllegalArgumentException("금액이 0보다 작을 순 없습니다!"); | ||
if (money % 1000 != 0) | ||
throw new IllegalArgumentException("1000원 단위로 입력해주세요!"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package Model; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
public class AutoLotto extends Lotto { | ||
|
||
public AutoLotto() { | ||
List<Integer> numbers = new ArrayList<>(); | ||
|
||
for (int i = 1; i <= 45; i++) { | ||
numbers.add(i); | ||
} | ||
|
||
Collections.shuffle(numbers); | ||
this.numbers = new ArrayList<>(numbers.subList(0, 6)); | ||
|
||
Collections.sort(this.numbers); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package Model; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
public class Jackpot { | ||
private List<Integer> jackpot; | ||
private int bounsNumber; | ||
|
||
public Jackpot(String[] inputJackpot, int bounsNumber) { | ||
JackpotGenerator from = JackpotGenerator.from(inputJackpot); | ||
this.jackpot = from.getNumber(); | ||
validateBonusNumber(bounsNumber, this.jackpot); | ||
this.bounsNumber = bounsNumber; | ||
} | ||
|
||
private void validateBonusNumber(int bounsNumber, List<Integer> jackpot) { | ||
if (bounsNumber < 1 || bounsNumber > 45) { | ||
throw new IllegalArgumentException("보너스 번호는 1과 45 사이의 숫자여야 합니다."); | ||
} | ||
if (jackpot.contains(bounsNumber)) { | ||
throw new IllegalArgumentException("보너스 번호는 당첨 번호와 중복될 수 없습니다."); | ||
} | ||
} | ||
|
||
public List<Integer> getJackpot() { | ||
return Collections.unmodifiableList(jackpot); | ||
} | ||
|
||
public int getBounsNumber() { | ||
return bounsNumber; | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return jackpot.toString(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package Model; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Set; | ||
import java.util.TreeSet; | ||
|
||
public class JackpotGenerator { | ||
private final List<Integer> number; | ||
|
||
private JackpotGenerator(List<Integer> numbers) { | ||
this.number = numbers; | ||
} | ||
|
||
public static JackpotGenerator from(String[] splits) { | ||
Set<Integer> numbers = parseAndValidateNumbers(splits); | ||
return new JackpotGenerator(Collections.unmodifiableList(new ArrayList<>(numbers))); | ||
} | ||
|
||
private static Set<Integer> parseAndValidateNumbers(String[] splits) { | ||
if (splits.length != 6) { | ||
throw new IllegalArgumentException("당첨 번호는 6개를 입력해야 합니다."); | ||
} | ||
Set<Integer> numbers = new TreeSet<>(); | ||
for (String s : splits) { | ||
int number; | ||
try { | ||
number = Integer.parseInt(s.trim()); | ||
} catch (NumberFormatException e) { | ||
throw new IllegalArgumentException("당첨 번호는 숫자여야 합니다."); | ||
} | ||
validateNumberRange(number); | ||
if (!numbers.add(number)) { | ||
throw new IllegalArgumentException("당첨 번호는 중복될 수 없습니다."); | ||
} | ||
} | ||
return numbers; | ||
} | ||
|
||
private static void validateNumberRange(int number) { | ||
if (number < 1 || number > 45) { | ||
throw new IllegalArgumentException("당첨 번호는 1과 45 사이의 숫자여야 합니다."); | ||
} | ||
} | ||
|
||
public List<Integer> getNumber() { | ||
return number; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package Model; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
|
||
public class Lotto { | ||
protected List<Integer> numbers; | ||
|
||
public List<Integer> getLotto() { | ||
return Collections.unmodifiableList(numbers); | ||
} | ||
|
||
@Override | ||
public String toString() { | ||
return numbers.toString(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package Model; | ||
|
||
import java.util.HashMap; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
public class LottoMatcher { | ||
private final Map<Integer, Integer> matchCounts; | ||
|
||
public LottoMatcher(Lottos lottos, Jackpot jackpot) { | ||
this.matchCounts = new HashMap<>(); | ||
|
||
for (Lotto lotto : lottos) { | ||
int matches = countMatches(jackpot.getJackpot(), lotto.getLotto()); | ||
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. LottoMatcher의 역할이 lottos의 번호들과 Jackpot 번호를 대조해보는 것이라면, 이 작업 전체를 그냥 Lottos 객체 안에서 수행해도 되지 않을까요? Matcher가 꼭 분리되어 존재해야 하는 이유가 궁금합니다. 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. 과제 요구사항에서 하나의 클래스 안에선 메소드가 10개 이하를 유지하기 위해서 클래스를 분리해서 만들어보았습니다!! 또한 기능 상 Lottos가 정답과 비교하는 기능 까지 가지고 있으면 너무 많은 책임을 가지고 있는 것 같아서 matcher라는 클래스를 생성해보았습니다! |
||
checkBonus(jackpot, lotto, matches); | ||
} | ||
|
||
} | ||
|
||
private void checkBonus(Jackpot jackpot, Lotto lotto, int matches) { | ||
if (matches == 5 && lotto.getLotto().contains(jackpot.getBounsNumber())) { | ||
matchCounts.put(7, matchCounts.getOrDefault(7, 0) + 1); | ||
return; | ||
} | ||
matchCounts.put(matches, matchCounts.getOrDefault(matches, 0) + 1); | ||
} | ||
|
||
private int countMatches(List<Integer> jackpotNumbers, List<Integer> lottoNumbers) { | ||
int count = 0; | ||
for (int number : lottoNumbers) { | ||
count += getMatchValue(jackpotNumbers, number); | ||
} | ||
return count; | ||
} | ||
|
||
|
||
private int getMatchValue(List<Integer> jackpotNumbers, int number) { | ||
if (jackpotNumbers.contains(number)) { | ||
return 1; | ||
} | ||
return 0; | ||
} | ||
|
||
public Map<Integer, Integer> getMatchCounts() { | ||
return matchCounts; | ||
} | ||
|
||
public double calculateTotalEarnings() { | ||
|
||
return matchCounts.getOrDefault(3, 0) * PrizeMoney.THREE.getMoney() | ||
+ matchCounts.getOrDefault(4, 0) * PrizeMoney.FOUR.getMoney() | ||
+ matchCounts.getOrDefault(5, 0) * PrizeMoney.FIVE.getMoney() | ||
+ matchCounts.getOrDefault(7, 0) * PrizeMoney.BONUS.getMoney() | ||
+ matchCounts.getOrDefault(6, 0) * PrizeMoney.SIX.getMoney(); | ||
} | ||
|
||
public double getRate(int money) { | ||
return calculateTotalEarnings() / money; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
package Model; | ||
|
||
import java.util.*; | ||
import java.util.function.Consumer; | ||
|
||
// 1. 제네릭을 사용하여 Iterable<Lotto>로 타입 명시 | ||
public class Lottos implements Iterable<Lotto> { | ||
private List<Lotto> lottoList; | ||
private static final int COST_PER_TICKET = 1000; | ||
|
||
public Lottos() { | ||
this.lottoList = new ArrayList<>(); | ||
} | ||
|
||
|
||
public void addLotto (Lotto tmp){ | ||
lottoList.add(tmp); | ||
} | ||
|
||
public int getAutoLottoCount(int money,int manualLottosNumber) { | ||
return money / COST_PER_TICKET - manualLottosNumber; | ||
} | ||
|
||
public void makeAutoLotto(int autoLottosNumber){ | ||
for (int i = 0; i < autoLottosNumber; i++) { | ||
AutoLotto autoLotto = new AutoLotto(); | ||
lottoList.add(autoLotto); | ||
} | ||
} | ||
|
||
@Override | ||
public Iterator<Lotto> iterator() { | ||
return lottoList.iterator(); | ||
} | ||
} |
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.
Jackpot 생성작업을 Jackpot 안에 두지 않고 Generator로 분리하신 이유가 궁금해요!
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.
jackpot => jackpotgenerator + bounsNumber 를 관리하는 클래스
jackpotgenerator => 사용자로 부터 정답 로또를 입력받는 클래스
jackpot은 보너스 넘버까지 같이 관리하려고 나눴습니다!
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.
ManualLotto의 경우에는 숫자 검증, 중복 숫자 검증과 같이 객체를 만드는 데에 필요한 검증 작업과 숫자 생성 작업이 생성자 안에 들어가 있습니다.
반면 Jackpot은 생성자에서 진행하지 않고 검증 작업과 숫자 생성 작업을 Generator로 분리하셨지요. 두 객체의 생성 방법에 차이를 두신 이유가 궁금한 것입니다!
Uh oh!
There was an error while loading. Please reload this page.
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.
🔔 요거 답을 안 주셨어요!
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.
아아 죄송합니다!
다른 의미는 크게 없었고 정적팩토리 메소드를 한번 코드에 적용시켜 보고 싶어서 코드를 generator로 구현해봤습니다!