Coding Feature.

[프로그래머스] 오픈채팅방 in C++ 본문

코딩테스트/프로그래머스

[프로그래머스] 오픈채팅방 in C++

codingfeature 2023. 12. 23. 18:46

(제가 생각한 솔루션이므로 최적의 솔루션이 아닐 수 있습니다..! 피드백 환영합니다!)

 

문제 : https://school.programmers.co.kr/learn/courses/30/lessons/42888

1. 문제 이해

문제에서 주어지는 입력값은 총 한 개입니다.

 

record : ['Enter' 또는 'Change' 또는 'Leave'] + [유저 ID] + [유저 닉네임] (vector<string>)

 

위 record에 따라, 유저 닉네임을 알맞게 수정해나가면서 최종적으로 채팅방에 남게 되는 메시지를 출력하는 것이 목표입니다.

 

메시지는 둘 중 하나입니다.

1) [닉네임] + "님이 들어왔습니다." (Enter)

2) [닉네임] + "님이 나갔습니다." (Leave)

 

그리고 닉네임은 총 두 가지 경우에 의해 바뀌게 됩니다.

1) 이미 나갔던 유저가 새로운 닉네임을 가지고 들어온 경우,

2) 이미 들어와있던 유저가 닉네임을 바꾼 경우,

 

만약, 같은 닉네임을 가진 유저가 둘 이상이라면, ID에 의해 구분이 가능합니다.

2. 풀이법 연상

처음에는

"아, 우선 record를 각각 iterate 하면서 Enter 또는 Change 또는 Leave에 맞게 바로 answer에 메시지를 삽입하자."

라는 생각을 했었습니다.

 

하지만 위 생각에는 한계가 있었습니다.

우선 record를 탐색하는 동안 닉네임이 수시로 바뀌게 되는데, 바뀐 닉네임이 같은 ID를 가진 사람의 모든 메시지에 대해서 계속해서 적용되어야 했기 때문에 불필요한 과정이 반복되는 단점이 있었습니다.

 

그럴 바에 1. 우선 record를 처음 iterate하면서 사용자의 ID에 따라 닉네임을 입력 또는 변경시키고,

2. 다시 record를 iterate를 할 때에는 이전 iteration에 의해 생성된 ID-닉네임 연관성을 참조해서 메시지를 answer에 집어넣는 것이 낫다고 생각했습니다.

 

3.  풀이에 필요한 지식

ID-닉네임 연관성을 저장 및 관리하기 위해 C++의 STL 중 map 자료구조를 사용하기로 했습니다.

map 의 구조는 Key, Value로 나누어져 있고, Key가 중복이 될 수 없습니다.

 

map을 통해 사용자의 ID를 Key 값으로, 닉네임을 Value 값으로 사용하고 필요에 따라 특정 ID를 가진 유저의 닉네임을 삽입, 삭제, 수정할 수 있습니다.

 

C++의 STL에는 ordered map(그냥 map)과 unordered map이 있습니다.

ordered map, 또는 그냥 map의 경우에는 Key 값을 기준으로 정렬을 하는 map 자료구조입니다.

반면에 unordered map의 경우에는 정렬을 따로 하지 않기 때문에 시간복잡도가 O(1)이라고 합니다. 

 

즉 저희가 일반적으로 생각하는 Hash map과 같은 구조는 unordered map이라고 보면 되겠네요!

 

좀 더 찾아보니 데이터의 개수가 적은 경우 map, 데이터의 개수가 굉장히 많은 경우 unordered map이 낫다고 합니다.

 

map은 다음과 같이 선언합니다.

map<Key 타입, Value 타입> map이름;

 

Key가 "1234"이고 value가 "hello"인 데이터를 삽입하고 싶다면 다음과 같이 작성할 수 있습니다. 그리고 value의 수정도 같은 방식으로 할 수 있습니다.

#include <map>

map<string, string> map1;
string Key = "1234";
string Value = "hello";
string Value2 = "bye";

map1[Key] = Value; // "1234"가 Key인 Value 삽입.
map1[Key] = Value2; // "1234"가 Key인 Value를 Value2로 수정.

 

 

 

그 다음, record의 각 문자열은 공백으로 구분지어져 ID, 닉네임 등을 받기 때문에 문자열 파싱이 필요했습니다.

C++에서 어떤 문자열에 대해서 공백으로 구분지어 각 단어를 입력받기 위해서 stringstream 라이브러리를 사용하기로 했습니다.

 

stringstream 라이브러리는 어떤 문자열에서 저희가 원하는 데이터를 파싱하기에 굉장히 유용하게 사용됩니다!

 

이번 문제의 경우에는 다음과 같이 사용될 수 있습니다.

#include <sstream>
#include <string>

string command, id, nickname;
stringstream ss("Enter 1234 Muzi"); // 공백으로 구분된 문자열을 stringstream에 입력. 

ss >> command >> id >> nickname; // command, id, nickname에 각각 "Enter", "1234", "Muzi"가 삽입된다.

 

 

4. 코딩

#include <string>
#include <vector>
#include <sstream>
#include <unordered_map>

using namespace std;

vector<string> solution(vector<string> record) {
    vector<string> answer;
    int record_size = record.size(); // record의 개수.
    unordered_map<string, string> idname_map; // id가 key, nickname이 value인 map.
    string command, id, nickname;
    
    // idname_map 데이터 갱신.
    for(int i=0;i<record_size; i++){
        stringstream ss(record[i]);
        ss >> command >> id >> nickname;
        if(command == "Enter" || command == "Change")
            idname_map[id] = nickname; // 각 id를 가진 유저의 닉네임을 입력 또는 수정.
    }
    
    // idname_map을 바탕으로 answer에 메시지 입력.
    for(int i=0;i<record_size; i++){
        stringstream ss(record[i]);

        ss >> command >> id >> nickname;
        if(command == "Enter")
            answer.push_back(idname_map[id] + "님이 들어왔습니다.");
        else if(command == "Leave")
            answer.push_back(idname_map[id] + "님이 나갔습니다.");
    }
    
    return answer;
}
 

 

5. 보완할 점.

Leave의 경우, Enter나 Change와는 달리 사용자의 ID만 주어지는데, stringstream으로 문자열 파싱을 할 때에는 위 경우를 따로 처리하지 않고 그대로 command, id, nickname을 받게끔 코드를 작성했습니다.

따라서 Leave일 때, nickname 스트링 변수에 garbage value 또는 이전 value가 저장되어 추가적으로 어떤 기능을 구현하는 경우에는 문제가 발생할 수 있습니다.

 

위 문제를 해결하기 위해 stringstream을 받을때 우선 command와 id만 받고 command에 따라 다르게 동작하도록 할 수 있을 것입니다.

 

ss >> command >> id;

if(command == "Leave")
	...
else
	ss >> nickname; // 추가로 닉네임을 입력 받음.
	...