IT/Algorithm

프로그래머스) 완주하지 못한 선수

프티 2021. 6. 17. 15:54
반응형

https://programmers.co.kr/learn/courses/30/lessons/42576

 

코딩테스트 연습 - 완주하지 못한 선수

수많은 마라톤 선수들이 마라톤에 참여하였습니다. 단 한 명의 선수를 제외하고는 모든 선수가 마라톤을 완주하였습니다. 마라톤에 참여한 선수들의 이름이 담긴 배열 participant와 완주한 선수

programmers.co.kr

 

문제 설명

수많은 마라톤 선수들이 마라톤에 참여하였습니다. 단 한 명의 선수를 제외하고는 모든 선수가 마라톤을 완주하였습니다.

마라톤에 참여한 선수들의 이름이 담긴 배열 participant와 완주한 선수들의 이름이 담긴 배열 completion이 주어질 때, 완주하지 못한 선수의 이름을 return 하도록 solution 함수를 작성해주세요.

제한사항

  • 마라톤 경기에 참여한 선수의 수는 1명 이상 100,000명 이하입니다.
  • completion의 길이는 participant의 길이보다 1 작습니다.
  • 참가자의 이름은 1개 이상 20개 이하의 알파벳 소문자로 이루어져 있습니다.
  • 참가자 중에는 동명이인이 있을 수 있습니다.

입출력 예

["leo", "kiki", "eden"] ["eden", "kiki"] "leo"
["marina", "josipa", "nikola", "vinko", "filipa"] ["josipa", "filipa", "marina", "nikola"] "vinko"
["mislav", "stanko", "mislav", "ana"] ["stanko", "ana", "mislav"] "mislav"

participant completion return

입출력 예 설명

예제 #1
"leo"는 참여자 명단에는 있지만, 완주자 명단에는 없기 때문에 완주하지 못했습니다.

예제 #2
"vinko"는 참여자 명단에는 있지만, 완주자 명단에는 없기 때문에 완주하지 못했습니다.

예제 #3
"mislav"는 참여자 명단에는 두 명이 있지만, 완주자 명단에는 한 명밖에 없기 때문에 한 명은 완주하지 못했습니다.

 

풀이

완주를 하지 못한 참가자(이하 x)는 한 명이다.

동명이인이라면 participant x는 2개, completion x는 1개이므로 합은 홀수이다.

동명이인이 아니라면 오직 participant x만 1개 존재하므로 홀수이다.

 

따라서 동명이인 유무에 상관없이 participant의 x의 개수와 completion x의 개수의 합은 무조건 홀수가 된다.

 

이를 이용해서 먼저 participant와 completion을 합치고 정렬한다음 filter 메소드를 활용하였다.

 

변수를 pl로 하고 내부에서 다시 filter 메소드를 호출하여 pl인 값들만 추출한다.

그 길이를 이용하여 홀수일 때 배열의 첫째 값(어떤 위치는 값은 같다.)을 반환한다.

 

function solution(participant, completion) {
    var answer = [];
    var cat = participant.concat(completion).sort();

    answer = cat.filter(pl => {
        return cat.filter(peo => peo === pl).length % 2 !== 0;
    })[0];

    return answer;
}

 

2시간 정도 걸려 구상하였고 코드를 짜서 첫 번째 테스트를 통과했으나 두 번째 채점 결과는..

 

 

내 코드의 효율이 똥이라고한다.

처음엔 몇 번 고치다보면 해결될 줄 알고 가볍게 생각했지만 그 뒤로 12시간이 걸릴 줄은 상상도 하지 못했다..

아무리 기존 코드를 버리고 버려도 효율성 테스트는 단 한 개도 통과하지 못했다!

 

그렇게 새벽 4시정도 지났을 때.. 심신이 지치고 피로하였지만 효율성 만점 코드는 과연 어떻게 생겼을까 궁금증을 이기지 못해 잠을 잘 수 없을 것 같아 결국 버튼을 누르고 말았다..

 

지는 기분..

 

내가 본 코드는 정말 놀라웠다.

_ 와 $가 변수로 쓰이는 내가 이해하지 못하는 저 한줄로 어떻게 정확도와 효율성까지 모두 챙길 수 있는 걸까?

var solution=(_,$)=>_.find(_=>!$[_]--,$.map(_=>$[_]=($[_]|0)+1))

다행히, 이 코드의 댓글에 친절하신 분께서 설명을 해주셔서 이해할 수 있었다.

 

변수를 문제대로 바꿔보면 다음과 같다.

var solution=(participant,completion)=>participant.find(name=>!completion[name]--,completion.map(name=>completion[name]=(completion[name]|0)+1))

여기서 participant.find(~~~,~~~)으로 두 개로 나뉘는데 MDN에 따르면 find() 메소드는 다음과 같은 구문을 가진다.

 

쉼표를 기준으로 [callback, thisArg]로 나뉘어있다.

즉 thisArg는 callback에 쓰일 데이터를 만드는 역할을 하므로 해석 순서는 thisArg - callback 순서일 것이다.

 

 

하나하나 분해해서 해석해보면,

var solution=(participant,completion)=>participant.find(name=> [~~]
  1. solution을 participant와 completion을 변수로 하는 함수의 리턴 값으로 정한다.
  2. participant에 find 메소드를 활용하여 name을 변수로하는 함수의 리턴 값 중 가장 첫 번째 순서로 true를 리턴하는 값을 찾아준다.

thisArg 부분을 보면,

completion.map(name=>completion[name]=(completion[name]|0)+1))

completion에 map 메소드를 사용하고 있다.

 

completion[name] = y라면,

y가 0이 아닌 y이거나(completion[name]), 0이라면 1을 더해준다는 뜻이다.

 

여기서 completion[name] = value이다.

배열도 JS에서는 오브젝트이므로(JS에서는 거의 모든 게 오브젝트), key(name)-value 형태의 호출이 가능하다.

 

이처럼 배열도 key-value의 특징이 있다는 것을 사용하면 sort 하거나 concat과 같은 추가 작업 없이 해당 요소의 개수를 셀 수 있다.

participant.find(name=>!completion[name]--, [~~]

이곳에서는 thisArg에서 작업된 key-value를 callback에서 활용한다.

  1. !completion[name] : name의 value가 false인 조건을 뜻한다. (여기서 false = 0)
    이 조건에 부합하는 첫 번째 name을 반환한다.
  2. 위의 입출력 예시를 표로 나타내면
    name stanko ana mislav
    completion[name] 1 1 1
    stanko, ana, mislav 모두 true 값을 가지므로(0 이상의 수),
    뒤에 후치된 '--' 연산에 의해 차감된다.
    따라서 participant의 name 중 completion에서 false로 반환되는 첫 번째 name이 정답이 된다.

    이 과정을 !completion[name]--으로 표현한 것이다.

 

다른 정답 풀이도 있었지만 이보다 더 간결한 코드는 없었다.

 

이 코드를 이해하면서 내가 몰랐거나 간과했던 것은

  1. 배열도 오브젝트로서 key-value 형태를 가질 수 있다.
  2. !completion[name]--과 같이 연산자를 좌에서 우 순서로 배치하여 라인을 줄일 수 있다.
  3. `completion[name] = (completion[name] | 0) + 1`처럼 (a|b)의 존재?를 몰랐던 것.

 

이 분의 실력이 부럽고 앞으로 갈 길이 멀다는 것을 느낀 시간이었다 ㅠㅠ

 
반응형