백준 에 있는 "크레이지 타임"에 대한 풀이 및 탐구에 대한 기록.
정답률 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;
}
}
- 최대 시간(= 12시), 최소 시간(= 1시)을 매직 넘버 대신 컴파일 타임 상수로 선언.
=> 성능 최적화
=> 매직넘버 상수화를 통한 가독성 향상. - 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 없이 문제를 푸는 것은 조금 어렵다.
'Software Engineering > Algorithm' 카테고리의 다른 글
[BaekJoon Silver3] C++: 이면수와 임현수 (0) | 2024.09.21 |
---|---|
[BaekJoon Silver5] C++: 단어 정렬 (0) | 2024.09.13 |
[BaekJoon Bronze2] C++: 블랙잭 (1) | 2024.09.08 |
[Programmers Lv. 2] C++: 최댓값과 최솟값 (0) | 2024.09.07 |
[Programmers Lv. 1] C++: 가장 많이 받은 선물 (0) | 2024.09.01 |