본문 바로가기

Software Engineering/Algorithm

[BaekJoon Bronze1] C++: 크레이지 타임

백준 에 있는 "크레이지 타임"에 대한 풀이 및 탐구에 대한 기록.

정답률 78.255%.

문제 분석

Input: 

  • 카드의 개수 N
  • N개의 줄에 걸쳐 (카드 종류) (숫자) 가 주어짐.

Output: 

  • N개의 줄에 걸쳐 (시간) (중앙 내리치기) 출력.

1시부터 시작, 순서대로 시간을 외친다.

시간 역행의 법칙: 만약 모래시계 카드가 나온다면, 시간은 거꾸로 흐른다.
동기화의 법칙: 내가 부를 숫자와 카드가 일치하면, 중앙을 내리친다.
과부하의 원칙: 위 두 법칙이 동시에 발동하면, 아무 일도 일어나지 않는다.

문제 풀이

초안

소요시간: 23분 42초.

#include <iostream>
#include <vector>
#include <tuple>
#include <string>

using namespace std;

int TimeCounter(int prevTime, bool isClockwise);
string IsMidEvent(int isMid);

int main() {
	const string HOUR_GLASS = "HOURGLASS";

	int cardCount; //카드의 개수.
	cin >> cardCount;

	vector<tuple<bool, int>> cards; //N개의 카드 정보: <reverse 여부, 시간>
	for (int i = 0; i < cardCount; i++) {
		string cardType;
		bool isReverse = false;
		int cardNum;
		cin >> cardType >> cardNum;
		if (cardType == HOUR_GLASS) isReverse = true;
		cards.push_back(make_tuple(isReverse, cardNum));
	}
	
	int curTime = 0;
	bool isClockwise = true;
	for (const auto& card : cards) {
		curTime = TimeCounter(curTime, isClockwise);
		if (get<0>(card)) {
			if (get<1>(card) != curTime) {//시간역행, 과부하x.
				isClockwise = !isClockwise;
			}
		}
		else if (get<1>(card) == curTime) {//동기화, 과부하x.
			cout << curTime << " " << IsMidEvent(true) << endl;
			continue;
		}
		cout << curTime << " " << IsMidEvent(false) << endl;
	}
}

/*다음 숫자 계산 함수, 인자: 이전 시간, 시간의 방향*/
int TimeCounter(int prevTime, bool isClockwise) {
	if (isClockwise) {
		prevTime++;
		if (prevTime > 12) prevTime -= 12;
	}
	else {
		prevTime--;
		if (prevTime < 1) prevTime += 12;
	}
	return prevTime;
}

/*bool값을 적절한 string으로 변환하는 함수*/
string IsMidEvent(int isMid) {
	if (isMid) return "YES";
	else return "NO";
}

리팩토링된 답안

# TimeCounter 함수

constexpr int MIN_TIME = 1;
constexpr int MAX_TIME = 12;

int TimeCounter(int prevTime, bool isClockwise) {
	if (isClockwise) {
		return (prevTime % MAX_TIME) + 1;
	} else {
		return (prevTime == MIN_TIME) ? MAX_TIME : prevTime - 1;
	}
}
  1. 최대 시간(= 12시), 최소 시간(= 1시)을 매직 넘버 대신 컴파일 타임 상수로 선언.
    => 성능 최적화
    => 매직넘버 상수화를 통한 가독성 향상.
  2. if문 간소화.
    => 시계방향일 때 % 기호를 사용함으로써 코드 단축.
    => 반시계방향일 때 삼항 연산자를 통해 코드 단축.
    => 반시계방향일 때 조건문을 <에서 ==이라는 적절한 기호로 변경함.

# IsMidEvent 함수

string IsMidEvent(bool isMid) {
	return isMid ? "YES" : "NO";
}

삼항 연산자를 통해 코드 간소화.

#  main 함수

/*struct 선언.*/
struct Card {
	bool isReverse;
	int cardNum;
};

/*main 함수 내부.*/
vector<Card> cards;

1. struct 선언
=> vector의 값을 접근할 때 필드명을 통해 값의 의미를 명확하게 드러낼 수 있음. 
=> 확장성 보장. 카드의 규칙이 추가될 때 새로운 필드 선언이 가능해짐.

for (int i = 0; i < cardCount; i++) {
	string cardType;
	int cardNum;
	cin >> cardType >> cardNum;
	bool isReverse = (cardType == HOUR_GLASS);
	cards.push_back({isReverse, cardNum});
}

2. isReverse 필드
=>  선언 위치 이동을 통해 변수 할당을 한 라인으로 단축.

3. vector cards의 push_back
=> 바뀐 자료형(struct)에 맞게 make_tuple을 {}형으로 변경.

for (const auto& card : cards) {
	curTime = TimeCounter(curTime, isClockwise);
	if (card.isReverse) {
		if (card.cardNum != curTime) isClockwise = !isClockwise;
	} else if (card.cardNum == curTime) {//동기화, 과부하x.
		cout << curTime << " " << IsMidEvent(true) << endl;
		continue;
	}
	cout << curTime << " " << IsMidEvent(false) << endl;
}

4. tuple로 인해 get<>을 사용하던 변수를 struct로 변경.
=> 가독성 향상

느낀 점

더 많은 알고리즘 문제를 풀어볼 필요를 느꼈다. 현재의 나는 말 그대로 "사용할 줄은" 아는 상태지만, 어떤걸 사용하면 더 좋은지에 대한건 많이 부족한 것 같다. 또한 다양한 라이브러리를 더 잘 활용할 수 있으면 좋겠다. 

문제 자체는 어렵지 않았지만, 아직 Intellisense 없이 문제를 푸는 것은 조금 어렵다.