JAVA 숫자야구게임 트러블슈팅

SJ.CHO·2024년 9월 13일

1. Set 에 의한 자동정렬?

public static void main(String[] args) {
        final Set<Integer> numSet = new HashSet<>();
        final List<Integer> numList = new ArrayList<>();
        final List<Integer> userNumList = new ArrayList<>();
        while (numSet.size() < 3) {
            // random 메소드를 통해 1~9 숫자 생성
            numSet.add((int) (Math.random() * 9) + 1);
        }
        System.out.println(numSet.toString());
    }
  • 해당코드는 예시용 코드임.
  1. 문제발생 : 숫자야구 생성을 위한 랜덤숫자 3개를 생성시키고 중복제거를 위해 HashSet()을 사용중 값이 계속 정렬된 상태로 저장되는것을 확인. 정렬된상태를 유지한다면 셔플 및 알고리즘 로직이 완전히 달라져버림.

  2. 가설 : Set은 값을 정렬된상태로 인덱스를 비교하여 저장 연산을 실행하는가?

  3. 원인 : 결론부터 말하자면 HashSet은 정렬을 지원하지 않는다. HashSet에 데이터가 들어가며 테이블인덱스의 연산때문에 그렇게 보이는것.

  • 테이블 인덱스를 구하는 로직.
  • 그림으로 보면 해당형태로 구현이 된다. 마치 문자열 정렬처럼 값을 집어넣는다.
// 게임 시작시 3개의 숫자를 생성. Set으로 중복을 제거하고 List로 변환시켜 셔플.
    public void addNum() {
        while (numSet.size() < 3) {
            // random 메소드를 통해 1~9 숫자 생성
            numSet.add((int) (Math.random() * 9) + 1);
        }
        numList.addAll(numSet);
        Collections.shuffle(numList);
        System.out.println("< 게임을 시작합니다 >");
    }


4. 문제해결 : Set을 List에 담아서 Collections.shuffle(); 메소드를 활용하여 랜덤한 순서로 바뀔수 있게 문제해결.

참조 :

2. Character 를 통한 입력 숫자 유효성 검사

  1. 문제 발생 : 숫자를 하나의문자열 456 등으로 받아 하나씩 쪼개어 중복, 0의 경우의 처리는 성공하였으나 해당 문자가 숫자인가? 에 대한 조건식의 부재발생.
  2. 가설 : ASCII 코드를 사용해서 풀이가 가능하지않을까?
  3. 문제 해결 : ASCII 코드에 숫자를 지원하긴 하지만 해당 검증을 위해 깔끔하지않은 코드가 너무 발생. 문자열 Class 에서 방법을 찾던중
    Character 클래스의 .isDigit() 메서드 발견. char 값이 숫자인지 여부를 boolean 타입으로 리턴해주는 메소드로써 해당 메서드를 사용해 해결.
  • 그외로는 해당 메서드들이 있다.
    • Double.parseDouble() (예외처리 필요)
    • String.matches() (정규식 패턴)
// 입력값 유효성 검사
            if (userNum.length() >= 4) {
                System.out.println("숫자를 3자리로 입력해주세요.");
            } else {
                // 문자열을 나누어서 각각의 int 타입 숫자로 일의 자릿수로 저장,
                for (int i = 0; i < userNum.length(); i++) {
                    char c = userNum.charAt(i);
                    // 입력값 유효성검사.
                    if (userNumList.contains(Character.getNumericValue(c)) || Character.getNumericValue(c) == 0 || !Character.isDigit(c)) {
                        System.out.println("올바르지 않은 입력값입니다.");
                        userNumList.clear();
                        break;
                    } else {
                        // 올바른 데이터일 경우 List add
                        userNumList.add(Character.getNumericValue(c));
                    }
                }

참조 :
https://velog.io/@dyko/string-isNumberic
https://jamesdreaming.tistory.com/157

3. 배열 2개에 대한 값 및 Index 비교연산 최적화.

  1. 문제 발생 : 현재는 3:N 개의 배열 2개로만 비교연산을 진행하지만 언젠간 요소가 점점 늘어날수록 O(N) 의 시간복잡도를 가지는 알고리즘이기에 소요시간이 기하급수적으로 늘어날것이다. 최적연산이 없을까?
  2. 가설 : String 문자열로 받아서 String.indexOf() 로 작성하면 최적화가 가능한가?
  3. 결론 : 결론은 역시 불가능 하다. String.indexOf() 또한 O(N)으로 작동하기에 적절한방법은 아니다.. 다른방법또한 문서들을 찾아봤지만 없는걸로...
    (하다하다 ChatGPT 까지 갔다..)

4. 2중 반복문, 조건문으로 원하는 루프 Break

public class LabeledBreakExample {
    public static void main(String[] args) {
        outerLoop:
        for (int i = 1; i <= 5; i++) {
            for (int j = 1; j <= 5; j++) {
                System.out.println("i: " + i + ", j: " + j);
                if (i * j == 9) {
                    break outerLoop; // outerLoop 라벨이 붙은 반복문을 종료
                }
            }
        }
        System.out.println("End of the outer loop.");
    }
}
  1. 문제 발생 : 찬스기능을 추가하면서 이용자의 입력 예외로 인해 조건문에서 빠져나올시 빠져나온 분기로오는게 아닌 반복문 최상단으로 복귀하여 흐름상 좋지않은 문제가발생
  2. 가설 : 자신의 분기 루프말고 원하는 반복문을 빠져나올수 있는 기능이 있는가?
  3. 문제해결 : 바람직한 반복문의 종료조건은 while문에서의 조건을 false로 바꾸는 로직 이지만 컨트롤하기가 너무 껄끄럽다.
    그래서 찾아낸것이 Labeled Loop 기법으로 Loop문에 이름표인 라벨을 달아 특정반복문을 명확하게 제어해낼수있다.
// 유저가 찬스기능을 활요하는지에 대한 흐름분기 제어
    public void useChance(List<Integer> nums) {
        System.out.println("현재남은 찬스횟수 : " + getChanceNumber());
        System.out.println("찬스를 사용하려면 Y 입력 미사용시 N 입력");
        // 루프 라벨기능 추가
        Loop1:
        while (true) {
            String userSelChance = sc.nextLine();
            // 대소문자 구분없이 비교
            if (userSelChance.equalsIgnoreCase("Y")) {
                while (true) {
                    System.out.println("0. 찬스 사용 중지 1. 정확한 번호 알기(기회 2회 소모) 2. 번호 Up & Down(기회 1회 소모) 3. 모든 번호의 합(기회 1회 소모)");
                    int userSelChanceMenu = sc.nextInt();
                    if (userSelChanceMenu == 0) {
                        break Loop1;
                    } else if (userSelChanceMenu == 1) {
                        getIndexNumber(nums);
                        break Loop1;
                    } else if (userSelChanceMenu == 2) {
                        getIndexNumberUpDown(nums);
                        break Loop1;
                    } else if (userSelChanceMenu == 3) {
                        getSumAnswer(nums);
                        break Loop1;
                    } else {
                        System.out.println("1, 2 번중 입력해주세요.");
                        sc.nextLine();
                    }
                }
            } else if (userSelChance.equalsIgnoreCase("N")) {
                break;
            } else {
                System.out.println("Y, N 중 입력해주세요.");
            }
        }
    }

참조 : https://sosodev.tistory.com/entry/%EC%9E%90%EB%B0%94-Java-Labeled-Loop

profile
70살까지 개발하고싶은 개발자

0개의 댓글