[백준] 10951번 : A+B - 4 - JAVA [자바]
https://www.acmicpc.net/problem/10951
- 문제
간단한 문제지만 의외로 종료시점을 몰라 틀리는 경우들이 많은 것 같다.
※ 주의할 점
- 두 정수는 공백으로 나뉘어 구분된다.
- 입력의 종료는 더이상 읽을 수 있는 데이터 (EOF) 가 없을 때 종료한다.
- 3가지 방법을 사용하여 풀이한다.
먼저 입력 방식의 차이를 두어 Scanner 로 입력받아 연산하는 방법과 BufferedReader 로 입력받아 연산하는 방법, 두 가지 방법으로 풀어볼 것이고, 나머지 하나는 문자열 분리 방법에 차이를 두어 풀어 볼 것이다.
즉 다음 3가지로 풀어볼 것이다.
- Scanner
- BufferedReader + StringTokenizer (문자열 분리)
- BufferedReader + String.charAt()
- EOF 란?
이 문제에서 가장 중요한 점이 파일 종료 조건이 없이 그냥 입력이 주어졌다는 것이다.
즉 입력에서 더이상의 읽을 수 있는 데이터가 존재하지 않을 때 반복문을 종료하라는 것이다.
이렇게 데이터가 더이상 존재하지 않을 때 우리는 EOF (End of File) 즉, 파일의 끝이라 한다.
이를 처리하는 방법은 입력의 종류에 따라 여러 방법이 있다. 우리는 그 중 Scanner 와 BufferedReader 두 개의 처리 방법을 알아보고자 한다.
- Scanner
Scanner 의 메소드들의 경우 더이상 읽을 데이터가 없으면 아래 사진과 같이 NoSuchElementException 을 던지게 된다.
보다시피 Scanner 에 읽을 데이터가 없으면 아래와 같이 예외를 던져버린다.
이렇게 던져진 예외의 경우 두 가지 방법이 있다.
- try-catch 문으로 예외발생시 반복문을 종료해주도록 처리함.
- Scanner 의 메소드인 hasNext() 를 통해 처리해준다.
위의 방법 중에 우리는 hasNext() 라는 메소드를 이용할 것이다.
※ 주의할 점
백준 알고리즘에서는 데이터를 주는 과정에서 더이상의 데이터를 보내지 않음으로 NoSuchElementException 을 발생 시킬 수 있다. 그러나 우리가 평상시에 입력받는 방법인 System.in, 즉 키보드로 입력받을 경우 Scanner 는 우리가 흔히 쓰는 Enter, Space 도 입력 예외를 발생시키지 않는다. 쉽게 말하면 데이터로 무언가를 받아들인단 소리다.
우리가 평상시에 입력받는 방식으로 예외처리를 하려면 \n 을 입력받거나 " "(공백) 을 입력받을 경우의 조건문을 걸어 예외를 발생시켜 종료시켜야하지만, 백준 문제처럼 파일 입력의 경우는 종료시점에서 더이상 데이터를 보낼 수가 없기 때문에 예외가 발생된다.
특히 hasNext(), hasNextInt() 등 이런 메소드들로 처리해도 백준에서는 문제가 해결되는 이유가 더이상 데이터를 읽을 것이 없는경우 당연히 nextInt() 에서 받는 입력이 존재하지 않아 예외를 던져준다.
그러나 IDE나 터미널에서 우리가 입력을 할 경우 공백이나 엔터를 치더라도 이 또한 입력 이벤트로 데이터가 스트림에 넣어지는 것이기 때문에 예외가 던져지는 것이 아니다. 결국 반복문을 종료시키려면 hasNextInt()에서 EOF를 입력(윈도우의 경우는 ctrl + Z, 리눅스계열의 경우 ctrl + D)해주거나 정수가 아닌 문자열을 입력한다던가 등 다른 타입의 입력을 주어 InputMismatchException 을 던져주어야 한다.
위에 보면 2번째 빈 칸은 공백(space)를, 4번째엔 Enter 를 쳤지만 예외가 발생하지 않는 걸 볼 수 있다.
예외를 발생시켜 프로그램을 종료시키기 위해 정수가 아닌 값을 입력함으로써 예외를 발생시켜야 종료될 수 있음을 보여줬다.
- BufferedReader
BufferedReader 의 경우 null 을 반환한다. 이 부분은 오히려 null 인지 아닌지만 조건문을 통해 구분해주면 되므로 쉽다.
참고로 BufferedReader로 null을 반환하기 위해서는 역시 EOF를 던져주어야 하기 때문에 ctrl + Z (윈도우)혹은 ctrl + D(리눅스)를 입력해야한다.
- 풀이
- 방법 1
import java.util.Scanner;
public class Main {
public static void main(String args[]){
Scanner in=new Scanner(System.in);
while(in.hasNextInt()){
int a=in.nextInt();
int b=in.nextInt();
System.out.println(a+b);
}
in.close();
}
}
가장 기초적인 방법이다.
이 문제에서는 hasNextInt(), hasNext() 둘 중 아무거나 써도 괜찮다. 어차피 입력이 아예 들어오지 않기 때문에 예외가 발생하는 형태는 같기 때문이다.
그래도 nextInt() 를 통해 정수를 입력받고자 했으니 hasNextInt() 를 써주는게 다른 코딩할 때도 착오가 발생하지 않는다.
참고로 hasNextInt() 의 경우 입력값이 정수일경우 true를 반환하며 정수가 아닐경우 바로 예외를 던지며 더이상의 입력을 받지 않고 hasNextInt()에서 false를 반환하면서 반복문이 종료된다.
- 방법 2
BufferedReader 을 쓰는 방식이다.
readLine() 을 통해 입력 받아 연산하는 방법을 설명할 것이다.
앞서 말했듯이 readLine() 은 한 행을 전부 읽기 때문에 공백단위로 입력해 준 문자열을 공백단위로 분리해주어야 문제를 풀 수 있을 것이다.
문자열 분리 방법에는 두 가지 방법이 있다.
- StringTokenizer 클래스를 이용하여 분리해주는 방법
- split() 을 이용하는 방법
필자는 StringTokenizer를 선호하는 편이다. 특정 상황이 아니면 성능면에서 훨씬 우월하기 때문이다.
고로 이 문제에서도 StringTokenizer 을 쓸 것이다. 또한 출력에서는 StringBuilder 을 쓸 것이다.
참고로 반드시 자료형 타입을 잘 보아야 한다.
st.nextToken() 은 문자열을 반환하니 Integer.parseInt()로 int 형으로 변환시켜준다.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.StringTokenizer;
public class Main {
public static void main(String args[]) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringBuilder sb = new StringBuilder();
StringTokenizer st;
String str;
while( (str=br.readLine()) != null ){
st = new StringTokenizer(str," ");
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
sb.append(a+b).append("\n");
}
System.out.print(sb);
}
}
위 코드에서 보다시피 readLine() 을 통해 입력을 하여 str 에 저장된 데이터가 null 일 경우 while 반복문을 종료시켜버리고 아닐경우 반복문을 계속 수행하도록 한다.
- 방법 3
뭔가 StringTokenizer가 성능이 좋다고는 하지만 반복문을 할 때마다 객체를 계속 생성해주는게 시간을 잡아먹을 것 같지 않은가?
여기서 문제를 잘 보면 입력되는 정수는 반드시 ( 0< A,B < 10 ) 이다. 즉, 한자릿수 정수만 입력받는다.
그렇게 되면 자연스레 공백(" ")의 위치도 항상 고정된 위치라는 것을 알 수 있다.
그러면 굳이 객체생성을 안하고 더욱 쉽고 빠르게 짤 수 있지 않을까?
당연히 있다. charAt()을 써보자.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
public class Main {
public static void main(String args[]) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringBuilder sb = new StringBuilder();
String str;
while( (str=br.readLine()) != null ){
int a = str.charAt(0) - 48;
int b = str.charAt(2) - 48;
sb.append(a+b).append("\n");
}
System.out.print(sb);
}
}
더욱 간결해진 느낌이다.
몇 번 언급했지만 charAt() 은 해당 문자의 아스키코드 값을 반환하기 때문에 반드시 우리가 아는 정수 형태로 변경하려면 -48 또는 -'0'을 해주어야 한다.
위와같이 짜면 더 빠르냐고 묻는다면 아래 성능 차이를 보면 된다.
- 성능 차이
위에서 부터 순서대로
채점 번호 : 17903361 - BufferedReader + String.charAt()
채점 번호 : 17903348 - BufferedReader + StringTokenizer
채점 번호 : 17903335 - Scanner
시간을 보면 BufferedReader 와 Scanner 의 성능차이가 확연하게 나는 것을 볼 수가 있다.
또한 반복적인 객체 생성보다는 String.charAt() 메소드 호출이 더 빠르다는 것을 볼 수 있다.
여러분께 도움되는 글이니 꼭 한 번씩 테스트 해보시길 권한다.
- 정리
EOF 에 대해서 간단하게 알아보았다.
사실 A+B 계열 문제는 워낙 많이 풀어왔으니 알고리즘 자체는 어렵지 않았을 것이고, 입력이 더이상 주어지지 않을 때 어떻게 처리해야하는지를 배운 것이 이 포스팅의 가장 큰 소득이 아닐까 싶다.
'JAVA - 백준 [BAEK JOON] > 반복문' 카테고리의 다른 글
[백준] 1110번 : 더하기 사이클 - JAVA [자바] (36) | 2020.02.26 |
---|---|
[백준] 10952번 : A+B - 5 -JAVA [자바] (21) | 2020.02.23 |
[백준] 2439번 : 별 찍기 - 2 - JAVA [자바] (6) | 2020.02.19 |
[백준] 2438번 : 별찍기 - 1 - JAVA [자바] (8) | 2020.02.19 |
[백준] 11022번 : A+B - 8 - JAVA [자바] (10) | 2020.02.19 |