ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java 개인과제] 계산기 Lv1
    부트캠프/과제 2024. 7. 26. 22:42

    과제 요구사항 정리

    1. Scanner를 사용하여 양의 정수 2개(0 포함)를 전달 받을 수 있습니다.

    2. Scanner를 사용하여 사칙연산 기호를 전달 받을 수 있습니다.

    3. 입력받은 양의 정수 2개와 사칙연산 기호를 사용하여 연산을 진행한 후 결과값을 출력합니다.

    4. 반복문을 사용하여 반복의 종료를 알려주는 “exit” 문자열을 입력하기 전까지
           무한으로 계산을 진행할 수 있도록 소스 코드를 수정합니다.

    5. 연산 결과 10개를 저장할 수 있는 배열을 선언 및 생성하고 연산의 결과를 저장합니다.

    6. 연산 결과가 10개를 초과하는 경우 가장 먼저 저장된 결과를 삭제하고,
           새로운 연산 결과가 저장될 수 있도록 소스 코드를 수정합니다.

    7. 연산 결과가 10개로 고정되지 않고 무한이 저장될 수 있도록 소스 코드를 수정합니다.

    8. “inquiry”라는 문자열이 입력되면 저장된 연산 결과 전부를 출력합니다.

     

    과제 요구사항 디테일

    1. Scanner를 사용하여 양의 정수 2개(0 포함)를 전달 받을 수 있습니다.

    해당 내용을 세분화하여 나누면 다음과 같이 나눌 수 있었다.

          1. 문자가 아닌 '정수'를 입력받아야 한다. 
          2. 양의 정수만을 받아야한다.

    우선, 정수만을 입력받아야 하는 내용에 대해서는 Integer.parseInt() 메서드를 사용하였다.

    해당 메서드에는 한가지 예외처리가 담겨있는데,
    Int형 변환에 실패할 경우 NumberFormatException를 throws한다는 것이다.

    // parseInt 함수 내용
    public static int parseInt(String s) throws NumberFormatException {
        return parseInt(s, 10);
    }

     

    그래서 나는 Int형이 아닌 다른 문자열이 들어왔을 경우, 발생할 예외를 처리할 catch 구문을 추가했다.

    catch (Exception e) {
        System.out.println("숫자가 아닌 내용을 입력하셨거나, 음의 정수를 입력하셨습니다.");
    }

     

    또한, 입력받은 데이터가 음수인 경우에도 새로운 Exception 객체를 생성해 catch로 받을 수 있게 추가했다.

     

    code : main

    더보기
    public class App {
        public static void main(String[] args) {
            int a = 0, b = 0;
            String aString, bString;
    
            Scanner sc = new Scanner(System.in);
    
            try {
                System.out.println("첫번째 양의 정수를 입력해주세요.");
                aString = sc.nextLine();
                a = Integer.parseInt(aString);
    
                System.out.println("두번째 양의 정수를 입력해주세요.");
                bString = sc.nextLine();
                b = Integer.parseInt(bString);
    
                // 음의 정수 입력했을 경우 Throw
                if(a < 0 || b < 0)  {
                    throw new Exception();
                }
    
            } catch (Exception e) {
                System.out.println("숫자가 아닌 내용을 입력하셨거나, 음의 정수를 입력하셨습니다.");
            }
        }
    }

    해당 요구 사항에서 한가지 문제가 발생했는데, 한글이 깨진다는 문제였고,

    구글링을 통해 해결할 수 있었다.

     

    해결과정 : [IntelliJ] 한글 깨짐 오류 :: 껀's 알고리즘 (tistory.com)

     

    [IntelliJ] 한글 깨짐 오류

    IntelliJ에서 프로그램을 사용할 경우 한글이 깨져서 나온다는 문제가 발생했다.  해당 에러를 해결하기 위해 우선 1. Global / Project encoding / Default encoding for properites files를 UTF-8로 변경 해당 Encodin

    choni.tistory.com

     

    2. Scanner를 사용하여 사칙연산 기호를 전달 받을 수 있습니다.

    해당 내용을 세분화하여 나누면 다음과 같이 나눌 수 있었다.

    1. 문자 1글자만 입력받아야 한다.
    2. + - * / 와 같은 사칙연산이 담겨있어야 한다.

    1번의 내용을 충족시키기 위해 입력 받은 길이가 2 이상일 경우, Exception을 Throw하게 구현하였다.

    2번의 내용을 충족시키기 위해 정규식을 사용하였고, + - * / 가 아닌 경우 Exception을 Throw하게 구현하였다.

     

    code : inputOperator

    더보기
    static final String REGEXP_ONLY_OPERATOR = "(.[+-/*])";
    
    public static void inputOperator(String inOperator, Scanner sc) throws Exception
    {
        System.out.println("사칙연산 기호를 입력해주세요.");
        inOperator = sc.nextLine();
    
        // 사칙연산 기호 확인 ( 길이가 2이상 이거나, 정규식에 포함이 안된 경우 Throw )
        if(inOperator.length() > 1 || Pattern.matches(REGEXP_ONLY_OPERATOR, inOperator)) {
            throw new Exception("사칙연산 기호를 확인해주세요.");
        }
    }

    정규식 참고 & 공부한 사이트 :  RegExr: Learn, Build, & Test RegEx

     

    RegExr: Learn, Build, & Test RegEx

    RegExr is an online tool to learn, build, & test Regular Expressions (RegEx / RegExp).

    regexr.com

     

     

    처음에는 해당 내용을 구현하면서, 함수 내에서 예외를 Throw 할 수 없는 문제가 발생했었다.

    해당 문제에 대해 조사를 해본 결과, 함수 내에서 예외를 외부로 Throw해야할 경우,

    throws Excetion과 같이 예외 타입을 명시해줘야 정상적으로 함수 외부로 예외를 Throw할 수 있었다.

     

    3. 입력받은 양의 정수 2개와 사칙연산 기호를 사용하여 연산을 진행한 후 결과값을 출력합니다.

    해당 내용에 대해서는 switch문을 통해 각 사칙연산에 맞는 결과를 반환해 줬다.

    나눗셈의 경우 분모에 0이 들어올 수 없는 만큼, 분모가 0인 경우 예외를 Throw하게 구현했다.

     

    code : calculation

    더보기
    public static double calculation(int[] numbers, char inOperator) throws Exception {
        double result = 0;
        switch (inOperator) {
            case '+' :
                result = numbers[0] + numbers[1];
                break;
            case '-' :
                result = numbers[0] - numbers[1];
                break;
            case '/' :
                if(numbers[1] == 0)
                {
                    throw new Exception("분모는 0이 될 수 없습니다.");
                }
                result = (double)numbers[0] / numbers[1];
                break;
            case '*' :
                result = numbers[0] * numbers[1];
                break;
            default :
                break;
        }
    
        return result;
    }

    4. 반복문을 사용하여 반복의 종료를 알려주는 “exit” 문자열을 입력하기 전까지 ,
            무한으로 계산을 진행할 수 있도록 소스 코드를 수정

    해당 내용은 while문을 통해, 반복 작동 하도록 구현했다.

    while문을 탈출하는 조건으로 boolean isRun이라는 변수를 선언하였고,

    문자열을 입력 받아 exit일 경우 isRun을 false로 변환해줬다.

     

    code : askMoreCalculation

    더보기
    public static boolean askMoreCalculation(Scanner sc) throws Exception {
        System.out.print("더 계산 하시겠습니까? yes/no/exit ( exit 입력시 종료 ) : ");
        String answer = sc.nextLine();
        answer = answer.toLowerCase();
    
        if(answer.equals("exit") || answer.equals("no")) {
            return false;
        }
        else if(answer.equals("yes")) {
            return true;
        }
        else {
            throw new Exception("yes/no/exit 중 하나만 입력해주십시오");
        }
    }

     

    code : run

    더보기
    public static boolean run() throws Exception  {
        int[] numbers = { 0, 0 };
        double calculationResult = 0;
        char inOperator = ' ';
        Scanner sc = new Scanner(System.in);
        boolean isRun = true;
    
        // 사용자가 잘못된 정보를 입력하거나, no / exit를 입력할 경우 inRun = false로 변환
        while(isRun) {
            inputNumbers(numbers, sc);
            inOperator = inputOperator(sc);
    
            calculationResult = calculation(numbers, inOperator);
            printResult(numbers, inOperator, calculationResult);
    
            isRun = askMoreCalculation(sc);
        }
    
        return true;
    }

    run이라는 싸이클을 관리하는 함수가 추가되었기에 main문 또한 간소화되었다.

     

    code : main

    더보기
    public static void main(String[] args) {
        try {
            run();
        } catch (NumberFormatException ex) {
            System.out.println("숫자를 입력해주세요.");
        } catch (Exception ex) {
            System.out.println(ex.getMessage());
        }
    }

     

    5. 연산 결과 10개를 저장할 수 있는 배열을 선언 및 생성하고 연산의 결과를 저장합니다.

    해당 내용을 구현하기 위해 결과를 저장하는 double[] memorize라는 변수를 추가하였고,

    현재 저장위치를 나타내는 int index를 추가했다.

     

    연산을 끝마칠 때마다 memorize[index] 위치에 값을 저장하고, index의 값을 +1씩 해주는 방식으로 요구사항을 처리했다.

    그런데 무작정 계산을 진행하다 보니,
    index가 최대 저장공간을 넘어서 배열 크기 밖의 메모리를 참조하는 out of range에러가 발생했다.

     

    out of range 문제를 해결하기 위해 최대 크기를 나타내는 final int CAPACITY 변수 또한 추가했고,

    inRun 판단 조건에 index와 CAPACITY를 비교하는 연산을 추가해,

    index == CAPACIRY인 경우에도 계산기를 종료하게 구현했다.

     

    Code : run

    더보기
    public static boolean run() throws Exception  {
        int[] numbers = { 0, 0 };
        double[] memorize = new double[CAPACITY];
        int index = 0;
        double calculationResult = 0;
        char inOperator = ' ';
    
        boolean isRun = true;
    
        Scanner sc = new Scanner(System.in);
    
        /*
            while 탈출 조건
            1. 사용자가 no / exit를 입력할 경우
            2. 저장공간이 꽉찬 경우
                => inRun = false로 변환
         */
        while(isRun) {
            inputNumbers(numbers, sc);
            inOperator = inputOperator(sc);
    
            calculationResult = calculation(numbers, inOperator);
            // 결과를 배열에 저장 후 index 증가
            memorize[index++] = calculationResult;
            printResult(numbers, inOperator, calculationResult);
    
            isRun = askMoreCalculation(sc) && index < CAPACITY;
        }

     

    6. 연산 결과가 10개를 초과하는 경우 가장 먼저 저장된 결과를 삭제하고,
           새로운 연산 결과가 저장될 수 있도록 소스 코드를 수정합니다.

     

    해당 내용을 구현하기 위해, Memorize를 한 칸씩 앞으로 push하는 pushMemorize함수를 구현했다.

    pushMemorize함수는 매개변수로 넘어온 double[]를 뒤에서부터 확인하여 다음과 같은 로직을 수행한다.

     

    1. memNowNum에 memorize[i]( 현재 위치의 숫자 )를 저장

    2. memorize[i]에 앞에서 저장해둔 memBackNum을 덮어쓴다.

    3. memBackNum에 memNowNum을 덮어쓴다.

     

    해당 로직을 통해, 맨 뒤의 숫자는 0으로 초기화 될 뿐 아니라 내용을 앞으로 1칸씩 이동시킬 수 있었다.

     

    Code : pushMemorize

    더보기
    public static void pushMemorize(double[] memorize) {
        double memBackNum = 0, memNowNum = 0;
        for(int i = CAPACITY - 1; i >= 0; --i) {
            /*
            *  a에 현재 값을 기억
            *  b에 이전 값을 덮어 씌운다
            *  이전 값을 현재 값으로 덮어 씌운다.
            */
            memNowNum = memorize[i];
            memorize[i] = memBackNum;
            memBackNum = memNowNum;
        }
    }

    또한, 데이터가 꽉찬 경우에만 데이터를 앞당겨야 했기에

    run 함수 내의 index가 CAPACITY와 동일한 경우에만 pushMemorize함수를 호출하도록 수정했고,

    요구사항 5번에서 추가한 isRun 조건에 memorize가 꽉찬 경우를 제거했다.

     

    Code : run

    더보기
        public static boolean run() throws Exception  {
            int[] numbers = { 0, 0 };
            double[] memorize = new double[CAPACITY];
            inOperator = inputOperator(sc);
            calculationResult = calculation(numbers, inOperator);
    
            // 배열이 꽉 찬 경우 앞으로 밀어준다.
            if(index == CAPACITY) {
                pushMemorize(memorize);
                --index;
            }
    
            // 결과를 배열에 저장 후 index 증가
            memorize[index++] = calculationResult;
            printResult(numbers, inOperator, calculationResult);
    
            isRun = askMoreCalculation(sc);
        }

     

    7. 연산 결과가 10개로 고정되지 않고 무한이 저장될 수 있도록 소스 코드를 수정합니다.

     

    해당 내용을 보고 처음으로 든 생각이 Queue를 쓰면, 문제가 해결되지 않을까 싶었다.

    Queue를 쓴 이유로는 삽입한 순서대로 데이터를 빼낼 수 있다는 점( FIFO )이 

    제일 과거 연산 결과를 삭제하는 6. 요구사항과 일치했기 때문이다.

     

    우선 remove할 것인지 물어보는 askRemove함수를 구현했다.

     

    Code : askRemove

    더보기
    public static boolean askRemove(Scanner sc) {
        System.out.print("가장 먼저 저장된 연산 결과를 삭제하시겠습니까? ( remove 입력시 삭제 ) : ");
        String answer = sc.nextLine();
        answer = answer.toLowerCase();
    
        return answer.equals("remove");
    }

     

    removeMemorize 함수를 통해 가장 오래된 값을 삭제 할 수 있게 구현했다.

    Queue가 비어져있는데 poll함수를 호출할 경우, 에러가 발생할 가능성이 있어

    비어있지 않은 경우에만 poll이 가능하게 구현했다.

     

    Code removeMemorize

    더보기
    public static void removeMemorize(Queue<Double> memorize) {
        if(!memorize.isEmpty()) {
            memorize.poll();
        } else {
            System.out.println("삭제할 값이 없습니다.");
        }
    }

     

    8. “inquiry”라는 문자열이 입력되면 저장된 연산 결과 전부를 출력합니다.

    Queue에 있는 데이터를 출력하는 함수 printMemorize를 선언했고,iterator를 사용하는 for문을 통해 값을 하나 씩 print했습니다.

     

    또한, 데이터 출력 여부를 묻는 askInquiry함수를 추가하여,

    사용자의 입력에 따를 수 있게 구현하였습니다.

     

    Code : askInquiry

    더보기
    public static boolean askInquiry(Scanner sc) throws Exception {
            System.out.print("저장된 연산 결과를 조회하시겠습니까? no/inquiry ( inquiry 입력시 출력 ) : ");
            String answer = sc.nextLine();
            answer = answer.toLowerCase();
    
            if(answer.equals("no")) {
                return false;
            }
            else if(answer.equals("inquiry")) {
                return true;
            }
            else {
                throw new Exception("no/inquiry 중 하나만 입력해주십시오");
            }
        }

     

    Code : printMemorize

    더보기
    public static void printMemorize(Queue<Double> memorize) {
        for(double d : memorize) {
            System.out.print(d + " ");
         }
        System.out.println();
    }
Designed by Tistory.