(Swift) Programmers 방금 그곡

SteadySlower·2023년 3월 14일
0

Coding Test

목록 보기
227/305

코딩테스트 연습 - [3차] 방금그곡

문제 풀이 아이디어

복잡한 알고리즘이 필요한 문제는 아닙니다만 주어진 String을 파싱하여 필요한 정보를 추출하는 과정이 필요합니다.

#을 어떻게 처리할 것인가?

일단 #을 어떻게 처리할 지 생각을 해봅시다. 일반적인 음계는 알파벳 1자로 이루어져 있지만 #이 붙은 음계의 경우 알파벳 1자와 #, 총 2글자로 이루어져있습니다. 따라서 그냥 1글자씩만 끊어서 파싱하는 경우 알파벳과 #이 떨어지게 됩니다.

저는 이 문제를 풀 때 사용한 방법이 떠올랐는데요. 바로 replacingOccurrences를 사용해서 “C#”을 다른 하나의 문자로 바꿔주는 것입니다. 예를 들어 “C#”을 “H”라는 길이가 1인 다른 문자로 대체하는 것이죠.

String을 Music으로

저는 이 문제를 풀기 위해서 구조체를 활용하기로 했습니다. 구조체를 사용하면 배열이나 튜플을 사용하는 경우보다 가독성도 좋고 computed property 같은 부가적인 기능도 사용할 수 있습니다.

"12:00,12:14,HELLO,CDEFGAB”로 주어지는 문자열을 일단 “,”를 기준으로 나눕니다. 그리고 각각 시작시간, 종료시간, 제목, 음계로 구분해서 저장합니다. 음계의 경우 다시 하나의 String으로 만들기 쉽게 [String] 형태로 저장하겠습니다.

runtime 구하기

재생시간을 구하기 위해서는 "12:00”와 “12:14”의 형태로 되어있는 시작, 종료 시간을 Int의 형태로 바꾸어 주어야 하는데요. “:”를 기준으로 두 부분으로 나눈 다음 각각 Int로 파싱하고 시간 * 60 + 분 해주면 됩니다.

재생된 note 구하기

저장된 음계(notes)를 runtime의 갯수만큼 반복해서 하나의 문자열로 만듭니다.

재생이 된 음악과 matching 되는지 구하기

String에 있는 .contain() 메소드를 사용하면 특정 문자열이 해당 문자열에 포함되어 있는지 알 수 있습니다.

코드

import Foundation

// #이 붙은 음들을 일반 알파벳으로 바꾸어 준다.
func removeSharp(_ notes: String) -> String {
    var notes = notes
    
    let dict = [
        "C#": "H",
        "D#": "I",
        "F#": "J",
        "G#": "K",
        "A#": "L",
    ]
    
    for key in dict.keys {
        notes = notes.replacingOccurrences(of: key, with: dict[key]!)
    }
    
    return notes
}

// 음악 구조체
struct Music {
    private let start: String
    private let end: String
    let name: String
    private let notes: [String]
    
    // initializer
    init(_ s: String) {
        // ","를 기준ㅇ로 나눈다.
        let a = s.split(separator: ",").map { String($0) }
        self.start = a[0]
        self.end = a[1]
        self.name = a[2]
        // note는 #을 제거한다.
        self.notes = removeSharp(a[3]).map { String($0) }
    }
    
    // 음악 재생시간이 길이
    var runtime: Int {
        // ":"를 기준으로 시간-분을 나누고 분단위로 계산
        let startMS = start.split(separator: ":").map { Int($0)! }
        let endMS = end.split(separator: ":").map { Int($0)! }
        let s = startMS[0] * 60 + startMS[1]
        let e = endMS[0] * 60 + endMS[1]
        return e - s
    }
    
    // 재생된 note
    var playedNotes: String {
        let length = notes.count
        var played = [String]()
        for i in 0..<runtime {
            let now = i % length //👉 i가 note의 길이를 넘어가면 다시 0으로
            played.append(notes[now])
        }
        // string으로 합쳐서 리턴
        return played.reduce("", +)
    }
}

func solution(_ m:String, _ musicinfos:[String]) -> String {
    
    // musicinfos를 [Music]으로 파싱
    let musicArray = musicinfos.map { Music($0) }
    // 주어진 m에 #제거
    let m = removeSharp(m)
    
    var result = [Music]()
    
    // music의 playedNotes가 m을 포함하면 result에 포함
    for music in musicArray {
        if music.playedNotes.contains(m) {
            result.append(music)
        }
    }
    
    // result가 비었다면 "(None)"을 리턴
    // 아니라면 result를 runtime(재생시간) 순으로 정렬하고 첫 Music의 name을 리턴
    return result.isEmpty ? "(None)" : result.sorted(by: { $0.runtime > $1.runtime })[0].name
}

여담: struct가 상수로 선언되었을 때 lazy var

제가 맨 처음에 구조체를 만들 때는 runtime과 playednotes를 아래처럼 lazy var로 만들었는데요. 왜냐하면 runtime과 playedNotes는 한번 정해지면 변경되지 않는 값이기 때문에 computed property로 만들어서 굳이 구할 때마다 다시 계산할 필요가 없기 때문이죠. (Swift는 String을 다룰 때 비용이 큽니다.)

// 음악 재생시간이 길이
lazy var runtime: Int = {
    // ":"를 기준으로 시간-분을 나누고 분단위로 계산
    let startMS = start.split(separator: ":").map { Int($0)! }
    let endMS = end.split(separator: ":").map { Int($0)! }
    let s = startMS[0] * 60 + startMS[1]
    let e = endMS[0] * 60 + endMS[1]
    return e - s
}()

// 재생된 note
lazy var playedNotes: String = {
    let length = notes.count
    var played = [String]()
    for i in 0..<runtime {
        let now = i % length //👉 i가 note의 길이를 넘어가면 다시 0으로
        played.append(notes[now])
    }
    // string으로 합쳐서 리턴
    return played.reduce("", +)
}()

아래의 반복문에서 에러가 발생했습니다.

이유인 즉 music이라는 변수가 상수라서 변경할 수 없다는 것인데요. 반복문 안에서 선언된 변수는 상수이기 때문에 발생한 에러입니다.

구조체는 value 타입이기 때문에 reference 타입인 class와 다르게 상수로 선언되는 경우 내부 property를 변경하는 것조차 불가능합니다. 따라서 위 같은 에러가 발생하는 것이죠. 이런 형태로 사용하고 싶다면 반복문 내부에서 var로 다시 선언해야 합니다.

어차피 runtime과 playedNotes는 각각 2번, 1번만 계산이 됩니다. 시간복잡도가 그렇게 빡빡한 문제도 아니므로 computed property로 구현을 했습니다.

profile
백과사전 보다 항해일지(혹은 표류일지)를 지향합니다.

0개의 댓글