ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [내배캠, Lv2] 자판기
    알고리즘 문제/Java 2024. 6. 14. 17:18

    문제

    문제1 : 자바/코틀린 코드를 이용하여 자판기를 만들어봅시다.

    • 사용자가 볼 수 있게 메뉴를 표시합니다.
      • 사이다 1,700원
      • 콜라 1,900원
      • 식혜 2,500원
      • 솔의눈 3,000원
    • 사용자는 음료를 선택할 수 있습니다.
      • 사용자에게 어떤 음료를 살 것인지를 입력받습니다.
        • ex) 사이다
    • 사용자는 지불할 금액을 입력할 수 있습니다.
      • 사용자에게 얼마를 넣을지 입력받습니다.
        • ex) 2000
    • 사용자는 음료를 구매하고 남은 잔액을 확인할 수 있습니다.

     

    문제 접근 

    문제를 풀기전 나는 다음과 같은 계획을 세웠다.

    1. Map을 활용한 메뉴 관리
    2. 구매, 충전, 반환 및 종료 메뉴 
    3. 입력받은 문자열을 사용하여 Map에서 데이터 참조하기 

    해당 계획대로 구현하기 위해 나는 다음과 같은 내용을 학습해야할 필요가 있었다.

    1. Java의 Map객체 생성 및 초기화
    2. Integer가 아닌 String값 입력 받기
    3. Map에서 데이터 참조

     

    문제점

    문자열 버퍼로 인한 nextLine 문제

    처음에 실행 해보았을 때, 나는 다음과 같은 문제를 만났다.

    사용자가 행동을 입력한 후에, "사이다"와 같은 문자열로된 메뉴를 입력할 경우,

    정상적인 메뉴를 입력 했음에도, 메뉴를 찾을 수 없어 프로그램이 종료되는 현상이 발생하였다.

     

    처음에는 ContainsKey 함수를 내가 잘 못 사용하고 있는가 싶어 조사를 해보았지만,

    해당 부분에는 문제가 있지 않았다.

     

    그렇다면, 입력 받은 문자가 문제라는 생각이 들었고, 디버깅을 통해 입력 받은 문자열을 확인해 보았다.

    확인해본 결과, 문자열이 아닌 공백 문자("")가 들어간다는 것을 확인했고,  나는 다음과 같은 의심이 들었다.

    "행동을 nextInt를 통해  int형으로 입력 받았다면, 개행 문자가 남아 있는 것이 아닐까?" 

     

    12345를 입력했다면, Scanner객체가 12345까지만 읽어오고,

    뒤의 개행문자(\n)는 숫자가 아닌 문자이기에 남겨뒀을 것이라 생각이 들었다.

    Buffer 1 2 3 4 5 '\n'

      [ 12345를 입력했을 경우의 버퍼, NextInt는 이 중 12345만 가져간다 ]

     

    이러한 문제를 해결하기 위해 문자열을 읽어오는 nextLine함수를 사용하여 개행문자를 비워냈고,

    그 뒤에는 정상적으로 작동됐다.

     

    Map

    C++을 사용하다 Java로 넘어오면서, 제일 어렵게 다가오는 것은 다양한 컨테이너 종류와

    각기 다른 초기화 방법이 아닐까 싶다.

     

    C++과 Java 둘 다 Map이라는 컨테이너가 존재하지만,  종류와 참조하는 방법,

    초기화 하는 방법 사이에는 너무 큰 간극이 있었다.

     

    우선 멀티 쓰레딩 환경이 아닌 만큼 HashMap, LinkedHashMap, TreeMap 세 가지 중에 하나를 선택했고,

    메뉴에 순서가 바뀌더라도 문제가 없기에 HashMap을 사용하기로 결정했다.

     

    처음에는 혹시나 하는 마음으로

    HashMap<String, Integer> menu = new HashMap() { { "사이다", 1700 }, ... }; 

    같은 C++스타일로 초기화를 해보았으나 역시나 초기화가 이뤄지지 않았다.

     

    그래서 조사를 하던 중, 다양한 초기화 방법이 있었고

    Java버전 별로 익명 클래스를 활용한 .put(), .StreamOf() 활용, map.Of(), ofEntires() 활용하는 다양한 방법 중에 

    가독성이 제일 좋아보여 익명 클래스를 활용한 .put() 함수를 사용했다.

     

    개발 완료 후, 각 초기화 방법마다 성능적인 차이가 존재하는지 알아보기 위해 조사를 해 보았다.

    내가 가독성 면에서 제일 좋아보였던 방법이 오히려 초기화 때마다, 익명 클래스를 생성할 뿐만 아니라,

    메모리 누수와 직렬화 문제가 발생 할 수 있어, 추천하지 않는다는 문제가 존재한다는 것을 알게 되었다.

     

    참고자료 - Initialize Map with Values in Java - Coding N Concepts

     

    계획대로 구현한 코드

    더보기
    import java.util.HashMap;
    import java.util.Scanner;
    
    public class App {
    
        public static void showMenu(HashMap<String, Integer> menu) {
            int[] index = {1};
    
            menu.forEach((name, price) -> {
                System.out.print(index[0] + ". " + name + " " + price + " ");
                ++index[0];
            });
    
            System.out.print("\n\n");
        }
        public static void main(String[] args) throws Exception {
            int nowMoney = 0;
            HashMap<String, Integer> menu = new HashMap<>() {
                {
                    put("사이다", 1700);
                    put("콜라", 1900);
                    put("식혜", 2500);
                    put("솔의눈", 3000);
                }
            };
    
            Scanner sc = new Scanner(System.in);
            while(true)
            {
                System.out.println("1. 구매 2. 충전 3. 반환하고 종료");
                int choice = sc.nextInt();
    
                switch(choice)
                {
                    case 1 : 
                        showMenu(menu);
                        System.out.println("\n구매할 음료를 입력해 주세요 : ");
                        sc.nextLine();
                        
                        String answer = sc.nextLine();
    
                        if(menu.containsKey(answer))
                        {
                            if(menu.get(answer) > nowMoney)
                            {
                                System.out.println("돈이 부족합니다.");
                            }
                            else
                            {
                                System.out.println(answer + "을/를 구매하였습니다.");
                                nowMoney -= menu.get(answer);
                                System.out.println("잔액 : " + nowMoney);
                            }
                        }
                        else
                        {
                            return;
                        }
    
                    break;
    
                    case 2:
                        System.out.println("투입할 금액을 입력해 주세요 : ");
                        int insertMoney = sc.nextInt();
                
                        if(insertMoney <= 0)
                        {
                            System.out.println("0 이하의 숫자는 입력할 수 없습니다.");
                        }
                        else
                        {
                            System.out.println(insertMoney + "원을 충전 했습니다.");
                            nowMoney += insertMoney;
                            System.out.println("현재 잔액 : " + nowMoney);
                        }
                    break;
    
                    case 3:
                        System.out.println(nowMoney + "원이 반환 되었습니다.");
                        System.out.println("이용해 주셔서 감사합니다");
                
                        nowMoney = 0;
                    return;
                    
                    default :
                        System.out.println("유효하지 않은 항목입니다.");
                    break;
                }
            }

     

     

     

    그리고 코드를 실행한 결과 다음과 같이 동작하였다.

    [코드 실행 후 콘솔 화면]

     

    결과는 요구사항 그대로 결과가 나왔지만, 무언가 아쉬움이 남았다.

    우리는 자판기를 이용 할 때, 넣은 돈으로 구매할 수 있는 음료수에 불빛이 들어오는 장면을 봤을 것이다.

    [ 흔히 볼 수 있는 자판기, 넣은 돈에 따라 구매할 수 있는 음료 하단에 불빛이 들어온다 ]

     

    이러한 고증을 살려, 음료수를 구매하기 전, 내가 구매할 수 있는 음료를 표시해주기로 했고,

    실제 자판기 처럼 내가 거스름돈 반환을 하기 전까지 돈만 넣으면 무한으로 사용할 수 있게 만들어보기로 했다.

     

    마지막으로 좀 더 깔끔한 코드를 위해 Veding Machine이라는 Class를 선언해 사용해 보았다.

     

    vendingMachine.java

    더보기
    import java.util.HashMap;
    import java.util.Scanner;
    
    public class VendingMachine {
        public HashMap<String, Integer> menu;
        int nowMoney;
    
        public void initaillize(HashMap<String, Integer> newMenu) {
            menu = new HashMap<>(newMenu);
            nowMoney = 0;
        }
    
        // 메뉴 보여주는 함수
        public void showMenu() {
            int[] index = {1};
    
            menu.forEach((name, price) -> {
                System.out.print(index[0] + ". " + name + " " + price + " ");
                ++index[0];
            });
    
            System.out.print("\n\n");
        }
    
        // 음료수 구매
        public void buy(Scanner sc) {
            int[] index = {1};
    
            System.out.println("구매 가능한 음료 : ");
    
            // Lambda에서 지역변수를 참조하는 것이 불가능 하여, Array나 객체로 변환해야 한다.
            menu.forEach((name, price) -> {
                if(price < nowMoney)
                {
                    System.out.print(index[0] + ". " + name + " " + price + " ");
                    ++index[0];
                }
            });
    
            System.out.println("\n구매할 음료를 입력해 주세요 : ");
            // buffer 비우기
            sc.nextLine();
    
            String answer = sc.nextLine();
            String bb = sc.nextLine();
    
            if(menu.containsKey(answer))
            {
                if(menu.get(answer) < nowMoney)
                {
                    System.out.println(answer + "을/를 구매했습니다.");
                    
                    nowMoney -= menu.get(answer);
                    System.out.println("남은 소지 금액 : " + nowMoney);
                }
                else
                {
                    System.out.println(answer + "을/를 구매하지 못했습니다.( 소지 금액 부족 )");
                    System.out.println("현재 소지 금액 : " + nowMoney);
                }   
            }
            else 
            {
                System.out.println("존재하지 않는 음료입니다.");
            }
        }
    
        public void insertMoney(Scanner sc) {
            System.out.println("투입할 금액을 입력해 주세요 : ");
            int insertMoney = sc.nextInt();
    
            if(insertMoney <= 0)
            {
                System.out.println("0 이하의 숫자는 입력할 수 없습니다.");
            }
            else
            {
                System.out.println(insertMoney + "원을 충전 했습니다.");
                nowMoney += insertMoney;
                System.out.println("현재 잔액 : " + nowMoney);
            }
        }
    
        public void endShopping()
        {
            System.out.println(nowMoney + "원이 반환 되었습니다.");
            System.out.println("이용해 주셔서 감사합니다");
    
            nowMoney = 0;
        }
    }

     

    app.java

    더보기
    import java.util.HashMap;
    import java.util.Scanner;
    
    public class App {
        public static void main(String[] args) throws Exception {
      
            VendingMachine vm = new VendingMachine();
            HashMap<String, Integer> menu = new HashMap<>() {
                {
                    put("사이다", 1700);
                    put("콜라", 1900);
                    put("식혜", 2500);
                    put("솔의눈", 3000);
                }
            };
    
            vm.initaillize(menu);
            Scanner sc = new Scanner(System.in);
            while(true)
            {
                System.out.println("1. 구매 2. 충전 3. 반환하고 종료");
                int choice = sc.nextInt();
    
                switch(choice)
                {
                    case 1 : 
                        vm.showMenu();
                        vm.buy(sc);
                    break;
    
                    case 2:
                        vm.insertMoney(sc);
                    break;
    
                    case 3:
                        vm.endShopping();
                    return;
                    
                    default :
                        System.out.println("유효하지 않은 항목입니다.");
                    break;
                }
            }
        }
    }

    결과

    [ 최종 결과물 ]

     

Designed by Tistory.