휘적이는 기록공간

[java] 백준 문제 2739, 구구단 (부제: 코드 길이가 길어도 반드시 성능이 떨어지지 않으며, 짧다고 해서 좋은 성능을 보장하는 것도 아니다) 본문

Tech Notes & Growth/Algorithm & Problem Solving

[java] 백준 문제 2739, 구구단 (부제: 코드 길이가 길어도 반드시 성능이 떨어지지 않으며, 짧다고 해서 좋은 성능을 보장하는 것도 아니다)

휘희 2025. 3. 30. 02:26

문제 유형

반복문

 

문제

N을 입력받은 뒤, 구구단 N단을 출력하는 프로그램을 작성하시오. 출력 형식에 맞춰서 출력하면 된다.

입력

첫째 줄에 N이 주어진다. N은 1보다 크거나 같고, 9보다 작거나 같다.

 

출력

출력형식과 같게 N*1부터 N*9까지 출력한다.

 

예제 입력 

2

 

예제 출력 

2 * 1 = 2
2 * 2 = 4
2 * 3 = 6
2 * 4 = 8
2 * 5 = 10
2 * 6 = 12
2 * 7 = 14
2 * 8 = 16
2 * 9 = 18

 

 
문제 도출 방법
특정 값을 입력 받고, 그 값을 기준으로 비슷한 유형이 출력되는 결과를 얻어야한다.
 
이때 비슷한 유형의 출력들이 여러 개가 나오는 형태라면 반복문이 확실하다.


생각한 방법

스캐너를 사용해서 입력 값을 받는다.

그리고 for문을 1부터 9까지 돌리되 출력되는 것을 반복문 내부에서 돌려서 한 줄 씩 출력되도록 구현했다.

 

이렇게 코드를 짠 이유는

입력을 받는 것에 대해서 스캐너가 가장 먼저 떠올랐다.

그리고 스캐너에서 nextInt()를 써서 숫자를 뽑아 쓰면 간결하다.

 

그리고 반복문의 구현의 경우도

한 줄씩 나와야하기 때문에 다른 데에 저장을 하는 것보다 반복문을 돌 때마다 출력되는 것이

의도가 명확하게 드러난다고 생각했다.

즉 '한 눈에 봐도 직관적인 코드'

 

결과

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int userInputNumber = sc.nextInt();

        for(int i = 1; i <= 9; i++) {
            System.out.println(userInputNumber+" * "+i+ " = "+ userInputNumber*i);
        }
    }
}

 

 

결과는 당연히 잘 나온다.

 

그런데 이제 해당 문제에서 동일한 언어로 푼 다른 분의 코드를 보았는데 나와 전혀 달랐다.

 

다른 풀이

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class Main {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		StringBuilder sb = new StringBuilder();
		int N = Integer.parseInt(st.nextToken());
		
		for(int i = 1; i < 10; i++) {
			sb.append(N).append(" ").append("*").append(" ").append(i).append(" ").append("=").append(" ").append(i * N);
			sb.append(System.lineSeparator());
		}
		System.out.println(sb);
	}
}

 

이번 알고리즘은 어려운 편이 아니다.

그런데 이 문제에서 일단 내가 짠 코드가 긴 것에서 당황을 했다.

그리고 내가 짠 코드보다 이 코드가 메모리와 시간면에서 훨씬 좋다고 할 수 있다.

 

이 작은 기능에서 성능이 다르게 나올 수 있는 요인은 크게 두개가 있다.

 

첫 번째, 입력값을 받는 방식

Scanner 방식

지원해주는 메소드도 많고 사용하기 쉽지만,

내부에서 정규표현식을 사용해 입력을 토큰 단위로 파싱하는 추가적인 문자열 처리 과정이 있다.

 

BufferedReader 방식

내부 버퍼를 이용하여 데이터를 한 문자씩 읽는 것이 아닌, 일정 크기의 데이터 블록을 한 번에 읽어 온다.

 

-> Scanner의 추가적인 정규표현식 파싱은 성능을 저하시키고, BufferedReader의 블록 단위 읽기는 성능을 향상시킨다.

 

두 번째, 출력하는 방식

반복문 내부에서 출력하는 방식

매 호출마다 출력 스트림에 접근하는 비용이 발생

 

반복문 외부에서 출력하는 방식

StringBuilder에 모든 문자열을 누적한 후 마지막에 한 번에 출력하면 I/O 호출이 줄어들어 성능 개선

 

 

결론

결과값 도출도 중요하지만 역시나 성능면에서는 아직 많은 고민이 필요한 것 같다.

협업에서 중요한 건 직관적인 코드라는 생각이 강해서

처음에는 누가봐도 납득되고 이해되게 작성하자라는 생각으로 한 것 같다.

 

근데 이게 단순 CRUD를 넘어설 때는 이 정도를 어디까지로 봐야할까를 생각할 때가 있다.

무엇이 정답인지는 아직도 잘 모르겠지만

일단 '알고리즘'은 짧게 짜는게 중요한 것이 아니기 때문에, 공부하면서 고민을 더 많이해봐야겠다는 생각이 들었다.

 

 

참고 블로그

BufferedReader, Scanner

https://rlakuku-program.tistory.com/33