반복문에 이름을 붙이다니, 왜?
...라고 생각했다면 아래 코드를 보자. 다음은 백준 2839번 '설탕 배달'에 대한 나의 첫 번째 코드다.
문제 상황 속, 설탕 배달에 쓰일 봉투는 5kg짜리와 3kg짜리가 있다. 배달해야 할 설탕의 무게 N이 주어질 때, 설탕 봉투 수를 최소로 하는 경우를 찾아내 총 봉투 수를 출력해야 하는 문제이다.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int temp = 0;
for (int i = 0; i <= N / 3; i++) {
for (int j = 0; j <= N / 5; j++) {
temp = (3 * i) + (5 * j);
if (temp == N) {
System.out.println(i + j);
break;
}
}
}
if (temp != N) {
System.out.println(-1);
}
}
}
여러 방법이 있겠지만, 나는 브루트 포스를 택했다. 용량 단위가 두 종류이므로 반복문을 중첩해서 사용하게 되고, 시간 복잡도는 O(n^2)가 될 것이다. 시간 복잡도 측면에서 이미 쓰레기 같지만, 그래도 풀면 그만이지 않나 하는 생각에 위 코드대로 구성해 보았다.
그런데 막상 예제 입력을 넣어보니, 이미 최적의 수를 출력했음에도 또 다른 출력이 나타났다. 원인은 break 문에서 찾을 수 있었는데, 조건문이 존재하는 해당 반복문에서 빠져나올 뿐, 바깥 반복문이 여전히 실행되기 때문이었다. (사실 당시에 break를 입력하면서도 우려했던 점이다.)
가장 먼저 두 가지 방법이 떠올랐다. 첫 번째 방법은 boolean 타입 변수를 써서 바깥 반복문에 if (true)인 경우 실행되도록 하고, 안쪽 반복문에서는 break를 쓰는 대신 boolean 타입 변수에 false를 대입해 바깥 반복문을 if (false)로 만들어 중지시키는 것이고, 두 번째 방법은 안쪽 반복문에서 아예 exit()을 사용해서 프로그램을 종료시켜 버리는 것이다.
어쨌든 가능하긴 할 것 같은데, 이런 불편한 방식을 다들 사용하나 싶어서 찾아보던 중 레이블이라는 것을 알게 되었다.
레이블
자바에서 반복문에 이름을 붙이는 것을 레이블이라고 한다. 이러한 레이블은 반복문을 식별할 수 있게 도우며, for, while, do-while 같은 반복문에 이름을 붙이는 방식으로 사용할 수 있다. 주로 앞서 말한 상황처럼 중첩된 반복문에서 특정 반복문을 빠져나오거나 명시적으로 참조할 때 유용하다.
사용법도 간단하다. 이름을 붙이고 싶은 반복문 앞에 '레이블명:'으로 레이블을 선언하면 된다. 아래 예제에서는 바깥 반복문에 'outer'라는 레이블을 선언한 결과이다.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int temp = 0;
outer: // 레이블 선언
for (int i = 0; i <= N / 3; i++) {
for (int j = 0; j <= N / 5; j++) {
temp = (3 * i) + (5 * j);
if (temp == N) {
System.out.println(i + j);
break outer;
}
}
}
if (temp != N) {
System.out.println(-1);
}
}
}
레이블 단점
레이블을 사용할 때 가장 큰 단점은 코드 가독성을 저해시킬 수 있다는 것이다. 레이블 사용을 남발하면 프로그램 흐름을 추적하기 어렵고, 코드를 읽는 사람은 레이블이 가리키는 반복문이 어디인지 즉각적으로 구분하기 어려울 수 있다. 그러니 복잡한 로직일수록 사용을 자제하는 것이 좋다. 꼭 타인이 아니더라도 본인이 디버깅할 때 어려움을 겪을 수도 있으니 말이다.
Q. 레이블 사용을 자제하라니, 그러면 어떻게 하나요?
아주 좋은 질문이다. 우리는 레이블을 사용하는 대신 함수로 분리하는 방법을 택할 수 있다. 함수로 반복문을 분리하면, 제어 흐름을 더 명확하게 만들 수 있고 디버깅에도 용이하다.
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int N = sc.nextInt();
int result = findMinSum(N); // 반환 값 대입
System.out.println(result);
}
// 반복문을 함수로 분리
public static int findMinSum(int N) {
for (int i = 0; i <= N / 3; i++) {
for (int j = 0; j <= N / 5; j++) {
int temp = (3 * i) + (5 * j);
if (temp == N) {
return i + j;
}
}
}
return -1;
}
}
더 좋은 방법이 있으니 레이블이라는 걸 쓰지는 않더라도 알고는 있자.
'프로그래밍 > 풀었어' 카테고리의 다른 글
[Java] BOJ 11650: 람다 표현식, 정렬 조건 부여하기 (1) | 2025.01.16 |
---|---|
[Java] BOJ 2609: 유클리드 호제법, 기약분수 만들기 (1) | 2025.01.15 |
[Java] BOJ 2775: 2차원 배열 (0) | 2025.01.14 |
[Java] BOJ 15829: BigInteger, BigInteger.ZERO (0) | 2025.01.13 |
[Java] BOJ 2750: 오름차순 정렬하기, 거품 정렬 (0) | 2025.01.09 |