이것저것 해보기🌼

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

코딩테스트

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

realtree 2021. 6. 25. 19:12

문제

카카오톡 오픈채팅방에서는 친구가 아닌 사람들과 대화를 할 수 있는데, 본래 닉네임이 아닌 가상의 닉네임을 사용하여 채팅방에 들어갈 수 있다.

신입사원인 김크루는 카카오톡 오픈 채팅방을 개설한 사람을 위해, 다양한 사람들이 들어오고, 나가는 것을 지켜볼 수 있는 관리자창을 만들기로 했다. 채팅방에 누군가 들어오면 다음 메시지가 출력된다.

"[닉네임]님이 들어왔습니다."

채팅방에서 누군가 나가면 다음 메시지가 출력된다.

"[닉네임]님이 나갔습니다."

채팅방에서 닉네임을 변경하는 방법은 다음과 같이 두 가지이다.

  • 채팅방을 나간 후, 새로운 닉네임으로 다시 들어간다.
  • 채팅방에서 닉네임을 변경한다.

닉네임을 변경할 때는 기존에 채팅방에 출력되어 있던 메시지의 닉네임도 전부 변경된다.

예를 들어, 채팅방에 "Muzi"와 "Prodo"라는 닉네임을 사용하는 사람이 순서대로 들어오면 채팅방에는 다음과 같이 메시지가 출력된다.

"Muzi님이 들어왔습니다."
"Prodo님이 들어왔습니다."

채팅방에 있던 사람이 나가면 채팅방에는 다음과 같이 메시지가 남는다.

"Muzi님이 들어왔습니다."
"Prodo님이 들어왔습니다."
"Muzi님이 나갔습니다."

Muzi가 나간후 다시 들어올 때, Prodo 라는 닉네임으로 들어올 경우 기존에 채팅방에 남아있던 Muzi도 Prodo로 다음과 같이 변경된다.

"Prodo님이 들어왔습니다."
"Prodo님이 들어왔습니다."
"Prodo님이 나갔습니다."
"Prodo님이 들어왔습니다."

채팅방은 중복 닉네임을 허용하기 때문에, 현재 채팅방에는 Prodo라는 닉네임을 사용하는 사람이 두 명이 있다. 이제, 채팅방에 두 번째로 들어왔던 Prodo가 Ryan으로 닉네임을 변경하면 채팅방 메시지는 다음과 같이 변경된다.

"Prodo님이 들어왔습니다."
"Ryan님이 들어왔습니다."
"Prodo님이 나갔습니다."
"Prodo님이 들어왔습니다."

채팅방에 들어오고 나가거나, 닉네임을 변경한 기록이 담긴 문자열 배열 record가 매개변수로 주어질 때, 모든 기록이 처리된 후, 최종적으로 방을 개설한 사람이 보게 되는 메시지를 문자열 배열 형태로 return 하도록 solution 함수를 완성하라.

제한사항

  • record는 다음과 같은 문자열이 담긴 배열이며, 길이는 1 이상 100,000 이하이다.
  • 다음은 record에 담긴 문자열에 대한 설명이다.
    • 모든 유저는 [유저 아이디]로 구분한다.
    • [유저 아이디] 사용자가 [닉네임]으로 채팅방에 입장 - "Enter [유저 아이디] [닉네임]" (ex. "Enter uid1234 Muzi")
    • [유저 아이디] 사용자가 채팅방에서 퇴장 - "Leave [유저 아이디]" (ex. "Leave uid1234")
    • [유저 아이디] 사용자가 닉네임을 [닉네임]으로 변경 - "Change [유저 아이디] [닉네임]" (ex. "Change uid1234 Muzi")
    • 첫 단어는 Enter, Leave, Change 중 하나이다.
    • 각 단어는 공백으로 구분되어 있으며, 알파벳 대문자, 소문자, 숫자로만 이루어져있다.
    • 유저 아이디와 닉네임은 알파벳 대문자, 소문자를 구별한다.
    • 유저 아이디와 닉네임의 길이는 1 이상 10 이하이다.
    • 채팅방에서 나간 유저가 닉네임을 변경하는 등 잘못 된 입력은 주어지지 않는다.

입출력 예

 

 

["Enter uid1234 Muzi", "Enter uid4567 Prodo","Leave uid1234","Enter uid1234 Prodo","Change uid4567 Ryan"] ["Prodo님이 들어왔습니다.", "Ryan님이 들어왔습니다.", "Prodo님이 나갔습니다.", "Prodo님이 들어왔습니다."]

 

 

풀이

 

 

문제를 이해하는데 조금 어려웠다.

무지가 들어왔다가 나간 뒤 프로도로 다시 들어오면, 과거에 무지였을때의 채팅방메세지에서도 이름이 바뀐다는 이야기인것 같다.

마찬가지로 만약 이름을 변경하는 경우에도 과거이름으로 만들어진 채팅방메세지에서 이름이 같이 변경된다.

 

나는 먼저 가장쉽게 생각할수 있는 String 배열로 매 명령어 마다 값을 추가하거나 변경하는 방식으로 구현을 했다.

굉장히 비효율적이고 코드도 반복되고

무엇보다 n*n의 시간복잡도를 가지게 되었다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Solution {
    public String[] solution(String[] record) {
        String[] answer = {};
        int ans_cnt = 0;
        int cnt = 0;
        for(int i=0; i<record.length;i++){
            if(record[i].contains("Enter"|| record[i].contains("Leave")) cnt++;
        }
        answer = new String[cnt];
        
        for(int i=0; i<record.length; i++){
            String tmp = "";
            String[] getStr = record[i].split(" ");
            
            if(record[i].contains("Enter")){
                tmp = (getStr[2+ "님이 들어왔습니다.");
                String original_name = "";
                for(int j=0; j<i; j++){
                    if(record[j].contains(getStr[1])){
                        String ori_name[] = {};
                        if(record[j].contains("Enter")){
                            ori_name = record[j].split(" ");
                            original_name = ori_name[2];
                        }
                        answer[j] = answer[j].replace(original_name, getStr[2]);
                    }
                }
                answer[ans_cnt] = tmp;
                ans_cnt++;
            }else if(record[i].contains("Leave")){
                for(int j=0; j<i; j++){
                    if(record[j].contains(getStr[1])){
                        String[] ori_name = record[j].split(" ");
                        tmp = (ori_name[2]+"님이 나갔습니다.");
                        break;
                    }
                }
                answer[ans_cnt] = tmp;
                ans_cnt++;
            }
            else if(record[i].contains("Change")){
                String original_name = "";
                for(int j=0; j<i; j++){
                    if(record[j].contains(getStr[1])){
                        String ori_name[] = {};
                        if(record[j].contains("Enter")){
                            ori_name = record[j].split(" ");
                            original_name = ori_name[2];
                        }
                        
                        answer[j] = answer[j].replace(original_name, getStr[2]);
                     
                    }
                }
            }
        }
        return answer;
    }
}
cs

 

 

그결과 제출시 모든 케이스에 런타임에러와 시간초과에러가 발생했다

 


구글링을 통해 아래 게시글을 참고하여, JAVA Hash Map 자료구조를 사용해 구현해보기로 했다.

https://sundries-in-myidea.tistory.com/46

 

방식은 아래와 같다.

1. hash map 자료구조인 user에는 user id 와 nickname의 매핑에 대한 최신 정보를 저장한다.

2. 그리고 record의 모든 기록을 읽을때, id 와 명령어 종류(들어왔습니다, 나갔습니다) 를 각각 logId와 logCom에 저장한다.

3. 위 연산이 끝난 후, 저장된 logId와 logCom를 한개씩 가져오면서 id에 대한 nickname을 user에서 검색하여 알맞게 출력문을 바꿔 answer에 저장한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import java.util.HashMap;
class Solution {
    public String[] solution(String[] record) {
        HashMap<String,String> user = new HashMap<>();
        String[] logId = {};
        String[] logCom = {};
        String[] answer = {};
        int ans_cnt = 0;
        int cnt = 0;
        for(int i=0; i<record.length;i++){
            if(record[i].contains("Enter"|| record[i].contains("Leave")) cnt++;
        }
        answer = new String[cnt];
        logId = new String[cnt];
        logCom = new String[cnt];
        
        for(int i=0; i<record.length; i++){
            String tmp = "";
            String[] getStr = record[i].split(" ");
            
            if(record[i].contains("Enter")){
                if(user.get(getStr[1])!=null){
                    user.remove(getStr[1]);
                }
                user.put(getStr[1],getStr[2]);
                
                logId[ans_cnt] = getStr[1];
                logCom[ans_cnt] = "님이 들어왔습니다.";
                ans_cnt++;
            }else if(record[i].contains("Leave")){
                logId[ans_cnt] = getStr[1];
                logCom[ans_cnt] = "님이 나갔습니다.";
                ans_cnt++;
            }
            else if(record[i].contains("Change")){
                user.remove(getStr[1]);
                user.put(getStr[1], getStr[2]);
            }
        }
        
        for(int i=0;i<cnt;i++){
            answer[i] = (user.get(logId[i]) + logCom[i]);
        }
        
        return answer;
    }
}
 
 
cs

 

 

코드도 훨씬 보기쉬워지고 구현하기에도 편했다.

결과는 모두 통과였다.

자료구조를 몰라서 그렇지, 알고나면 그만큼 쉬운 문제이긴 했다.

 

 

이기회로 Map 자료구조에 대해서도 알게되었다.

우선 기본적인 내용정도만 알아두어도 좋을것 같다.

 

 

Hash Map 자료구조

: <key>, <value> 의 쌍으로 이루어진 데이터를 보관한다.

 

선언 - HashMap<string,string> user = new HashMap<>();

데이터 넣기 - user.put(id, value)   

데이터 삭제 - user.remove(id)  : id를 key로 갖는 데이터를 삭제한다

데이터 검색 - user.get(id)   :  id를 key로 갖는 데이터의 value를 return다. (없으면 null 리턴)

 

전체 데이터 출력하기

for(String tempKey: keySet){
            System.out.println("map1.get(\"" + tempKey + "\") = " + map1.get(tempKey));
        }

 


Tree Map 자료구조

: <key>, <value> 의 쌍으로 이루어진 데이터를 보관한다.

하지만 Hash Map 과 달리 key 기준 항상 정렬이 되어있어 최솟값, 최댓값을 바로 가져오는 메소드를 지원한다. 레드-블랙 트리로 이루어져있다.

 

선언 -  TreeMap<Integer, String> map = new TreeMap<>();

데이터 넣기 - map.put(id, value)

최소 키 검색 - map.firstKey()  

최대 키 검색 - map.lastKey()

최솟값 entry 검색 - map.firstEntry()   : ex) 1=수박

최댓값 entry 검색 - map.lastEntry()   : ex) 3=멜론

 

entry set을 활용해 전체 출력하기

for (Entry<Integer, String> entry : map.entrySet()) {
    System.out.println("[Key]:" + entry.getKey() + " [Value]:" + entry.getValue());
}