프로그래머스 - 오픈 채팅방💬

최정윤·2022년 7월 22일
0
post-thumbnail

💻출처 : 프로그래머스 > 코딩 테스트 연습 > 2019 KAKAO BLIND RECRUITMENT > 오픈 채팅방
🏆난이도 : Level 2

📜 문제

📝 요약

사용자가 오픈 채팅방을 들어왔다 나갔다 할 수 있다.

사용자는 오픈 채팅방에 들어온 상태에서 닉네임을 바꿀 수 있고, 오픈 채팅방에 입장할 때도 닉네임을 바꿀 수 있다.
오픈 채팅방에서 나가있는 상태에서는 닉네임을 바꿀 수 없다.

사용자가 오픈 채팅방에 들어올 때는 "닉네임님이 들어왔습니다."라는 문구가 출력되고, 오픈 채팅방에서 나갈 때는 "닉네임님이 나갔습니다."라는 로그가 출력된다.

닉네임의 경우 사용자들끼리 중복될 수 있으며, 닉네임을 변경한 경우 로그에 출력된 닉네임까지 모두 변경된다.

사용자들에게는 고유한 아이디가 부여된다.

사용자의 채팅방의 입장&퇴장, 닉네임 변경 기록이 사용자의 아이디와 함께 주어진다. 입장과 닉네임 변경의 경우에는 닉네임도 함께 제공된다.

주어진 기록을 이용하여 최종적으로 출력될 로그를 구하시오.



🧠 문제 분석

결국은 사용자의 아이디에 해당하는 최종적인 닉네임만 알면 되는 문제이다.
이 최종 닉네임을 구하는 방법으로 아래와 같은 방법들을 생각해보았다.

💡아이디어 1 - 시간 초과

첫번째로는 look-up table을 만들어 보는 방식을 생각했다.

사용자가 채팅방에 들어오고 나가는 로그는 여러개 일 수 있는데, 사용자의 아이디에 따른 닉네임은 하나다.

왜냐하면, 우리는 사용자의 처음 닉네임, 두번째로 바꾼 닉네임.. 등의 히스토리가 필요한 것이 아니라, 최종 닉네임 단 하나만 필요하기 때문이다.

따라서, 아래와 같이 테이블을 구성하여 문제를 풀고자 하였다.

우선 문제에서 제공하는 기록을 조회하며 테이블들을 구성하였다.

기본적으로 id와 사용자의 Enter, Leave 정보는 왼쪽의 테이블에 저장하였다.

그리고 사용자의 닉네임을 바꿀 수 있는 Enter, Change 의 경우는 아래와 같은 두가지 상황에 따라 다르게 대처하였다.

  • 오른쪽에 있는 테이블에 id가 있는 경우: 닉네임 변경
  • 오른쪽에 있는 테이블에 id가 없는 경우: id닉네임을 테이블에 새로 추가

위와 같은 규칙으로 최종적인 테이블을 구성하였다면,
왼쪽 테이블의 idstate를 하나씩 읽으며, 오른쪽의 look-up 테이블에서 사용자의 id에 대응하는 닉네임을 조회하여 찾고자 하였다.

이를 구현한 코드는 아래와 같다.

코드

function solution(record) {
    idAndState =[]
    idAndNickname=[]
    
    record.forEach((r)=>{
        let info = r.split(" ")
        const state = info[0]
        const id = info[1]
        const nickname = info[2]
        
        
        if(state==="Change" || state=="Enter"){
            const index = idAndNickname.findIndex((idNick)=>idNick[0]===id)
            if(state==="Change"){
                idAndNickname[index] = [id,nickname]
            }else{
                if(index===-1){
                    idAndNickname.push([id,nickname])
                }else{
                    idAndNickname[index] = [id,nickname]
                }
            }
        }
        
        if(state=="Enter" || state=="Leave"){
            idAndState.push([id,state])
        }
       
    })
    
    var answer = [];
    
    idAndState.forEach((idState)=>{
        const nickname = idAndNickname.find((idNick)=> idNick[0] === idState[0])[1]
        
        if(idState[1] == "Enter"){
            answer.push(`${nickname}님이 들어왔습니다.`)
        }else{
            answer.push(`${nickname}님이 나갔습니다.`)
        }
    })
   
    return answer;
}

이 코드의 결과는 실패다😅

코드를 짜면서도 걱정했었던 시간 초과가 발생했기 때문이다.

테이블 만들면서도 오른쪽 테이블에서 해당 id가 존재하는지 여부를 알기위해id검색 연산을 하고, 출력 로그들을 생성할 때도 오른쪽 테이블에서 id닉네임 검색 연산을 하니...시간을 안초과할수야 없겠더라.

그래도 도무지 아이디어가 나오지 않아 구글링을 하였다.



💡아이디어2 - Map 사용✨

구글링을 통해 얻은 것은 바로 Map 자료구조이다!

Map 자료구조는 key값과 value 값으로 구성되어 있고, 검색속도가 아주 빠르다는 특징을 가지고 있다.

검색 속도가 빠른 이유는, key값을 hashing 한 결과를 인덱스 값으로 사용하기 때문이다.

간단히 말하자면, key 자체를 배열의 인덱스로 사용하는 자료구조라고 생각하면 된다.

그렇기에, key값을 알고 있으면, 그에 대응하는 value를 아주 빠르게 조회할 수 있다.

현재 아이디어 1을 참고해보았을 때, 우리는 id에 대응하는 닉네임을 찾고자 하며, 조회에 시간이 오래걸리는 문제가 있었다.

여기에 Map 구조를 사용하면, idkey에 대응시키고 닉네임value에 대응시킬 수 있으며, Map 구조의 특성 덕분에 실행 속도도 줄일 수 있다.

이를 구현한 코드는 아래와 같다.


💻 코드

function solution(record) {
    const idAndState =[]
    const idAndNickMap = new Map();
    
    record.forEach((r)=>{
        const [state,id,nickname] = r.split(" ");
        
        if (state !== "Change"){
            idAndState.push([id,state])
        }
        
        if(nickname){
            idAndNickMap.set(id,nickname);
        }
       
    })
    
    var answer = [];
    
    idAndState.forEach((idState)=>{
        const [id,state] = idState;
        const nickname = idAndNickMap.get(id)
        
        if(state == "Enter"){
            answer.push(`${nickname}님이 들어왔습니다.`)
        }else{
            answer.push(`${nickname}님이 나갔습니다.`)
        }
    })
   
    return answer;
}

🎤 느낀 점

머릿속 깊은 곳에 있는 Map을 다시 꺼내올 수 있었던 문제였다.
현재 문제에 가장 적합한 자료구조를 생각해내는 것도 중요한 요소라는 생각이 들었다.

profile
매일 뿌듯하기🍬🍭🍡🍫

0개의 댓글