mojo's Blog

2018 KAKAO BLIND RECRUITMENT : 추석 트래픽 본문

프로그래머스/Level 3

2018 KAKAO BLIND RECRUITMENT : 추석 트래픽

_mojo_ 2021. 12. 30. 18:12

추석 트래픽

이번 추석에도 시스템 장애가 없는 명절을 보내고 싶은 어피치는 서버를 증설해야 할지 고민이다. 장애 대비용 서버 증설 여부를 결정하기 위해 작년 추석 기간인 9월 15일 로그 데이터를 분석한 후 초당 최대 처리량을 계산해보기로 했다. 초당 최대 처리량은 요청의 응답 완료 여부에 관계없이 임의 시간부터 1초(=1,000밀리초)간 처리하는 요청의 최대 개수를 의미한다.

입력 형식

  • solution 함수에 전달되는 lines 배열은 N(1 ≦ N ≦ 2,000)개의 로그 문자열로 되어 있으며, 각 로그 문자열마다 요청에 대한 응답완료시간 S와 처리시간 T가 공백으로 구분되어 있다.
  • 응답완료시간 S는 작년 추석인 2016년 9월 15일만 포함하여 고정 길이 2016-09-15 hh:mm:ss.sss 형식으로 되어 있다.
  • 처리시간 T는 0.1s, 0.312s, 2s 와 같이 최대 소수점 셋째 자리까지 기록하며 뒤에는 초 단위를 의미하는 s로 끝난다.
  • 예를 들어, 로그 문자열 2016-09-15 03:10:33.020 0.011s은 "2016년 9월 15일 오전 3시 10분 33.010초"부터 "2016년 9월 15일 오전 3시 10분 33.020초"까지 "0.011초" 동안 처리된 요청을 의미한다. (처리시간은 시작시간과 끝시간을 포함)
  • 서버에는 타임아웃이 3초로 적용되어 있기 때문에 처리시간은 0.001 ≦ T ≦ 3.000이다.
  • lines 배열은 응답완료시간 S를 기준으로 오름차순 정렬되어 있다.

출력 형식

  • solution 함수에서는 로그 데이터 lines 배열에 대해 초당 최대 처리량을 리턴한다.

입출력 예제

예제1

  • 입력: [
    "2016-09-15 01:00:04.001 2.0s",
    "2016-09-15 01:00:07.000 2s"
    ]
  • 출력: 1

예제2

  • 입력: [
    "2016-09-15 01:00:04.002 2.0s",
    "2016-09-15 01:00:07.000 2s"
    ]
  • 출력: 2
  • 설명: 처리시간은 시작시간과 끝시간을 포함하므로
    첫 번째 로그는 01:00:02.003 ~ 01:00:04.002에서 2초 동안 처리되었으며,
    두 번째 로그는 01:00:05.001 ~ 01:00:07.000에서 2초 동안 처리된다.
    따라서, 첫 번째 로그가 끝나는 시점과 두 번째 로그가 시작하는 시점의 구간인 01:00:04.002 ~ 01:00:05.001 1초 동안 최대 2개가 된다.

예제3

  • 입력: [
    "2016-09-15 20:59:57.421 0.351s",
    "2016-09-15 20:59:58.233 1.181s",
    "2016-09-15 20:59:58.299 0.8s",
    "2016-09-15 20:59:58.688 1.041s",
    "2016-09-15 20:59:59.591 1.412s",
    "2016-09-15 21:00:00.464 1.466s",
    "2016-09-15 21:00:00.741 1.581s",
    "2016-09-15 21:00:00.748 2.31s",
    "2016-09-15 21:00:00.966 0.381s",
    "2016-09-15 21:00:02.066 2.62s"
    ]
  • 출력: 7
  • 설명: 아래 타임라인 그림에서 빨간색으로 표시된 1초 각 구간의 처리량을 구해보면 (1)은 4개, (2)는 7개, (3)는 2개임을 알 수 있다. 따라서 초당 최대 처리량은 7이 되며, 동일한 최대 처리량을 갖는 1초 구간은 여러 개 존재할 수 있으므로 이 문제에서는 구간이 아닌 개수만 출력한다.

 


구현 문제이다.

특별한 알고리즘이 필요한 문제는 아니지만 문자열 처리와 연도 / 월 / 일 / 시간 에 대한 정보를 어떻게 처리해야 할지에 대해서만 약간의 고민을 해야 한다.

 

문제 접근 방식은 다음과 같다.

1. 연도 / 월 / 일 / 시간 에 대한 정수 값을 변수에 담아주도록 한다.

2. hashing 을 통해 연도 / 월 / 일 / 시간 에 대한 정보를 하나의 long long 값으로 표현해준다.

3. 처리 시간을 이용하여 시작 시간과 끝나는 시간에 대한 정보를 벡터 v_start, v_end 에 넣어준다.

4. 모든 연도 / 월 / 일 / 시간 에 대해서 1 ~ 3 과정을 걸쳤다면 v_start, v_end 를 오름차순으로 정렬한다.

5. v_start 의 각각의 원소값을 t 라고 할 때, [t, t + 1000) 내에 v_start, v_end 의 원소 값이 포함하는지를 살피면서 카운팅한 결과중 최대 결과와  v_end 의 각각의 원소값을 t 라고 할 때, [t, t + 1000) 내에 v_start, v_end 의 원소 값이 포함하는지를 살피면서 카운팅한 결과중 최대 결과 중 더 큰 값이 결과이다.  

 

1번 방법은 c++의 substr() 함수와 stoi를 적절하게 이용하여 연도 / 월 / 일 / 시간 에 대한 정보를 얻을 수 있다.

이때 시간은 23:52:06.123 와 같이 소수점 초까지 고려해야 한다.

따라서 좀 더 편리하게 계산을 위해 아래와 같이 하나의 시간 값으로 압축시킬 수 있다.

 

time = 소수점 아래 숫자 + 1000 * (초 + 분 * 60 + 시 * 60 * 60) 

 

즉, 23:52:06.123 는 time = 123 + 1000 * (6 + 52 * 60 + 23 * 60 * 60) 으로 하나의 시간값으로 압축시킬 수 있다.

 

2번 방법도 1번 방법과 마찬가지로 연도 / 월 / 일 / 시간 을 하나의 long long 값으로 압축시킨다.

이때 시간의 범위 [0, 24 * 60 * 60 * 1000 -1] = [0, 86,399,999] 임을 고려하여 아래와 같이 하나의 값으로 압축이 가능하다.

 

info = time + day * (1e8) + month * (1e10) + year * (1e12)

 

예를 들어 2016-09-15 03:10:33.020 는 

time = 20 + 1000 * (33 + 10 * 60 + 3 * 60 * 60)

info = time + 15 * (1e8) + 9 * (1e10) + 2016 * (1e12)

와 같이 long long info 값으로 하나의 정보로 표현이 가능하게 된다.

 

3번 방법은 2번 방법에서 구한 info는 결국 처리가 끝나는 시간이다. 

따라서 처리가 시작하는 시간은 info - T * 1000 + 1 이 된다. (이때 T는 처리시간)

두 값을 v_start, v_end 에 각각 push 해주면 된다.

그러나 T는 0.1s, 0.312s, 2s 와 같이 문자열 길이가 다르게 제공이 되는 것으로 보인다.

이에 대한 처리는 다음과 같이 처리하였다.

int getGap(string s) {
	int mul_op[4] = { -1, 100, 10, 1 };
	string gap;
	gap = s.substr(24, s.size());
	gap = gap.substr(0, gap.size() - 1);
	if (gap.size() == 1) return 1000 * stoi(gap);
	else {
		string mm = gap.substr(2, gap.size());
		gap = gap.substr(0, 1);
		return 1000 * stoi(gap) + mul_op[mm.size()] * stoi(mm);
	}
}

 

4번 방법O(n log n) 으로 정렬을 해주면 된다.

 

5번 방법은 다음과 같이 3가지 경우를 고려해볼 수 있다.

 

 

문제에서 1초간 처리라고 하였으므로 next - cur + 1 = 1000, next = cur + 999 임을 알 수 있다.

아래와 같이 3가지 케이스로 분류할 수 있다.

 

case 1 : [cur, next] 구간에 start 가 있는 경우

case 2 : [start, end] 구간에 cur, next가 있는 경우

case 3 : [cur, next] 구간에 end 가 있는 경우

 

이에 대한 코드는 다음과 같다.

bool isProcess(long long cur, long long next, long long start, long long end) {
	if (cur <= start && start <= next) return true;
	else if (start <= cur && next <= end) return true;
	else if (cur <= end && end <= next) return true;
	return false;
}

 

이렇게 해서 O(n^2) 으로 완전 탐색을 통해 결과를 구할 수 있다.

 

Solution Code

 

#include <string>
#include <vector>
#include <algorithm>

using namespace std;

// year / month / day / time 
vector<long long> v_start, v_end;

int getYear(string s) {
	string year = s.substr(0, 4);
	return stoi(year);
}

int getMonth(string s) {
	string month = s.substr(5, 2);
	return stoi(month);
}

int getDay(string s) {
	string day = s.substr(8, 2);
	return stoi(day);
}

int getTime(string s) {
	string h, m, t, mm;
	h = s.substr(11, 2), m = s.substr(14, 2);
	t = s.substr(17, 2), mm = s.substr(20, 3);
	return 1000 * (stoi(h) * 3600 + stoi(m) * 60 + stoi(t)) + stoi(mm);
}

int getGap(string s) {
	int mul_op[4] = { -1, 100, 10, 1 };
	string gap;
	gap = s.substr(24, s.size());
	gap = gap.substr(0, gap.size() - 1);
	if (gap.size() == 1) return 1000 * stoi(gap);
	else {
		string mm = gap.substr(2, gap.size());
		gap = gap.substr(0, 1);
		return 1000 * stoi(gap) + mul_op[mm.size()] * stoi(mm);
	}
}

long long getHash(int year, int month, int day, int time) {
	long long year_ = (long long)year * 1e12;
	long long month_ = (long long)month * 1e10;
	long long day_ = (long long)day * 1e8;
	return year_ + month_ + day_ + (long long)time;
}

bool compare(long long x, long long y) {
	return x < y;
}

bool isProcess(long long cur, long long next, long long start, long long end) {
	if (cur <= start && start <= next) return true;
	else if (start <= cur && next <= end) return true;
	else if (cur <= end && end <= next) return true;
	return false;
}

int solution(vector<string> lines) {
	int answer = 0;
	int year, month, day, time, gap, N;
	long long info;

	N = lines.size();
	for (int i = 0; i < N; i++) {
		string s = lines[i];
		year = getYear(s), month = getMonth(s), day = getDay(s);
		time = getTime(s), gap = getGap(s);
		v_end.push_back((info = getHash(year, month, day, time)));
		v_start.push_back(info - gap + 1);
	}

	sort(v_start.begin(), v_start.end());
	sort(v_end.begin(), v_end.end());

	// start 지점부터 전부 비교
	for (int i = 0; i < N; i++) {
		long long cur = v_start[i], next = v_start[i] + 999;
		int cnt = 0;
		for (int j = 0; j < N; j++) {
			if (isProcess(cur, next, v_start[j], v_end[j])) cnt++;
		}
		answer = max(answer, cnt);
	}

	// end 지점부터 전부 비교
	for (int i = 0; i < N; i++) {
		long long cur = v_end[i], next = v_end[i] + 999;
		int cnt = 0;
		for (int j = 0; j < N; j++) {
			if (isProcess(cur, next, v_start[j], v_end[j])) cnt++;
		}
		answer = max(answer, cnt);
	}

	return answer;
}

 

'프로그래머스 > Level 3' 카테고리의 다른 글

순위  (0) 2022.03.04
2018 KAKAO BLIND RECRUITMENT : 자동완성  (0) 2022.01.03
Comments