문제 이해
이 문제에서는 조건문 사용이 관건인 것 같다. 또한 네 가지 방향을 어떻게 단순하게 정리할 수 있는지도 학습 목표 중에 하나인 것 같다.
현재 살펴보고 있는 위치를 저장하는 변수를 `curPos = [r, c]`, 행과 열의 개수를 나타내는 변수를 각각 `rows`와 `cols`라고 하자.
과정1 - 진행할 것인지 결정하는 조건문
해당 진행 방향을 선택할지 말지에 관한 조건에 부합하는지 확인하는 방법은 여러 가지가 있을 것이다. 그 중 가장 기초적인 방법은 index가 공원을 벗어나는지 판단하는 것이다.
if (curPos[0] < 0 || curPos[1] >= rows || park[curPos[0]][curPos[1]] === 'X') {...}
여기서 중요한 것은 마지막 조건을 앞에 두게 된 경우, `undefined`가 행 또는 열에서 발생하면 런타임 에러가 발생한다는 것이다. 조건문에서 `||`은 앞에서부터 확인할 때 true가 있으면 뒷부분은 살펴보지 않는다(하나만 참이어도 참이되는 '또는'을 생각하면 당연한 절차다).
위 조건문을 좀더 간략하게 작성하려면 optional chaining operator를 사용하면 된다. Optional operator는 객체 속성이 존재하지 않아도 에러를 발생시키지 않고, 그 대신 `undefined`를 반환하는 연산자이다. 만약 문제의 공원의 크기가 3*4인데, `park[4][1]`을 살펴보려고 하면 `park[4]`가 존재하지 않아 런타임 에러가 발생할 것이다. 이 경우 `park[4]?.[1]`이라고 하면 `undefined`가 대신 출력된다. 따라서 위 조건문을 아래와 같이 수정할 수 있다.
if (!park[curPos[0]]?.[curPos[1]] || park[curPos[0]][curPos[1]] === 'X') {...}
여기서 첫번째 조건을 조금더 살펴보자. 만약 `park[2][4]`를 살펴보려고 하면, `park[2]`는 존재하지만 `park[2][4]`는 존재하지 않아 `undefined`가 반환되는데, 이는 falsy한 값이므로 `!`를 통해 `true`로 바꿔준 것이다.
과정2 - 진행 방향에 따른 위치 이동 계산하기
먼저 내가 사용한 방법에 대해 설명하고, 다른 방법에 대해 설명하려고 한다.
나는 북쪽이나 서쪽 진행은 idx가 감소하고, 남쪽이나 동쪽 진행은 idx가 증가하는 것에 초점을 두었다. 따라서 증감에 따라 `sign`이라는 변수를 주어 추후 for문을 사용할 때 idx의 증감식에 사용하고자 했다. 그리고 수평 이동(동서)인지 수직 이동(남북)인지에 따라 경우를 나누어 주었다.
따라서 내가 작성한 최종 코드는 다음과 같다. (위에서 사용한 `curPos`와는 조금 다르다. 이동한다고 결정된 경우 이동 후 위치를 저장한 변수이다.)
function solution(park, routes) {
const rows = park.length;
const cols = park[0].length;
let curPos = [0, 0];
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (park[r][c] === "S") {
curPos = [r, c];
break;
}
}
}
for (let route of routes) {
const [dir, steps] = route.split(" ").map((el) => (isNaN(+el) ? el : +el));
const sign = ["N", "W"].includes(dir) ? -1 : 1;
const [r, c] = curPos;
if (["S", "N"].includes(dir)) {
// 수직 이동
let forward = true;
for (let i = sign; Math.abs(i) <= steps; i += sign) {
if (!park[r + i]?.[c] || park[r + i][c] === "X") {
forward = false;
break;
}
}
if (forward) {
curPos = [r + sign * steps, c];
}
} else {
// 수평 이동
let forward = true;
for (let i = sign; Math.abs(i) <= steps; i += sign) {
if ((park[r]?.[c + i] ?? "X") === "X") {
forward = false;
break;
}
}
if (forward) {
curPos = [r, c + sign * steps];
}
}
}
return curPos;
}
const park = ["OOO", "OSO", "OOO", "OOO"];
const routes = ["N 2", "S 2"];
console.log(solution(park, routes));
이제 다른 사람 풀이를 살펴보자.
나도 생각은 했었으나 사용하지 않았는데, 더 코드가 깔끔하게 작성되는 것 같아 소개한다.
위와 달리 방향에 따른 이동을 다음과 같은 dict로 저장했다.
const dirs = { E: [0, 1], W: [0, -1], S: [1, 0], N: [-1, 0] };
따라서 이를 적용해 코드를 작성하면 다음과 같다.
function solution(park, routes) {
const dirs = { E: [0, 1], W: [0, -1], S: [1, 0], N: [-1, 0] };
const rows = park.length;
const cols = park[0].length;
let curPos = [0, 0];
for (let r = 0; r < rows; r++) {
for (let c = 0; c < cols; c++) {
if (park[r][c] === "S") {
curPos = [r, c];
break;
}
}
}
routes.forEach((route) => {
const [dir, steps] = route.split(" ").map((el) => (isNaN(+el) ? el : +el));
let cnt = 1;
while (cnt <= steps) {
[nx, ny] = [
curPos[0] + cnt * dirs[dir][0],
curPos[1] + cnt * dirs[dir][1],
];
if (!park[nx]?.[ny] || park[nx][ny] === "X") {
break;
}
cnt++;
}
if (cnt == steps + 1) [x, y] = [nx, ny];
});
return [x, y];
}
단축 평가 (Short Circuit Evaluation)
위에서 사용한 optional operation `?.` 그리고 내 코드 두 번째 조건문에서 사용한 `??`와 같은 연산들을 단축 평가라고 한다. 사용한 김에 정리하고자 한다.
Optional Chaining `?.`
먼저 위에서 본 `?.`부터 살펴보자. 위에서 말했듯이 객체의 속성에 접근할 때 해당 경로에 속성이 존재하지 않아도(`undefined` 또는 `null`인 경우) 에러를 발생시키지 않고 `undefined`를 반환한다. 에러가 발생하지 않기 때문에 중첩된 속성에 안전하게 접근할 수 있다.
const user = {
profile1: {
name: "김철수",
details: {
age: 20,
location: "서울시 강남구"
}
}
greet: () => console.log('안녕하세요'),
};
console.log(user.profile?.details.age); // undefined
console.log(user.greets?.()); // undefined
Null 병합 연산자 `??`
`??` 연산자는 좌변이 `null`이나 `undefined`인 경우에만 우변을 평가한다. `null` 또는 `undefined`가 아닌 falsy한 값들은 유효한 값으로 처리하고 싶을 때 사용된다.
function displayPreferences(preferences) {
// `||` 연산자 사용 예
const textLength = preferences.textLength || 50; // textLength가 0일 경우 50이 할당됨
console.log(`Text Length: ${textLength}`);
// `??` 연산자 사용 예
const itemsPerPage = preferences.itemsPerPage ?? 10; // itemsPerPage가 null 또는 undefined일 때만 10이 할당됨
console.log(`Items Per Page: ${itemsPerPage}`);
}
// 테스트 케이스
const userPreferences = {
textLength: 0, // 0이 유효한 값이지만 || 연산자로 인해 50이 출력됨
itemsPerPage: null // null 값에 대해 ?? 연산자로 인해 10이 출력됨
};
displayPreferences(userPreferences);
논리합 연산자 `||`
위 예시 코드에서 쓰인 `||`에 대해 살펴보자. 논리합 연산자 `||`는 좌변이 falsy한 값(`false`, `0`, `""`,`null`, `undefined`, `NaN`)일 경우 우변을 평가한다. 좌변이 truthy한 값인 경우 그 값이 바로 결과값으로 반환되며 우변은 평가하지 않는다.
// user 정보가 제공되지 않았을 때 기본 이름 제공
function getUsername(user) {
return user.name || '신원미상';
}
console.log(getUsername({ name: '김철수' })); //김철수
console.log(getUsername({})); //신원미상
또한 변수가 초기화되지 않았을 때 초기화하는 용도로도 사용할 수 있다.
let x;
let y = x || 10; // x가 할당되지 않은 경우, y의 초기값을 10으로 지정함
console.log(y); // 10
논리곱 연산자 `&&`
`&&`는 좌변이 truthy한 값일 대만 우변을 평가한다. 조건에 따라 특정 코드를 실행하고 싶을 때 사용할 수 있다. 특정 조건을 만족할 때 화면에 특정 컴포넌트를 보일지 말지 결정할 때 사용되기도 한다.
// 사용자가 로그인 상태이면 환영 메시지를 출력
let loggedIn = true;
let username = '김철수';
loggedIn && console.log('환영합니다! ' + username); //환영합니다! 김철수
loggedIn = false;
loggedIn && console.log('환영합니다! ' + username); //아무것도 출력되지 않음
'코딩테스트 > 프로그래머스' 카테고리의 다른 글
[JS] 괄호 회전하기 (0) | 2024.09.06 |
---|---|
[JS] JadenCase 문자열 만들기 (+ string[i] vs string.charAt(i)) (0) | 2024.08.27 |
[JS] 달리기 경주 (0) | 2024.08.19 |
[JS] 햄버거 만들기 (0) | 2024.08.14 |
[JS] 옹알이 (2) (0) | 2024.08.09 |