www.acmicpc.net/problem/1406

 

1406번: 에디터

첫째 줄에는 초기에 편집기에 입력되어 있는 문자열이 주어진다. 이 문자열은 길이가 N이고, 영어 소문자로만 이루어져 있으며, 길이는 100,000을 넘지 않는다. 둘째 줄에는 입력할 명령어의 개수

www.acmicpc.net

1. 풀이방법

 

- 커서가 여러칸씩 뛰는 명령어는 없으므로 상대적으로 간단하였습니다.

 

- 저는 커서의 앞쪽은 VECTOR 커서의 뒤쪽은 STACK 을 이용하였습니다.

 

- 방법은 여러가지가 있을 것 같습니다 편하신대로...

 

- 커서를 왼쪽으로 옮길 때 커서의 뒤쪽이 되는 한 문자는 STACK에 푸쉬하고

 

- 커서를 오른쪽으로 옮길 때는 STACK에서 POP해 와서 다시 VECTOR에 넣는 식으로 하였습니다.

 

- 아마 시작할 떄의 커서의 위치가 맨 뒤쪽이라서 이러한 생각을 자연스럽게 하게된 것 같습니다.

 

 

2. 주의사항

 

 

3. 나의코드

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
#include<queue>
#include<stack>
using namespace std;


int main() {
	ios_base::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	string s;
	vector<char> tmpc;
	stack<char> tmps;
	int cursor;
	cin >> s;
	for (int i = 0; i < s.length(); i++) {
		tmpc.emplace_back(s[i]);
	}
	cursor = s.length();
	int n;
	cin >> n;
	while (n--) {
		char c;
		cin >> c;
		if (c == 'L') {
			if (cursor == 0) continue;
			else { //커서 뒤쪽은 스택으로 옮겨 놓음
				tmps.push(tmpc[cursor - 1]); tmpc.pop_back(); cursor--;
			}
		}
		else if (c == 'D') {
			if (tmps.empty()) continue;
			else {
				tmpc.emplace_back(tmps.top()); tmps.pop();
				cursor++;
			}
		}
		else if (c == 'B') {
			if (cursor == 0) continue;
			else {
				tmpc.pop_back();
				cursor--;
			}
		}
		else {
			char c;
			cin >> c;
			tmpc.emplace_back(c);
			cursor++;
		}
	}

	//출력부
	for (int i = 0; i < tmpc.size(); i++) {
		cout << tmpc[i];
	}
	while (!tmps.empty()) {
		cout << tmps.top();
		tmps.pop();
	}


	return 0;
}

'알고리즘 문제풀이 > 스택' 카테고리의 다른 글

백준 10799 [C++]  (0) 2021.01.09
백준 1874 [C++]  (0) 2021.01.09
백준 3986  (0) 2020.03.02
백준 2493  (0) 2020.01.08

1. 페이지네이션(페이징) 이란 ?

- 서버와 클라이언트의 상황에서 보통 모든 데이터를 한 번에 가져오지 않습니다.

 

- 보통 필요한 갯수를 지정하고 상황에 맞춰 정렬기준이 조건에 추가됩니다( 정렬기준 + 갯수)

 

- 이러한 조건을 맞춰서 데이터를 가져오는 것을 Pagenation(페이지네이션), 페이징 이라고 합니다.

 

- 페이지네이션을 처리하는 방법으로는 대표적으로 1. 오프셋 기반 페이지네이션, 2.커서 기반 페이지네이션

  으로 처리 가능합니다.

 

- 비교적 구현이 매우 간단한 오프셋 기반 페이지네이션 부터 살펴보겠습니다.

 

 

 

2. Offset-based Pagination (오프셋 기반)

 

- 제가 사용하는 MySQL로 말씀드리자면 LIMIT을 사용하여 개수를 지정하면 됩니다.

SELECT UserId FROM membership ORDER BY CreateAt desc LIMIT 20,40

 

- 좀 더 일반적인 변수를 사용한 쿼리문은 이렇게 되겠네요

( pagenum -> 페이지(1.2.....) )

( takenum -> 한번에 불러올 데이터(row) 수)

'SELECT UserId FROM membership
ORDER BY CreateAt DESC
LIMIT' + ( $takenum*($pagenum-1))+','+$takenum;

- 이렇듯 매우 직관적이고 이해하기가 쉽습니다. 사용하기도 편하구요.

 

- 보통 밑에 이동할 수 있는 페이지가 있고 페이지를 뛰어 넘어갈 수 있다면 offset 기반입니다.

 

- 하지만 제가 생각하는 가장 와닿는 문제는 예를 들겠습니다.

 

- 사용자가 매우 많은 사이트에서 글을 쓰는 사람이 매우 많다고 하겠습니다.

 

- 초당 평균 20개의 새로운 데이터가 들어온다고 가정하고, 한 페이지당 20개의 데이터를

 

- 보여준다면, 이를 오프셋 기반으로 구현을 한다면 1페이지의 20개의 글을 본 사람이

 

- 2페이지의 글을 보려고 눌렀을때, 방금 본 20개의 데이터를 그대로 다시 보게 됩니다.

 

- 즉, 중복데이터를 출력하게 됩니다. 그리고 이런 최악은 아니더라도 생각보다 이런 상황은

 

- 빈번하게 나타나고, 아마  경험하신 분들도 많이 있으실거라 생각합니다.

 

- 또 다른 문제는, 정렬기준 조건에 따라서 row가 몇번 째 데이터인지 바뀌게 되는데,

 

- 당연히 DB는 모든 경우에 따른 rownum을 가지고 있지 않기때문에 

 

- row의 수가 많아질수록 성능에 문제가 생기게 됩니다.

 

-이러한 단점을 보완해줄 수 있는 것이 커서 기반 페이지네이션 입니다.

 

 

 

3. Cursor-based Pagination (커서 기반)

 

- 음.... cursor를 포인터라고 생각하면 이해가 빠를 것 같습니다.

 

- cursor를 만들어 놓고 "이 cursor가 가리키는 것 다음 부터 n개의 데이터를 주세요" 하는 방식입니다.

 

- 오프셋 방식을 굳이 예로 들자면 "n개의 데이터를 page-1만큼 스킵하고 그 다음부터 n개의 데이터를 주세요"

  가 될 것 같네요.

 

- 만약 cursor가 int로 된 UserId라면 UserId 자체를 커서로 사용하여도 상관이 없습니다. (UserId - PK)

 

- 하지만 cursor를 회원가입 시기(CreateAt) 라고 지정한다면 어떻게 될까요?????

(데이터가 만들어진 순서대로 출력하고 싶을 때)

 

- 상황 설명을 위해 LIMIT를 사용해서 현재 가정한 테이블의 상태를 확인해보겠습니다.

  (CreateAt은 설명의 편의성을 위해서 간단한 숫자로 대체하여 표현하겠습니다.)

 

SELECT UserId,CreateAt FROM membership 
ORDER BY CreateAt ASC LIMIT 4

 

UserId CreateAt
123 1
124 2
120 3
114 4

 

- 전혀 문제가 없어 보입니다. 하지만 만약에 UserId가 "114"와 동시에 가입한 115,116,117이 있을경우

 

- cursor를 CreateAt으로 설정할 경우 cursor>4 인 리스트를 다음페이지에 불러올 텐데 그럴경우

 

- 115,116,117의 데이터는 누락이 발생합니다.

 

- 여기서 알 수 있는 것은

 

- "커서 기반 페이지네이션을 위해서는 정렬 기준에 포함되는 필드 중 하나이상은 반드시 고유값을 가져야 한다는 것 "

 

- 설명을 위해 테이블의 상황을 살짝 수정하겠습니다. 아래와 같습니다.
  (말이 좀 안되지만 age는 중복값이 없는 고유값이라고 가정을 좀 하겠습니다.)

UserId CreateAt Age
123 1 17
124 2 18
120 3 19
114 4 20

- 이 다음 페이지의 데이터를 얻기 위해서는 다음과 같은 쿼리를 날리면 됩니다.

 

SELECT UserId,CreateAt,Age FROM membership
WHERE (CreateAt >4) or (CreateAt=4 and Age>20)
ORDER BY CreateAt ASC, Age ASC LIMIT 4

- 이렇게 날리면 CreateAt이 4 인 115,116,117도 누락되지 않습니다.

 

- 문제가 모두 해결된 것 처럼 보이지만 여기서도 제가 생각하는 문제점 중 하나는

 

- 이렇게 조건절에 필요사항이 달림에 따라서 클라이언트 측에서도 이를 이해하고

 

- ORDER BY에 달려있는 필드 들을 이해하고 이에 해당하는 값들을 요청시 마다 보내야 하는데

 

- 당연히 클라이언트 측은 그걸 좋아하지 않고 원치 않습니다

 

- 그러므로 WHERE절에 걸리는 조건들을 이용해서 고유한 값인 CURSOR(커서)를 만들면 됩니다.

 

- 간단한 예시로 CreateAt이 최대 6자라고 하고 AGE가 최대 3자 라고 가정하면,

  

- 두 가지를 합쳐서 "4" + "17" => "000004017" 이런 식으로 가공하여 CURSOR 값으로 사용을 하는 것입니다.

 

- 커서를 기준으로 출력 후 , 그 다음 리스트는 이 커서 기준으로 다음부터 데이터를 가져오면

 

- 위의 문제들이 해결된다는 것을 알 수 있습니다.

 

 

 

 

 

4. Offset vs Cursor

 

- 제 생각에는 왠만하면 커서를 이용하는 것이 좋은 것 같습니다만

 

- 데이터의 입력이 매우 드물고, 또는 중복되는 데이터가 나와도 크게 상관이 없는경우

 

- 데이터의 수 자체가 그리 많지 않아서 성능에 딱히 연연하지 않아도 되는 경우

 

- 등에서는 오프셋을 사용해도 크게 문제되지는 않을 것 같습니다.

 

- 하지만 유저가 접속해서 사용하는 경우 커서 기반을 사용하는 것이 좋습니다.

'Web & App > Mysql' 카테고리의 다른 글

Query문의 실행 순서  (2) 2020.10.27
mysql 외부 접속 허용  (0) 2020.08.17

+ Recent posts