문제

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

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

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

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

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

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

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

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

예를 들어, 채팅방에 "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 이하이다.
    • 채팅방에서 나간 유저가 닉네임을 변경하는 등 잘못 된 입력은 주어지지 않는다.

입출력 예

recordresult

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

입출력 예 설명

입출력 예 #1
문제의 설명과 같다.

 

 

 

풀이

function solution(record) {
    let tempList = [];
    let answer = [];
    let userInfo = {}
    record.forEach((item) =>{
       let list = item.split(" ")
        tempList.push(list)
        if(list[2]){
            userInfo[list[1]] = list[2]
        }
    })

    
    tempList.forEach((item) => {
        if(item[0] === "Enter"){
            answer.push(`${userInfo[item[1]]}님이 들어왔습니다.`)
        }else if(item[0] === "Leave"){
            answer.push(`${userInfo[item[1]]}님이 나갔습니다.`)
        }
    })
    
    
    return answer
}

 

uuid로 유저를 저장하기 위해서 userInfo 라는 객체를 생성하고, for문 안에서 uuid에 해당하는 이름을 갱신하는게 포인트다.

처음엔 Leave 일시, split으로 나눴던 배열 안의 index 2번 값이 비어있는걸 고려하지 않아 오류가 났다.

if문으로 비어있는지 아닌지 체크를 해야한다. 비어있지 않을때 userInfo의 uuid의 이름을 갱신해야한다.

자바스크립트에서 event를 컨트롤 할 때 preventDefault라는 메서드를 자주 접하게 된다.

 

event.preventDefault()는 해당 event가  cancelable 할때 (cancelable이 true일 때) 해당 이벤트의 기본 동작의 수행을 막는 메서드이다. 모든 event가 cancelable 하지는 않으니 모든 이벤트에 유효한 메서드는 아니다. cancelable 하지 않은 이벤트는 preventDefault를 호출해도 아무 반응이 일어나지 않는다.

 

 

예를 들어서 체크박스의 클릭이벤트를 막아 체크박스를 체크하지 못하게 만든다거나, submit 이벤트에 사용하여 데이터를 전송하지 못하게 하는 등 다양한 방면에서 사용 가능하다.

 

그러나 preventDefault는 이벤트 전파를 막지는 못합니다. 상위요소로의 이벤트 전파를 막기 위해서는 stopPropagation() 또는 stopImmediatePropagation()을 함께 사용해야합니다.

 

참고

https://developer.mozilla.org/ko/docs/Web/API/Event/preventDefault

 

Event.preventDefault() - Web API | MDN

Event 인터페이스의 preventDefault() 메서드는 어떤 이벤트를 명시적으로 처리하지 않은 경우, 해당 이벤트에 대한 사용자 에이전트의 기본 동작을 실행하지 않도록 지정합니다.

developer.mozilla.org

 

1.map

각 요소에 대해 주어진 함수를 수행하는 결과를 모아 새로운 배열을 반환한다.

ex) let newArray = testArray.map((value, index, array) => {

           // value : 현재 요소의 값, index : 현재 요소의 인덱스, array : 원본 배열
            return value * 2;
       });

 

2.filter

각 요소에 대해 주어진 함수의 결과값이 true인 결과를 모아 새로운 배열을 반환한다.

ex) let newArray = testArray.filter((value, index, array) => {

           // value : 현재 요소의 값, index : 현재 요소의 인덱스, array : 원본 배열
            return value * 2 > 0 //boolean 값 이어야 한다.
       });

 

map과는 다르게 return 값이 boolean 이고, true인 결과를 모아 새로운 배열을 반환하는데, 메서드 이름 그대로 , 어떤 배열을 조건에 맞게 필터링할 때 (ex validation을 체크해서 새로운 배열을 만들 때) 사용하기 쉽다. 

 

3.reduce

배열 각 요소에 대해 reducer함수를 실행하고 배열이 아닌 하나의 결과값을 반환한다.

ex) const numberList = [1, 2, 3, 4];
 const sum = numberList.reduce((acc, value) => { 
        //acc : 축적된 데이터 value : 현재 요소의 값
        return acc + cur; 
    }, 10 // 초기 값);
예를들어 위와 같이 사용하였을 때는 10 + 1 + 2 + 3+ 4 의 계산결과인 20이 sum에 할당 되게 된다.
초기값에 빈 배열을 할당하고 value를 그 배열에 push 하는 형태로 사용하면 새로운 배열을 만들어 낼 수 있다는 점에서 filter와 map을 다 아우를 수 있는 메서드이다. 다양한 방면에 적용이 가능하고 퍼포먼스 적인 측면에서도 가장 낫기 때문에 가장 많이 쓰인다. 

 

 
 

자바스크립트 런타임 구성요소

setTimeout 의 콜백함수가 delay시간 이후에 정확하게 호출되지 않는 이유는 자바스크립트 런타임 구성요소를 알아야 이해가 쉽다.

 

자바스크립트는 기본적으로 싱글 스레드 이므로 한개의 작업만을 다룰 수 있다. 그렇기 때문에 하나의 프로그램에서 동시에 하나의 코드만 실행할수있다. 

 

1) Call Stack

 자바스크립트 메서드 호출 시 실행 순서대로 call stack에 쌓이게되고(push), 함수 실행이 완료되면 스택에서 제거된다.(pop)(선입선출)

setTimeout도 호출되면 callStack에 먼저 쌓이게된다. 이후 호출은 완료되었기 때문에 stack에서 지워지게된다. 

 

2) Callback Queue

 

WebAPI의 콜백이 쌓이는 곳이다. 설정했던 delay시간이 지나고 나면 setTimeout의 콜백이 쌓이게 된다.

Web API는 자바스크립트가 실행되는 런타임 환경에 존재하는 별도의 API 이다. (V8 소스코드에는 존재하지 않는다.) ex) setTimeout, setInterval, setImmediate 등..

 

3) Event Loop

이벤트 루프는 콜 스택이 빌 때까지 기다린 후 콜백 큐에 있는 콜백을 콜 스택에 넣어주는 역할을 한다. 

 

다시 한번 설명하면 1. setTimeout이 실행된 시점에 Call Stack에 setTimeout이 쌓이게 되고, 호출이 완료된 후 제거된다.

2. 호출 이후 지정한 delay시간이 지나고나면 setTimeout의 콜백함수가 Callback Queue에 쌓이게 된다.

3. 이벤트 루프는 콜스택이 비게되면 콜백 큐에 있는 콜백함수를 Call Stack에 넣어준다.

 

이때 Call Stack에 다른 할일이 많이 쌓여있다면 콜백함수가 호출되는 시점은 더 늦어질 것이다. 그렇기 때문에 setTimeout으로 지정한 delay시간이 지나고 나서 지연시간이 생기게 된다. 

 

 

문제

문제 설명

배열 arr가 주어집니다. 배열 arr의 각 원소는 숫자 0부터 9까지로 이루어져 있습니다. 이때, 배열 arr에서 연속적으로 나타나는 숫자는 하나만 남기고 전부 제거하려고 합니다. 단, 제거된 후 남은 수들을 반환할 때는 배열 arr의 원소들의 순서를 유지해야 합니다. 예를 들면,

  • arr = [1, 1, 3, 3, 0, 1, 1] 이면 [1, 3, 0, 1] 을 return 합니다.
  • arr = [4, 4, 4, 3, 3] 이면 [4, 3] 을 return 합니다.

배열 arr에서 연속적으로 나타나는 숫자는 제거하고 남은 수들을 return 하는 solution 함수를 완성해 주세요.

제한사항
  • 배열 arr의 크기 : 1,000,000 이하의 자연수
  • 배열 arr의 원소의 크기 : 0보다 크거나 같고 9보다 작거나 같은 정수

입출력 예arranswer
[1,1,3,3,0,1,1] [1,3,0,1]
[4,4,4,3,3] [4,3]
입출력 예 설명

입출력 예 #1,2
문제의 예시와 같습니다.

 

 

풀이

스택을 이용한 방법으로 풀어보았다.

 

function solution(arr)
{
    var answer = [];

    arr.forEach((item) => {
        if(answer.length === 0){
            answer.push(item)
        }else{
            let a = answer.pop()
            let b = item
            if(a == b){
                answer.push(b)
            }else{
                answer.push(a)
                answer.push(b)
            }
        }
        
    })
    return answer;
}

 

 

setTimeout 이란?

일정 시간이 지나고 지정한 코드를 실행시키는 메서드로 보통 아래와 같은 형태로 자주 사용한다.

setTimeout(() => {console.log("첫 번째 메시지")}, 5000);
setTimeout(() => {console.log("두 번째 메시지")}, 3000);
setTimeout(() => {console.log("세 번째 메시지")}, 1000);

위에서 , 뒤에 있는 숫자가 지연 시간을 나타내는데, 단위는 밀리세컨드이다. (1000 = 1초)

 

정확히 지정한 시간 이후 코드가 실행될까?

그런데 실제로 사용해보면 정확하게 지정한 시간이 지났을때 코드가 실행되지 않고 지연되는 경우가 발생한다. 글쓴이의 경우 스크롤을 이동시킬 때 setTimeout에 딜레이를 0으로 주고 그 안에서 scrollTo메서드를 사용할 때와 setTimeout으로 감싸지 않고 scrollTo를 사용할 때 결과에 차이가 발생하여(setTimeout으로 감싼 경우에만 스크롤이 이동하였다) setTimeout에대해 더 찾아보게 되었고, 그 결과 다음과 같은 문서를 보게 되었다. 실제로 찾아보면 scroll이동 이벤트는 많은 사람들이 setTimeout과 함께 사용하는 것을 추천하고 있다.

 

https://developer.mozilla.org/ko/docs/Web/API/setTimeout#%EB%94%9C%EB%A0%88%EC%9D%B4%EA%B0%80_%EC%A7%80%EC%A0%95%ED%95%9C_%EA%B0%92%EB%B3%B4%EB%8B%A4_%EB%8D%94_%EA%B8%B4_%EC%9D%B4%EC%9C%A0

 

setTimeout() - Web API | MDN

전역 setTimeout() 메서드는 만료된 후 함수나 지정한 코드 조각을 실행하는 타이머를 설정합니다.

developer.mozilla.org

 

위 문서에 나온 내용을 요약해보면

1. 중첩 타임아웃 사용시

브라우저는 setTimeout 호출이 5번 이상 호출된 경우 4ms의 최소 타임아웃을 강제함

2. 비활성탭의 타임아웃

백그라운드 탭으로 인한 부하(와 그로 인한 배터리 사용량)를 경감하기 위해, 브라우저는 비활성 탭에서의 최소 딜레이에 최소 값을 강제함

3. 페이지, 운영체제, 브라우저가 다른 작업으로 인해 바쁠 경우

페이지, 운영체제, 브라우저가 다른 작업으로 인해 바쁠 경우 타임아웃이 예상보다 늦게 실행될 수 있습니다.

 

나의 경우 3번에 해당하는 경우라고 추정하였다. (서버에서 데이터를 불러오고, 이를 이용하여 렌더링을 마치고 나서 스크롤이 생긴 이후 스크롤을 이동시켜야 하기 때문에)

 

이를 더 자세히 이해하기 위해서는 자바스크립트 런타임에 대한 이해가 필요한데, 이는 다음장에서 설명하도록 하겠다.

맞닥드린 문제

최근 구글 플레이스토어 HTML 태그를 파싱해서 현재 스토어에 올라가있는 앱 버전을 가지고와 업데이트 체크를 하는 로직이 구글측에서 태그를 변경하면서 사용할 수 없게 되자 RemoteConfig로 config값을 옮기는 과정이 있었다. 그러던 중 업데이트 버전을 받아오는 로직을 독립적인 클래스의 함수로 만들어 의존성을 줄이고자 하였는데, listener안에서 적시에 받아온 값을 어떻게 사용하느냐에 대한 문제가 생겼다. 

 

생각해본 해결방법

매개변수에 함수를 전달하여 실행시키면 어떨까? 라는 생각이 들어 검색해봤고, 코틀린은 고차함수와 람다를 제공함을 알아내어 적용시켜보았다. 고차함수란 일반함수에 또다른 함수를 인자나 반환값으로 사용하는 함수이다. 

사용법과 설명은 https://play.kotlinlang.org/byExample/04_functional/01_Higher-Order%20Functions

 

Kotlin Playground: Edit, Run, Share Kotlin Code Online

 

play.kotlinlang.org

https://kotlinlang.org/docs/lambdas.html#function-literals-with-receiver

 

High-order functions and lambdas | Kotlin

 

kotlinlang.org

를 참고하면 좋다.

 

해결?

나는 인자에 () -> Unit 형의 함수를 넣어 원하는 곳에서 실행시키는 방식으로 이 문제를 해결하였다. 반환자료형이 없을 때는 Return을 Unit 형태로 선언하면 된다. 고차함수와 람다는 생소해서 잘 쓰지 않게되는 문법이지만, 필요할때가 종종 있어 인자에 함수를 넣는 형태로는 사용할때가 있다.. 모양이 어색해서 '이게 잘 쓰는건가?' 싶을때가 많지만 어쨌든 코틀린의 편리한 특징임은 틀림 없는듯 하다

 

문제

단어 s의 가운데 글자를 반환하는 함수, solution을 만들어 보세요. 단어의 길이가 짝수라면 가운데 두글자를 반환하면 됩니다.

재한사항
  • s는 길이가 1 이상, 100이하인 스트링입니다.
입출력 예 return
"abcde" "c"
"qwer" "we"

풀이

function solution(s) {
    var answer = '';
    
    if((s.length % 2) == 1){
        //2로 나누면 1이 생김
        answer = s[((s.length + 1)/2) - 1]
    }else{
        //나누어 떨어짐
        answer = s[s.length/2 - 1] + s[s.length / 2]
        console.log(s[s.length - 1] )
    }
    return answer;
}

 

문제

네오는 평소 프로도가 비상금을 숨겨놓는 장소를 알려줄 비밀지도를 손에 넣었다. 그런데 이 비밀지도는 숫자로 암호화되어 있어 위치를 확인하기 위해서는 암호를 해독해야 한다. 다행히 지도 암호를 해독할 방법을 적어놓은 메모도 함께 발견했다.

  1. 지도는 한 변의 길이가 n인 정사각형 배열 형태로, 각 칸은 "공백"(" ") 또는 "벽"("#") 두 종류로 이루어져 있다.
  2. 전체 지도는 두 장의 지도를 겹쳐서 얻을 수 있다. 각각 "지도 1"과 "지도 2"라고 하자. 지도 1 또는 지도 2 중 어느 하나라도 벽인 부분은 전체 지도에서도 벽이다. 지도 1과 지도 2에서 모두 공백인 부분은 전체 지도에서도 공백이다.
  3. "지도 1"과 "지도 2"는 각각 정수 배열로 암호화되어 있다.
  4. 암호화된 배열은 지도의 각 가로줄에서 벽 부분을 1, 공백 부분을 0으로 부호화했을 때 얻어지는 이진수에 해당하는 값의 배열이다.

네오가 프로도의 비상금을 손에 넣을 수 있도록, 비밀지도의 암호를 해독하는 작업을 도와줄 프로그램을 작성하라.

입력 형식

입력으로 지도의 한 변 크기 n 과 2개의 정수 배열 arr1, arr2가 들어온다.

  • 1 ≦ n ≦ 16
  • arr1, arr2는 길이 n인 정수 배열로 주어진다.
  • 정수 배열의 각 원소 x를 이진수로 변환했을 때의 길이는 n 이하이다. 즉, 0 ≦ x ≦ 2n - 1을 만족한다.

출력 형식

원래의 비밀지도를 해독하여 '#', 공백으로 구성된 문자열 배열로 출력하라.

입출력 예제

매개변수값

n 5
arr1 [9, 20, 28, 18, 11]
arr2 [30, 1, 21, 17, 28]
출력 ["#####","# # #", "### #", "# ##", "#####"]

매개변수값

n 6
arr1 [46, 33, 33 ,22, 31, 50]
arr2 [27 ,56, 19, 14, 14, 10]
출력 ["######", "### #", "## ##", " #### ", " #####", "### # "]

 

풀이

 

function solution(n, arr1, arr2) {
    var answer = [];
    var firstList = makeList(n,arr1);
    var secondList = makeList(n,arr2);
    
    for(let i = 0 ; i < n ; i++){
        let tempList = "";
        for(let j = 0; j < n; j++) {
            if((firstList[i][j] + secondList[i][j]) >= 1){
                tempList += '#'
            }else{
                tempList += ' '
            }
        }
         answer.push(tempList)
    }
    return answer;
}

function makeList(n, arr){
    var list = []; 
    for(let i = 0; i < arr.length; i++){
            var innerList = [];
            var num = arr[i]
            
            while(num > 0){
                innerList.unshift(num % 2)
                num = parseInt(num / 2)
            }
     
        while(innerList.length < n){
            innerList.unshift(0)
        }   
        list.push(innerList)
        
    }
    return list
}

 

 

 

makeList 라는 메서드를 사용하여 각각 이진법 리스트를 생성하여 해결하였다.

이중에서 눈여겨 봐야할 것은 push 대신에 unshift를 사용해야 한다는 점이다! (push는 나중에 계산된 값이 array 끝으로 가기 때문에) **

unshift까먹지말자!

문제 설명

명함 지갑을 만드는 회사에서 지갑의 크기를 정하려고 합니다. 다양한 모양과 크기의 명함들을 모두 수납할 수 있으면서, 작아서 들고 다니기 편한 지갑을 만들어야 합니다. 이러한 요건을 만족하는 지갑을 만들기 위해 디자인팀은 모든 명함의 가로 길이와 세로 길이를 조사했습니다.

아래 표는 4가지 명함의 가로 길이와 세로 길이를 나타냅니다.

명함 번호가로 길이세로 길이
1 60 50
2 30 70
3 60 30
4 80 40

가장 긴 가로 길이와 세로 길이가 각각 80, 70이기 때문에 80(가로) x 70(세로) 크기의 지갑을 만들면 모든 명함들을 수납할 수 있습니다. 하지만 2번 명함을 가로로 눕혀 수납한다면 80(가로) x 50(세로) 크기의 지갑으로 모든 명함들을 수납할 수 있습니다. 이때의 지갑 크기는 4000(=80 x 50)입니다.

모든 명함의 가로 길이와 세로 길이를 나타내는 2차원 배열 sizes가 매개변수로 주어집니다. 모든 명함을 수납할 수 있는 가장 작은 지갑을 만들 때, 지갑의 크기를 return 하도록 solution 함수를 완성해주세요.


제한사항
  • sizes의 길이는 1 이상 10,000 이하입니다.
    • sizes의 원소는 [w, h] 형식입니다.
    • w는 명함의 가로 길이를 나타냅니다.
    • h는 명함의 세로 길이를 나타냅니다.
    • w와 h는 1 이상 1,000 이하인 자연수입니다.

입출력 예sizesresult
[[60, 50], [30, 70], [60, 30], [80, 40]] 4000
[[10, 7], [12, 3], [8, 15], [14, 7], [5, 15]] 120
[[14, 4], [19, 6], [6, 16], [18, 7], [7, 11]] 133

입출력 예 설명

입출력 예 #1
문제 예시와 같습니다.

입출력 예 #2
명함들을 적절히 회전시켜 겹쳤을 때, 3번째 명함(가로: 8, 세로: 15)이 다른 모든 명함보다 크기가 큽니다. 따라서 지갑의 크기는 3번째 명함의 크기와 같으며, 120(=8 x 15)을 return 합니다.

입출력 예 #3
명함들을 적절히 회전시켜 겹쳤을 때, 모든 명함을 포함하는 가장 작은 지갑의 크기는 133(=19 x 7)입니다.

 

풀이

푸는 방법은 각각 너비와 높이를 주어진 2차원 배열의 길이 만큼 for문을 돌려서 비교한다음 큰수는 maxList에 작은수는 minList에 담아 마지막에는 maxList minList에서 가장 큰 값들을 곱해 정답을 구하는 방식으로 풀었다

 

function solution(sizes) {

var answer = 0;

var maxList = [];

var minList = [];

 

 

for(let i = 0; i < sizes.length; i++){

maxList.push(Math.max(sizes[i][0], sizes[i][1]))

minList.push(Math.min(sizes[i][0], sizes[i][1]))

} //큰수는 큰수끼리, 작은수는 작은수끼리 모아보기

 

maxList.sort((a,b)=> a-b)

minList.sort((a,b)=> a-b)

 

answer = maxList[maxList.length-1] * minList[minList.length-1]

 

 

 

return answer;

}

+ Recent posts