🔴 문제
문제 설명
지나다니는 길을 'O', 장애물을 'X'로 나타낸 직사각형 격자 모양의 공원에서 로봇 강아지가 산책을 하려합니다. 산책은 로봇 강아지에 미리 입력된 명령에 따라 진행하며, 명령은 다음과 같은 형식으로 주어집니다.["방향 거리", "방향 거리" … ]예를 들어 "E 5"는 로봇 강아지가 현재 위치에서 동쪽으로 5칸 이동했다는 의미입니다. 로봇 강아지는 명령을 수행하기 전에 다음 두 가지를 먼저 확인합니다.주어진 방향으로 이동할 때 공원을 벗어나는지 확인합니다.주어진 방향으로 이동 중 장애물을 만나는지 확인합니다.
위 두 가지중 어느 하나라도 해당된다면, 로봇 강아지는 해당 명령을 무시하고 다음 명령을 수행합니다.
공원의 가로 길이가 W, 세로 길이가 H라고 할 때, 공원의 좌측 상단의 좌표는 (0, 0), 우측 하단의 좌표는 (H - 1, W - 1) 입니다.
공원을 나타내는 문자열 배열 park, 로봇 강아지가 수행할 명령이 담긴 문자열 배열 routes가 매개변수로 주어질 때, 로봇 강아지가 모든 명령을 수행 후 놓인 위치를 [세로 방향 좌표, 가로 방향 좌표] 순으로 배열에 담아 return 하도록 solution 함수를 완성해주세요.
제한사항
- 3 ≤ park의 길이 ≤ 50
- 3 ≤ park[i]의 길이 ≤ 50
- park는 직사각형 모양입니다.
- park[i]는 다음 문자들로 이루어져 있으며 시작지점은 하나만 주어집니다.
- S : 시작 지점
- O : 이동 가능한 통로
- X : 장애물
- 1 ≤ routes의 길이 ≤ 50
- routes의 각 원소는 로봇 강아지가 수행할 명령어를 나타냅니다.
- 로봇 강아지는 routes의 첫 번째 원소부터 순서대로 명령을 수행합니다.
- routes의 원소는 "op n"과 같은 구조로 이루어져 있으며, op는 이동할 방향, n은 이동할 칸의 수를 의미합니다.
- op는 다음 네 가지중 하나로 이루어져 있습니다.
- N : 북쪽으로 주어진 칸만큼 이동합니다.
- S : 남쪽으로 주어진 칸만큼 이동합니다.
- W : 서쪽으로 주어진 칸만큼 이동합니다.
- E : 동쪽으로 주어진 칸만큼 이동합니다.
- 1 ≤ n ≤ 9
입출력 예
🔵 풀이 - switch 사용
import Foundation
func solution(_ park:[String], _ routes:[String]) -> [Int] {
let park = park.map {Array($0)}
let routes = routes.map { $0.split(separator: " ")}
// 시작 좌표 찾기
var location: [Int] = []
for (yIndex, yValue) in park.enumerated() {
for (xIndex, xValue) in yValue.enumerated() {
if xValue == "S" {
location = [yIndex, xIndex]
}
}
}
// 산책하기
for i in routes {
//print("\(i) -> \(location)")
switch i[0] {
case "N":
guard location[0] >= Int(i[1])! else {break}
var temp = location
for step in 1...(Int(i[1]) ?? 1) {
temp[0] -= 1
guard park[temp[0]][temp[1]] == "O" else { temp = location; break }
}
location = temp
case "S":
guard location[0] < park.count - (Int(i[1])!) else {break}
var temp = location
for step in 1...(Int(i[1]) ?? 1) {
temp[0] += 1
guard park[temp[0]][temp[1]] == "O" else { temp = location; break }
}
location = temp
case "W":
guard location[1] >= Int(i[1])! else {continue}
var temp = location
for step in 1...(Int(i[1]) ?? 1) {
temp[1] -= 1
guard park[temp[0]][temp[1]] == "O" else { temp = location; break }
}
location = temp
case "E":
guard location[1] < park[0].count - (Int(i[1])!) else {continue}
var temp = location
for step in 1...(Int(i[1]) ?? 1) {
temp[1] += 1
guard park[temp[0]][temp[1]] == "O" else { temp = location; break }
}
location = temp
default:
continue
}
}
return location
}
print(solution(["SOO","OOO","OOO"],["E 2","S 2","W 1"])) // [2,1]
print(solution(["OSO", "OOO", "OXO", "OOO"], ["E 2", "S 3", "W 1"])) // [0,0]
🔵 풀이 2 - dictionary, tuple 사용하여 반복되는 코드 삭제하기
import Foundation
func solution2(_ park:[String], _ routes:[String]) -> [Int] {
let park = park.map {Array($0)}
let routes = routes.map { $0.split(separator: " ")}
// 시작 좌표 찾기
var location: [Int] = []
for (yIndex, row) in park.enumerated() {
if let xIndex = row.firstIndex(of: "S") {
location = [yIndex, xIndex]
break
}
}
// 동서남북 델타값 사전
let deltas: [Character: (Int, Int)] = ["N": (-1, 0), "S": (1, 0), "W": (0, -1), "E": (0, 1)]
// 산책하기
for i in routes { // ex) i = ["S", "2"]
//let direction = i.first! // [Substring]에 .first 붙이면 substring? 반환.
let direction = i[0].first!
let (deltaY, deltaX) = deltas[direction]! // Substring에 .first 붙이면 Character? 반환.
let distance = Int(i[1])!
var temp = location
temp[0] += deltaY * distance
temp[1] += deltaX * distance
guard temp[0] >= 0 && temp[0] < park.count && temp[1] >= 0 && temp[1] < park[0].count else {
continue
}
temp = location
for step in 1...(Int(i[1])!) {
temp[0] += deltaY
temp[1] += deltaX
if park[temp[0]][temp[1]] == "X" {
temp = location
break
}
}
location = temp
}
return location
}
print(solution2(["SOO","OOO","OOO"],["E 2","S 2","W 1"])) // [2,1]
print(solution2(["OSO", "OOO", "OXO", "OOO"], ["E 2", "S 3", "W 1"])) // [0,0]
🥕 알게 된 점
1. split(separator:)의 리턴타입은 SubSequence
참고: apple 공식문서(링크), Dominic's blog(링크)
문자열을 자르는 split(separator:) 또는 prefix(_:) 와 같은 메서드는 'Self.SubSequence' 타입을 반환한다.
(* SubSequence 는 Substring, ArraySlice<Element> 등의 별명이다.)
2. Substring(String.SubSequence)의 특징
- Substring 타입은 String 타입에서 사용하는 대부분의 메서드를 가지고 있다.
- Substring은 메모리에 원본 String을 계속 가지고 있다.
위 사진에서 b는 a "안녕하세요" 문자열의 "안녕"을 참조하지만, 사실 메모리에는 "안녕하세요" 전체를 가지고 있다.
따라서 Substring을 계속 사용한다면 쓸 데 없는 메모리를 차지하게 된다.
따라서 별개의 String 변수/상수에 저장하여 사용하는 것이 좋다.
3. '.first' 메소드는 String.SubSequence에 사용될 수 있으며 'Character?' 타입을 반환한다.
'.first' 메소드는 String이나 Array와 같은 Collection 타입에 사용되며,
콜렉션의 첫 번째 요소를 옵셔널 타입으로 반환한다.
String.SubSequence 역시 String과 유사한 성질을 가지므로 .first 메소드를 가지고 있다.
코드에서 .frist 메소드를 사용한 이유는 [Character:(Int,Int)] 형태인 deltas dictionary에 접근하기 위함이다.
first 메소드를 사용할 때와 사용하지 않을 때 direction의 데이터 타입은 다음과 같이 달라진다.
let direction = i // [String.SubSequence]
let direction = i.first! // String.SubSequence 타입
let direction = i[0] // String.SubSequence (= Substring) 타입
let direction = i[0].first! // Substring.Element (= Character)타입
deltas dictionary에서 value를 가져오기 위해서는 key로 Character타입을 입력해야 하므로
first 메소드를 사용하여 Character 타입을 구한 것이다.