카카오 코딩테스트 풀이 2 - 방금 그 곡 [2018 KAKAO BLIND] [Python]

딩코딩코·2021년 7월 12일
10
post-thumbnail

안녕하세요! 개발자 여러분들.
이번에는 카카오 코딩테스트 풀이 자료를 공유해드리고자 합니다.
누군가에게 도움이 되셨으면 좋겠습니다.

Notion 문서를 velog 에 옮긴 것이라서 보기 불편할 수 있습니다.
더 쉽게 보고 싶으시다면 Notion 문서에서 보셔도 괜찮습니다

📹 0. 해설 영상

📝 1. 오늘의 문제

(질문 링크)

  • 문제 풀이 자료

질문 읽기

  • Q. 네오는 자신이 기억한 멜로디를 가지고 방금그곡을 이용해 음악을 찾는다. 그런데 라디오 방송에서는 한 음악을 반복해서 재생할 때도 있어서 네오가 기억하고 있는 멜로디는 음악 끝부분과 처음 부분이 이어서 재생된 멜로디일 수도 있다. 반대로, 한 음악을 중간에 끊을 경우 원본 음악에는 네오가 기억한 멜로디가 들어있다 해도 그 곡이 네오가 들은 곡이 아닐 수도 있다. 그렇기 때문에 네오는 기억한 멜로디를 재생 시간과 제공된 악보를 직접 보면서 비교하려고 한다. 다음과 같은 가정을 할 때 네오가 찾으려는 음악의 제목을 구하여라.

    m = "ABCDEFG"
    musicinfos = ["12:00,12:14,HELLO,CDEFGAB", "13:00,13:05,WORLD,ABCDEF"]
    # "HELLO" 가 출력되어야 합니다.

고민해보기: 실전 문제처럼 20분 이상을 고민해 본 다음, 아래 방법들을 펼쳐 봅시다!

  • 아래 코드를 복사 붙여넣기 하고 함수를 작성해보세요!

    • [코드스니펫] 방금 그곡

      def solution(m, musicinfos):
          answer = ''
          return answer

✍️ 2. 풀이 방법

2-1. 목표 설정

이 문제에서 구하는 것은 자신이 들은 멜로디가 포함되어 있는 음악 중 재생된 시간이 제일 긴 음악 제목입니다.

그러면 자신이 들은 멜로디가 각 음악에 포함되어 있는지 아닌지를 파악합니다.
포함되어 있는 음악들 중에서 재생된 시간이 가장 긴 음악의 제목을 반환해주면 됩니다.

2-2. 주의해야 하는 점 - 입력값으로부터 힌트 얻기

그런데 여기서 주의해야 할 점들이 몇가지 있습니다.
주의해야 할 점을 입력값 예시에서 힌트를 주고 있습니다!

세 번째 예시에서 HELLO는 C#DEFGABC#DEFGAB로, WORLD는 ABCDE로 재생되었다. HELLO 안에 있는 ABC#은 기억한 멜로디인 ABC와 일치하지 않고, WORLD 안에 있는 ABC가 기억한 멜로디와 일치한다.

→ ABC# 은 ABC 를 포함하지 않는다. 즉, C# 과 C는 아예 다른 음이라는 것입니다!
따라서 C# 과 C를 구분하기 위한 방법을 떠올려야 합니다.

2-3. 풀이 방법 떠올려보기 1

첫 번째 방법은 문자열을 기준으로 묶어버리는 것입니다.

C#DEFGABC#DEFGAB 라는 문자열을
["C#", "D", E", "F", "G", "A", "B", "C#", "D", E", "F", "G", "A", "B"] 라고 하는 것입니다.
그러면 이 안에 ABC 가 있는지 확인하기 위해서 배열을 순회하면서 "A", "B", "C" 원소가 순서대로 있는지 파악하면 됩니다.

2-4. 풀이 방법 떠올려보기 2

두 번째 방법은 #이 붙은 문자들을 치환해버리는 겁니다.

C# → c
D# → d 처럼 말입니다.
C#DEFGABC#DEFGAB 라는 문자열이 cDEFGABcDEFGAB 라고 표시가 됩니다.
그러면 이 안에 ABC 가 있는지 확인하기 위해 문자열을 돌면서 "ABC" 라는 부분 문자열이 있는지 파악하면 됩니다.

두 가지 경우 다 비슷한 시간 복잡도와 구현 난이도가 있지만, 보다 편리해보이는 두 번째 방법으로 진행하도록 하겠습니다! python 에서는 문자열 찾기 관련된 함수가 많기 때문에 쉬울 것 같습니다.

3-1. 코드 구현하기 - 치환 및 입력값 정제하기

이제 코드를 구현해 보도록 하겠습니다!

우선 #이 붙은 문자들을 치환해버리기로 했으니, replace_step 이라는 함수를 만들어서 m의 #붙은 문자열들을 치환해주도록 하겠습니다.

    def replace_step(m):
        return m.replace('C#', 'c').replace('D#', 'd').replace('F#', 'f').replace('G#', 'g').replace('A#', 'a')

    def solution(m, musicinfos):
        answer = ''
        m = replace_step(m)

        return answer

그리고 이제 musicinfos 를 순회하면서, music info 에서 원하는 정보를 뽑아봅시다.
우선 시작 시간, 끝나는 시간, 이름, 멜로디가 "," 라는 문자를 기준으로 이루어져있습니다.
이를 분리해서 변수에 담아보도록 하겠습니다.

음악의 시작 시각과 끝난 시각을 이용하는 용도는 단순히 이 음악이 몇초나 재생되었는지 밖에 없습니다. 따라서 end_time 에서 start_time 을 빼서 얼마나 재생되었는지, play_time 을 계산해보도록 하겠습니다!

    def replace_step(m):
        return m.replace('C#', 'c').replace('D#', 'd').replace('F#', 'f').replace('G#', 'g').replace('A#', 'a')

    def solution(m, musicinfos):
        answer = ''
        m = replace_step(m)

        **for musicinfo in musicinfos:
            start_time, end_time, name, melody = musicinfo.split(",")
            play_time = (int(end_time[:2]) * 60 + int(end_time[3:])) - (int(start_time[:2]) * 60 + int(start_time[3:]))**
            
        return answer

3-2. 코드 구현하기 - 멜로디가 어떻게 실행되었을까?

여기서, melody 에는 아직 C# 등의 문자가 남아있을테니 마찬가지로 치환을 해주도록 하겠습니다!
저희는 이제, melody 도 알게 되었고, 그 멜로디가 몇초나 진행되었는지도 알고 있습니다.

예를 들어서 ABC 라는 멜로디가 15초 진행되었다고 해볼게요.
그러면 ABCABCABCABCABC 이렇게 계속 반복이 되었을 것입니다.

3이라는 멜로디의 길이를 15초 동안 실행해주기 위해서 5번이 반복된 겁니다.

만약에 그러면 실행기간이 16초 였다면 어떻게 실행되었을까요?
ABCABCABCABCABCA 까지 실행되었을 겁니다.

즉, 멜로디의 모습과 실행시간을 이용해서 실제 실행된 멜로디를 알아와야 합니다.
이를 구하기 위해서 저는 올림(실행시간 / 멜로디의 길이)를 통해서 멜로디를 반복할 숫자를 구했습니다.
그러면 ABCABCABCABCABCABC 라는 문자열이 됩니다.

그리고 멜로디의 길이 까지 잘라줍니다. 16까지 길이를 잘라주면
ABCABCABCABCABCA 가 되기 때문에, 실제 실행된 멜로디를 구할 수 있습니다!

    import math

    def replace_step(m):
        return m.replace('C#', 'c').replace('D#', 'd').replace('F#', 'f').replace('G#', 'g').replace('A#', 'a')

    def solution(m, musicinfos):
        answer = ''
        m = replace_step(m)

        for musicinfo in musicinfos:
            start_time, end_time, name, melody = musicinfo.split(",")
            play_time = (int(end_time[:2]) * 60 + int(end_time[3:])) - (int(start_time[:2]) * 60 + int(start_time[3:]))

            melody = replace_step(melody)
            melody_repeated_count = math.ceil(play_time / len(melody))
            melody_played = (melody * melody_repeated_count)[:play_time]
        return answer

3-3. 코드 구현하기 - 그래서 얻고 싶은 게 뭐라고?

자 그러면 melody_played 는 ABCABCABCABCABCABC 가 만들어졌습니다.

이 안에 m 이라는 문자열이 존재하는지 확인하면 됩니다.
in 이라는 연산자를 쓰면 몹시 간단하게 구현할 수 있습니다.

자 그런데, 저희가 원하는 것은 자신이 들은 멜로디가 포함되어 있는 음악 중 재생된 시간이 제일 긴 음악 제목 입니다.
즉, 가장 재생된 시간이 긴 제목을 저장하고 있어야 합니다.

이를 구분하기 위해서 max_play_time 이라는 변수를 만들고, answer 의 기본값을 None 으로 만들겠습니다.
만약 play_time 이 max_play_time 보다 크다면 answer 와 max_play_time 을 갱신하도록 합니다.

    import math

    def replace_step(m):
        return m.replace('C#', 'c').replace('D#', 'd').replace('F#', 'f').replace('G#', 'g').replace('A#', 'a')

    def solution(m, musicinfos):
        answer = None
        max_play_time = 0
        m = replace_step(m)

        for musicinfo in musicinfos:
            start_time, end_time, name, melody = musicinfo.split(",")
            play_time = (int(end_time[:2]) * 60 + int(end_time[3:])) - (int(start_time[:2]) * 60 + int(start_time[3:]))

            melody = replace_step(melody)
            melody_repeated_count = math.ceil(play_time / len(melody))
            melody_played = (melody * melody_repeated_count)[:play_time]
            if m in melody_played and play_time > max_play_time:
                answer = name
                max_play_time = play_time**

        return answer

그리고 조건이 일치하는 음악이 없을 때에는 “(None)”을 반환한다.
라는 요구사항에 맞춰서 간단한 if 문도 추가하면 끝입니다!

    import math

    def replace_step(m):
        return m.replace('C#', 'c').replace('D#', 'd').replace('F#', 'f').replace('G#', 'g').replace('A#', 'a')

    def solution(m, musicinfos):
        answer = None
        max_play_time = 0
        m = replace_step(m)

        for musicinfo in musicinfos:
            start_time, end_time, name, melody = musicinfo.split(",")
            play_time = (int(end_time[:2]) * 60 + int(end_time[3:])) - (int(start_time[:2]) * 60 + int(start_time[3:]))

            melody = replace_step(melody)
            melody_repeated_count = math.ceil(play_time / len(melody))
            melody_played = (melody * melody_repeated_count)[:play_time]
            if m in melody_played and play_time > max_play_time:
                answer = name
                max_play_time = play_time
        **if not answer:
            return "(None)"**
        return answer

3-4. 문제의 교훈

문제를 푸시느라 수고하셨습니다!
다양한 문제 풀이 방법이 있겠지만, 조금 더 효율적이고 연산량이 적은 방법이 어떤 것일지 고민해보시는 것이 좋습니다.
또한 코드를 어떻게 간결하게 쓸 수 있을지 고민해보시다 보면 좋은 코드를 작성하실 수 있게 될겁니다. 수고하셨습니다!

🤗 4. 끝내며

혹시 더 나은 풀이나 잘못된 부분이 있다면 댓글로 남겨주세요

오늘도 좋은 하루 보내시길 바랍니다

profile
안녕하세요 코딩을 뒤집다. 딩코딩코입니다

1개의 댓글

comment-user-thumbnail
2021년 9월 15일

코테의 비중은 얼마나 둬야될가요? 알고리즘 공부를 하다가도 항상 현타가옵니다.

답글 달기