GitHub

https://github.com/Backcoder-June

BackCoder 기록 그리고 숙달

Coding Test

2022 우테코 3rd Week java-Lotto 리뷰

Backcoder 2022. 11. 11. 23:24

2022 우테코에서 진행하는 공개된 사전과제 코딩테스트 문제 입니다.

 

https://github.com/Backcoder-June/java-lotto

 

GitHub - Backcoder-June/java-lotto: 로또 미션을 진행하는 저장소

로또 미션을 진행하는 저장소. Contribute to Backcoder-June/java-lotto development by creating an account on GitHub.

github.com

( 상세한 문제 설명은 링크를 타고 들어가서 확인해주세요! )

 

 

Lotto Application

로또 프로그램을 Console 에서 구현해 보는 미션이었습니다. 

전체적으로 제한사항 및 요구사항이 많았고, 자료구조를 다양하게 활용해 볼 수 있어서 많은 도움이 됬던 과제였습니다.

 

=> 메소드 당, 15라인 이내에서 작은 기능 단위별로 구분하고, 이를 적절하게 연결하여 프로그램을 만들 수 있는가

=> 사용자의 입력값을 받아, 기능별로 적절한 자료구조를 활용할 수 있는가

=> 자료구조를 sort 정렬하는 다양한 방법에 대한 이해 

=>  RegEx, Exception 을 통해 상황에 따른 ERROR 처리를 할 수 있는가 

 

이번 우테코 과제에서는 

전체적으로 메소드들을 최대한 작은 단위로 짤라서 구현하게끔 유도해주었는데 ( 메소드 별 Max 15 Lines 제한 )

이 과정에서

 

기능별로 나누는 포인트를 잡는 법,

나눠진 메소드들을 효율적으로 연결 하는 법, 

만들어 둔 메소드를 다른 기능에 재활용 하는 법, 

전체적 흐름을 코드 라인을 따라가면서 파악하는게 아니라

method 흐름을 따라가면서 파악하는 법  

 

등등, 확실히 많은 부분을 배울 수 있었고,  왜 그렇게 객체지향을 추구해야 하는지 다시한번 생각하게 되었습니다. 

 

 

[ 구현 Methods ] 

 

(1) 기본 주어진 Lotto class 및 validate 활용

- Lotto 클래스가 기본적으로 List<Integer> numbers 를 가지고있고, validate 까지 실행시키고 있다.

=> 즉 랜덤한 로또번호를 만들고, 그 값으로 Lotto 클래스를 생성하면 validate 까지 시켜주게 된다. 

validate 메소드에는 프로그램 오류로 발생할 수 있는 ERROR 들을 추가해 주었다. 

public class Lotto {
    private final List<Integer> numbers;

    public Lotto(List<Integer> numbers) {
            validate(numbers);
        this.numbers = numbers;
    }

    private void validate(List<Integer> numbers) {
        if (numbers.size() != 6) {
            throw new IllegalArgumentException("[ERROR] 로또 번호는 총 6개를 입력해야 합니다.");
        }
        if (numbers.size() == 6 && convertListToSet(numbers).size() != 6) {
            throw new IllegalArgumentException("[ERROR] 로또 번호는 중복될 수 없습니다.");
        }
        for (Integer eachNum:numbers
             ) {
            if (eachNum < 1 || 45 < eachNum) {
                throw new IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
            }
        }
    }

 

(2) 로또번호 생성 및 Sort 정렬 

-  주어진 Randoms 메소드를 이용해 Lotto 클래스를 생성한다. 

=> 순서가 랜덤하게 되어있다.

- API 에서는 Collections.sort 등을 활용하면 정렬되고 눈에 보이는 오류 없이 실행이 되었다.

하지만 testCode 를 돌려보면 Collections.sort 부분에서 UnsupportedOperationException 이 발생하며 실패했다.

주로 List 형을 new로 초기화하지 않는 상태에서 생성하였을 시 발생한다고 하는데, 

new ArrayList 로 리스트를 생성한게 아니라, new Lotto로 생성하면서 발생하는 문제인 듯 하다.

 

=> TreeSet 을 이용하는 방법을 택했다. 

 

직접적으로 lotto의 데이터에 손을대고 sort 하는게 아니라, 그저 TreeSet에 담을 뿐,

TreeSet에는 자동으로 정렬되며 담기기 때문에 sort 를 사용하지 않아도 된다.

public static List<Lotto> getLottoNum(Integer lottoCount){
    List<Lotto> lottoNumList = new ArrayList<>();
    for (int i = 0; i < lottoCount; i++) {
        Lotto lotto = new Lotto(Randoms.pickUniqueNumbersInRange(1, 45, 6));
        Set sortedLotto = lotto.sortLottoNum(lotto);
        lottoNumList.add(lotto);
        System.out.println(sortedLotto);
    }
    return lottoNumList;
}

public Set sortLottoNum(Lotto lotto) {
    Set sortingSet = new TreeSet();
    for (Integer eachNum:lotto.numbers
         ) {
        sortingSet.add(eachNum);
    }
    return sortingSet;
}

 

(3) 적절한 자료구조를 사용하기 위한 convert 메소드들 

- 기능별로, 자료구조들을 List - Set - Map 등을 바꿔가며 사용해야 했다.

=> 필요한 자료구조 변환을 메소드로 만들어 두고 사용한다. 

public static Set<Integer> convertStringToIntegerSet(String winningNumbers){
    String[] split = winningNumbers.split(",");
    Set<Integer> IntegerSet = new HashSet<>();
    for (int i = 0; i < split.length; i++) {
        IntegerSet.add(Integer.parseInt(split[i]));
    }
    return IntegerSet;
}

public static List<Set<Integer>> convertLottoToIntegerSet(List<Lotto> lottoList) {
    List<Set<Integer>> LottoNumSetList = new ArrayList<>();

    for (int i = 0; i < lottoList.size(); i++) {
        Set<Integer> LottoNumSet = new HashSet<>();
        List<Integer> lottoNums = lottoList.get(i).numbers;
        for (Integer lottoNum:lottoNums
             ) {
            LottoNumSet.add(lottoNum);
        }
        LottoNumSetList.add(LottoNumSet);
    }
    return LottoNumSetList;
}

public static List<Integer> convertSetToList(Set<Integer> lottoList) {
    List<Integer> tempList = new ArrayList<>();
    for (Integer lottoNum:lottoList
         ) {
        tempList.add(lottoNum);
    }
    return tempList;
}

public static Set<Integer> convertListToSet(List<Integer> lottoList) {
    Set<Integer> tempSet = new HashSet<>();
    for (Integer lottoNum:lottoList
    ) {
        tempSet.add(lottoNum);
    }
    return tempSet;
}

 

(4) 맞춘 로또 개수 Count && 맞춘 로또 개수 별 Count 

- 핵심 로직부분이 아닐까 한다. 

- List 로 돌리면서 확인하기엔 코드도 복잡하고 for 문을 여러번 돌아야 할 것으로 판단,

=> Set 의 retainAll 을 사용했다. 

- retainAll 을 해준 뒤 Set 의 Size 는 matching 된 개수를 의미한다. 

 

- 5개를 맞췄을 경우, bonusNum 에 대한 여부를 확인해야 하므로, 

로직을 추가해 5개를 맞추고 보너스까지 맞췄을 경우 임의적 수 7을 넣어두고 후에 처리한다. 

=> 여기서, 처음에 777 을 줬는데 후처리를 할 때, 분명 777 리스트에 넣어둔 777과 keySet의 777이 일치하는데도 

  boolean 에서 false 가 나서 시간을 꽤나 잡아먹혔다.

=> .byteValue 를 이용해서 비교하니 제대로 작동했고, 7로 바꾸니 boolean 작동이 정상적으로 됬다.

프로그램에서 숫자가 커지면서 생기는 0.00 소수점 아래의 문제가 아닐까 한다. 

 

- 맞춘 개수별로, map 을 이용해 3 - 몇개 / 4 - 몇개 / .. / 7 - 몇개 하는 식으로 저장해 두고 이를 리턴해서 수익률 계산할 때 사용했다. 7을 5등 + 보너스 로 후처리 해주면 된다.

public static List<Integer> getMatchingCount(List<Set<Integer>> lottoList, Set<Integer> winningNumbers, int bonusNum) {
    List<Integer> matchingCountList = new ArrayList<>();
    for (int i = 0; i < lottoList.size(); i++) {
        List<Integer> tempList = convertSetToList(lottoList.get(i));
        Set<Integer> LottoNumSet = lottoList.get(i);
        LottoNumSet.retainAll(winningNumbers);
        if (LottoNumSet.size() == 5 && tempList.contains(bonusNum)) {
                matchingCountList.add(7);
                continue;
        }
        matchingCountList.add(LottoNumSet.size());
    }
    return matchingCountList;
}

public static Map<Integer,Integer> getMatchingCountNumber(List<Integer> matchingCountList) {
    Map<Integer, Integer> matchingCount_Number = new HashMap<>();
    for (int i = 0; i < 4; i++) {
        matchingCount_Number.put(i + 3, 0);
    }
    matchingCount_Number.put(7, 0);
    for (int j = 0; j < matchingCountList.size(); j++) {
        for (Integer keySet:matchingCount_Number.keySet()) {
            if (matchingCountList.get(j)>=3 && matchingCountList.get(j) == keySet) {
                matchingCount_Number.put(keySet, matchingCount_Number.get(keySet) + 1);
            }
        }
    }
    return matchingCount_Number;
}

 

 

(5) 수익률 계산 및 enum 활용 

- map 에 등수 별 개수가 들어있으니 수익률 계산은 간단하지만, 

등수 별 => 상금 등 대응 관계가 있는 부분이라 enum 을 사용하기에 적절해 보여서 약간 억지로 enum 을 써보기 위해 코드를 고쳤다.

- 쓰고 나서도, ' enum 그렇게 쓰는거 아닌데~ ' 싶다. 아직은 익숙하지 않으니 사용법을 더 숙지해야 겠다. 

 

- 수익률은 소수점 첫째자리까지 반올림하여 나타내줘야 하고, 정수 부분은 , 쉼표처리 해주기 위해 

DecimalFormat 을 이용했다.

=> 수익률이 없을 경우, 혹은 소수점 자리가 없을 경우 0.0% 등으로 표시되어야 하므로 마지막 두자리는 0 으로 format  

public enum matching {
    THREE(3, 5000),
    FOUR(4, 50000),
    FIVE(5, 1500000),
    SIX(6, 2000000000),
    BONUS(7, 3000000);
    private final int count, value;
    private matching(int count, int value) {
        this.count = count;
        this.value = value;
    }
    public int getCount() {
        return this.count;
    }
    public int getValue() {
        return this.value;
    }
}


public static String getBenefit(Map<Integer, Integer> matchingCountNum, String price) {
    double totalPrice = 0;
    for (Integer matchingCount:matchingCountNum.keySet()
    ) {
        if (matchingCountNum.get(matchingCount) != 0) {
            if (matching.THREE.getCount() == matchingCount) {totalPrice += matching.THREE.getValue() * matchingCountNum.get(matchingCount);}
            if (matching.FOUR.getCount() == matchingCount) {totalPrice += matching.FOUR.getValue() * matchingCountNum.get(matchingCount);}
            if (matching.FIVE.getCount() == matchingCount) {totalPrice += matching.FIVE.getValue() * matchingCountNum.get(matchingCount);}
            if (matching.SIX.getCount() == matchingCount) {totalPrice += matching.SIX.getValue() * matchingCountNum.get(matchingCount);}
            if (matching.BONUS.getCount() == matchingCount) {totalPrice += matching.BONUS.getValue() * matchingCountNum.get(matchingCount);}
        }
    }
    return new DecimalFormat("###,##0.0").format((totalPrice/Integer.parseInt(price))*100);
}

 

출력할 메세지는 StringBuilder 에 담아서 return 해주었다.

public static StringBuilder convertMatchingInfoToMessage(Map<Integer, Integer> matchingCountNumber) {
    StringBuilder sb = new StringBuilder();
    sb.append("3개 일치 (5,000원) - ");
    sb.append(matchingCountNumber.get(3) + "개\n");
    sb.append("4개 일치 (50,000원) - ");
    sb.append(matchingCountNumber.get(4) + "개\n");
    sb.append("5개 일치 (1,500,000원) - ");
    sb.append(matchingCountNumber.get(5) + "개\n");
    sb.append("5개 일치, 보너스 볼 일치 (30,000,000원) - ");
    sb.append(matchingCountNumber.get(7) + "개\n");
    sb.append("6개 일치 (2,000,000,000원) - ");
    sb.append(matchingCountNumber.get(6) + "개");
    return sb;
}

 

(6) ERROR Handle 

- 사용자의 입력값을 받기 때문에, 발생할 수 있는 에러의 종류가 다양하다. 

=> RegEx 를 이용해 대부분의 에러를 잡는다. 

=> RegEx 로 걸러지지 못한 Exception ( 중복, 범위 등 ) 에 대해 처리한다. 

- RegEx 에 많이 숙달이 되면, 중복이나 범위 등에 대한 에러도 RegEx로 처리할 수 있을 듯 하다. 

 

- 메소드에서 throw new Exception 으로 에러를 던져놓고, 

main 실행 API 에서 try-catch 로 잡으면서 e.getMessage 를 System.out 해주면 문제에서 요구하는 대로 

[ERROR] 로 시작하는 내가 지정해둔 메세지만이 출력된다.  

public static void priceValidator(String price) {
    if (!price.matches("[\\d]{1,8}")) {
        throw new IllegalArgumentException("[ERROR] 1억 이하의 숫자만 입력 가능합니다. (1000원 단위)");
    }
    if (Integer.parseInt(price) % 1000 != 0 || price.equals("0")) {
        throw new IllegalArgumentException("[ERROR] 구입은 1000원 단위로만 가능합니다.");
    }
}

public static void winningNumFormatValidator(String winningNum) {
    if (!winningNum.matches("[0-9]{1,2},[0-9]{1,2},[0-9]{1,2},[0-9]{1,2},[0-9]{1,2},[0-9]{1,2}")) {
        throw new IllegalArgumentException("[ERROR] 당첨번호는 숫자,숫자,숫자... 형식으로 6개를 입력해주세요. ex)1,2,3,4,5,6");
    }
}


public static void winningNumValidator(Set<Integer> winningNumSet) {
    if (winningNumSet.size() != 6) {
        throw new IllegalArgumentException("[ERROR] 당첨번호는 중복될 수 없습니다.");
    }
    for (Integer winnigNum:winningNumSet
         ) {
        if (winnigNum < 0 || 45 < winnigNum) {
            throw new IllegalArgumentException("[ERROR] 당첨번호는 1 ~ 45 사이의 숫자여야 합니다.");
        }
    }
}

public static void bonusNumValidator(String bonusNum, Set<Integer> winningNumSet) {
    if (!bonusNum.matches("[0-9]{1,2}")) {
        throw new IllegalArgumentException("[ERROR] 한 개의 숫자만 입력해 주세요.(1-45)");
    }
    if (Integer.parseInt(bonusNum) < 0 || 45 < Integer.parseInt(bonusNum)) {
        throw new IllegalArgumentException("[ERROR] 1~45 사이의 숫자만 가능합니다.");
    }
    if (winningNumSet.contains(Integer.parseInt(bonusNum))) {
        throw new IllegalArgumentException("[ERROR] 보너스 번호는 당첨번호와 중복될 수 없습니다.");
    }
}

 

[ 실행 API ]

package UTEKO.ThirdWeek.lotto;

import camp.nextstep.edu.missionutils.Console;

import java.util.List;
import java.util.Map;
import java.util.Set;

public class Application {
    public static void main(String[] args) {
        // TODO: 프로그램 구현
        try {
            System.out.println("구입할 금액을 입력해주세요.");
            String price = Console.readLine();
            Lotto.priceValidator(price);
            Integer lottoCount = Integer.parseInt(price) / 1000;

            System.out.println("\n" + lottoCount + "개를 구매했습니다.");

            List<Lotto> lottoNumList = Lotto.getLottoNum(lottoCount);
            List<Set<Integer>> lottoNumSetList = Lotto.convertLottoToIntegerSet(lottoNumList);

            System.out.println("\n당첨 번호를 입력해 주세요.");
            String winningNumbers = Console.readLine();
            Lotto.winningNumFormatValidator(winningNumbers);

            Set<Integer> winningNumSet = Lotto.convertStringToIntegerSet(winningNumbers);
            Lotto.winningNumValidator(winningNumSet);

            System.out.println("\n보너스 번호를 입력해 주세요.");
            String bonusNumber = Console.readLine();
            Lotto.bonusNumValidator(bonusNumber, winningNumSet);

            List<Integer> matchingCount = Lotto.getMatchingCount(lottoNumSetList, winningNumSet, Integer.parseInt(bonusNumber));

            System.out.println("\n당첨 통계\n---");

            Map<Integer, Integer> matchingCountNumber = Lotto.getMatchingCountNumber(matchingCount);
            StringBuilder resultMessage = Lotto.convertMatchingInfoToMessage(matchingCountNumber);

            System.out.println(resultMessage);
            String benefit = Lotto.getBenefit(matchingCountNumber, price);
            System.out.println("총 수익률은 " + benefit + "%입니다.");
        } catch (IllegalArgumentException e) {
            System.out.println(e.getMessage());
        }
    }
}

 

- 제한 사항, 요구 사항이 많아서 놓친 부분들도 많고, 코드도 효율적으로 리팩토링할 여지가 많은 것 같습니다. 

리팩토링 하는 능력을 키우고, 이게 숙달되면 처음부터 좀 더 효율적인 클린코드를 구현할 수 있도록 계속 노력해야겠습니다.

 

 

 

[ 전체 메소드 ] 

package UTEKO.ThirdWeek.lotto;

import camp.nextstep.edu.missionutils.Randoms;

import java.text.DecimalFormat;
import java.util.*;

public class Lotto {
    private final List<Integer> numbers;

    public Lotto(List<Integer> numbers) {
            validate(numbers);
        this.numbers = numbers;
    }

    private void validate(List<Integer> numbers) {
        if (numbers.size() != 6) {
            throw new IllegalArgumentException("[ERROR] 로또 번호는 총 6개를 입력해야 합니다.");
        }
        if (numbers.size() == 6 && convertListToSet(numbers).size() != 6) {
            throw new IllegalArgumentException("[ERROR] 로또 번호는 중복될 수 없습니다.");
        }
        for (Integer eachNum:numbers
             ) {
            if (eachNum < 1 || 45 < eachNum) {
                throw new IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
            }
        }
    }
    // TODO: 추가 기능 구현
    @Override
    public String toString() {
        return "" + numbers ;
    }

    public static List<Lotto> getLottoNum(Integer lottoCount){
        List<Lotto> lottoNumList = new ArrayList<>();
        for (int i = 0; i < lottoCount; i++) {
            Lotto lotto = new Lotto(Randoms.pickUniqueNumbersInRange(1, 45, 6));
            Set sortedLotto = lotto.sortLottoNum(lotto);
            lottoNumList.add(lotto);
            System.out.println(sortedLotto);
        }
        return lottoNumList;
    }

    public Set sortLottoNum(Lotto lotto) {
        Set sortingSet = new TreeSet();
        for (Integer eachNum:lotto.numbers
             ) {
            sortingSet.add(eachNum);
        }
        return sortingSet;
    }

    public static Set<Integer> convertStringToIntegerSet(String winningNumbers){
        String[] split = winningNumbers.split(",");
        Set<Integer> IntegerSet = new HashSet<>();
        for (int i = 0; i < split.length; i++) {
            IntegerSet.add(Integer.parseInt(split[i]));
        }
        return IntegerSet;
    }

    public static List<Set<Integer>> convertLottoToIntegerSet(List<Lotto> lottoList) {
        List<Set<Integer>> LottoNumSetList = new ArrayList<>();

        for (int i = 0; i < lottoList.size(); i++) {
            Set<Integer> LottoNumSet = new HashSet<>();
            List<Integer> lottoNums = lottoList.get(i).numbers;
            for (Integer lottoNum:lottoNums
                 ) {
                LottoNumSet.add(lottoNum);
            }
            LottoNumSetList.add(LottoNumSet);
        }
        return LottoNumSetList;
    }

    public static List<Integer> convertSetToList(Set<Integer> lottoList) {
        List<Integer> tempList = new ArrayList<>();
        for (Integer lottoNum:lottoList
             ) {
            tempList.add(lottoNum);
        }
        return tempList;
    }

    public static Set<Integer> convertListToSet(List<Integer> lottoList) {
        Set<Integer> tempSet = new HashSet<>();
        for (Integer lottoNum:lottoList
        ) {
            tempSet.add(lottoNum);
        }
        return tempSet;
    }

    public static List<Integer> getMatchingCount(List<Set<Integer>> lottoList, Set<Integer> winningNumbers, int bonusNum) {
        List<Integer> matchingCountList = new ArrayList<>();
        for (int i = 0; i < lottoList.size(); i++) {
            List<Integer> tempList = convertSetToList(lottoList.get(i));
            Set<Integer> LottoNumSet = lottoList.get(i);
            LottoNumSet.retainAll(winningNumbers);
            if (LottoNumSet.size() == 5 && tempList.contains(bonusNum)) {
                    matchingCountList.add(7);
                    continue;
            }
            matchingCountList.add(LottoNumSet.size());
        }
        return matchingCountList;
    }

    public static Map<Integer,Integer> getMatchingCountNumber(List<Integer> matchingCountList) {
        Map<Integer, Integer> matchingCount_Number = new HashMap<>();
        for (int i = 0; i < 4; i++) {
            matchingCount_Number.put(i + 3, 0);
        }
        matchingCount_Number.put(7, 0);
        for (int j = 0; j < matchingCountList.size(); j++) {
            for (Integer keySet:matchingCount_Number.keySet()) {
                if (matchingCountList.get(j)>=3 && matchingCountList.get(j) == keySet) {
                    matchingCount_Number.put(keySet, matchingCount_Number.get(keySet) + 1);
                }
            }
        }
        return matchingCount_Number;
    }

    public enum matching {
        THREE(3, 5000),
        FOUR(4, 50000),
        FIVE(5, 1500000),
        SIX(6, 2000000000),
        BONUS(7, 3000000);
        private final int count, value;
        private matching(int count, int value) {
            this.count = count;
            this.value = value;
        }
        public int getCount() {
            return this.count;
        }
        public int getValue() {
            return this.value;
        }
    }


    public static String getBenefit(Map<Integer, Integer> matchingCountNum, String price) {
        double totalPrice = 0;
        for (Integer matchingCount:matchingCountNum.keySet()
        ) {
            if (matchingCountNum.get(matchingCount) != 0) {
                if (matching.THREE.getCount() == matchingCount) {totalPrice += matching.THREE.getValue() * matchingCountNum.get(matchingCount);}
                if (matching.FOUR.getCount() == matchingCount) {totalPrice += matching.FOUR.getValue() * matchingCountNum.get(matchingCount);}
                if (matching.FIVE.getCount() == matchingCount) {totalPrice += matching.FIVE.getValue() * matchingCountNum.get(matchingCount);}
                if (matching.SIX.getCount() == matchingCount) {totalPrice += matching.SIX.getValue() * matchingCountNum.get(matchingCount);}
                if (matching.BONUS.getCount() == matchingCount) {totalPrice += matching.BONUS.getValue() * matchingCountNum.get(matchingCount);}
            }
        }
        return new DecimalFormat("###,##0.0").format((totalPrice/Integer.parseInt(price))*100);
    }




    public static StringBuilder convertMatchingInfoToMessage(Map<Integer, Integer> matchingCountNumber) {
        StringBuilder sb = new StringBuilder();
        sb.append("3개 일치 (5,000원) - ");
        sb.append(matchingCountNumber.get(3) + "개\n");
        sb.append("4개 일치 (50,000원) - ");
        sb.append(matchingCountNumber.get(4) + "개\n");
        sb.append("5개 일치 (1,500,000원) - ");
        sb.append(matchingCountNumber.get(5) + "개\n");
        sb.append("5개 일치, 보너스 볼 일치 (30,000,000원) - ");
        sb.append(matchingCountNumber.get(7) + "개\n");
        sb.append("6개 일치 (2,000,000,000원) - ");
        sb.append(matchingCountNumber.get(6) + "개");
        return sb;
    }

    public static void priceValidator(String price) {
        if (!price.matches("[\\d]{1,8}")) {
            throw new IllegalArgumentException("[ERROR] 1억 이하의 숫자만 입력 가능합니다. (1000원 단위)");
        }
        if (Integer.parseInt(price) % 1000 != 0 || price.equals("0")) {
            throw new IllegalArgumentException("[ERROR] 구입은 1000원 단위로만 가능합니다.");
        }
    }

    public static void winningNumFormatValidator(String winningNum) {
        if (!winningNum.matches("[0-9]{1,2},[0-9]{1,2},[0-9]{1,2},[0-9]{1,2},[0-9]{1,2},[0-9]{1,2}")) {
            throw new IllegalArgumentException("[ERROR] 당첨번호는 숫자,숫자,숫자... 형식으로 6개를 입력해주세요. ex)1,2,3,4,5,6");
        }
    }


    public static void winningNumValidator(Set<Integer> winningNumSet) {
        if (winningNumSet.size() != 6) {
            throw new IllegalArgumentException("[ERROR] 당첨번호는 중복될 수 없습니다.");
        }
        for (Integer winnigNum:winningNumSet
             ) {
            if (winnigNum < 0 || 45 < winnigNum) {
                throw new IllegalArgumentException("[ERROR] 당첨번호는 1 ~ 45 사이의 숫자여야 합니다.");
            }
        }
    }

    public static void bonusNumValidator(String bonusNum, Set<Integer> winningNumSet) {
        if (!bonusNum.matches("[0-9]{1,2}")) {
            throw new IllegalArgumentException("[ERROR] 한 개의 숫자만 입력해 주세요.(1-45)");
        }
        if (Integer.parseInt(bonusNum) < 0 || 45 < Integer.parseInt(bonusNum)) {
            throw new IllegalArgumentException("[ERROR] 1~45 사이의 숫자만 가능합니다.");
        }
        if (winningNumSet.contains(Integer.parseInt(bonusNum))) {
            throw new IllegalArgumentException("[ERROR] 보너스 번호는 당첨번호와 중복될 수 없습니다.");
        }
    }

}