지뢰찾기 유사품!
나의 아이디어는 각 칸을 기준으로 주변에 1(지뢰)인지 여부를 판별하는 것이였다. 1이면 해당 지역은 위험지역 그렇지 않으면 안전지대! 그래서 각 점을 중심으로 아래와 같이 체크를 하였다.
board[i-1][j-1] === 1 || board[i-1][j] === 1 || board[i-1][j+1] ||
board[i][j-1] === 1 || board[i][j] === 1 || board[i][j+1] ||
board[i+1][j-1] === 1 || board[i+1][j] === 1 || board[i+1][j+1]
i, j로 되어있는 부분이 현재 순회 중인 좌표이다. 그런데 여기서 문제가 생긴다. 바로 맨 끝인 경우엔 undefined 오류가 생긴다는 것이다. 예를 들어 i=0, j=0
인 경우엔 board[-1][0]
의 좌표는 없기 때문에 TypeError: Cannot read properties of undefined (reading '-1')
라는 에러가 노출된다. 좀 더 정확히 살펴보자.
board[-1]의 값은 undefined이다. 그런데 여기에 참조를 해서 [0]의 값으로 접근하려고하니 오류가 나타나는 것이다.
그래서 i, j가 양쪽 끝인 경우, i-1
과 j-1
그리고 i+1
과 j+1
에 삼항연산자를 이용해서 이를 회피하는 코드를 작성하였다. (아래쪽에 이보다 더 좋은 방법이 있었으니...🤩)
그리고 또 한가지 실수를 하였다... 사실 이 문제 때문에 엄청 오래 시간을 잡아먹었다. 아래 코드를 보면, i-1이 음수인 경우에만 0이고 나머지는 원래 값을 리턴해야하는데, i값으로 리턴하게 작성하여 체크하는 범위가 줄어들게 되어 위험지역이 실제보다 적게 카운팅되는 오류가 있었다. 아마 무의식의 흐름에서 그냥 적은것 같다...😒
const minI = i - 1 < 0 ? 0 : i;
const minJ = j - 1 < 0 ? 0 : j;
최종 구현 코드는 아래와 같다.
function solution(board) {
const total = board.length * board.length;
let danger = 0;
for (let i = 0; i < board.length; i++) {
for (let j = 0; j < board.length; j++) {
const minI = i - 1 < 0 ? 0 : i - 1;
const minJ = j - 1 < 0 ? 0 : j - 1;
const maxI = i + 1 === board.length ? board.length - 1 : i + 1;
const maxJ = j + 1 === board.length ? board.length - 1 : j + 1;
if (
board[minI][minJ] === 1 ||
board[minI][j] === 1 ||
board[minI][maxJ] ||
board[i][minJ] === 1 ||
board[i][j] === 1 ||
board[i][maxJ] ||
board[maxI][minJ] === 1 ||
board[maxI][j] === 1 ||
board[maxI][maxJ]
)
danger++;
}
}
return total - danger;
}
나중에 알게 되었는데, 옵션널 체이닝
을 통해서 이차배열에서도 undefined를 회피할 수 있었다. 왜 이것을 생각하지 못했는지 모르겠다. 그렇다면 굳이 삼항연산자로 회피코드를 만들 필요도 없었고, 여기서 나온 실수로 시간이 잡아먹을 이유도 없었는데 말이다. 보통 객체에서 옵션널 체이닝을 많이 사용하고 이차배열에서는 사용하는 게 익숙하지 않다 보니 놓친 부분인 것 같다. 이차배열 자체도 많이 사용하지 않았던 것 같고...
아래는 업데이트 코드이다!
function solution(board) {
const total = board.length * board.length;
let danger = 0;
for (let i = 0; i < board.length; i++) {
for (let j = 0; j < board.length; j++) {
if (
board[i - 1]?.[j - 1] === 1 ||
board[i - 1]?.[j] === 1 ||
board[i - 1]?.[j + 1] ||
board[i][j - 1] === 1 ||
board[i][j] === 1 ||
board[i][j + 1] ||
board[i + 1]?.[j - 1] === 1 ||
board[i + 1]?.[j] === 1 ||
board[i + 1]?.[j + 1]
)
danger++;
}
}
return total - danger;
}
다른 사람 풀이 중에 같은 아이디어임에도 좀 더 깔끔한 풀이를 소개한다.
function solution(board) {
let outside = [
[-1, 0],
[-1, -1],
[-1, 1],
[0, -1],
[0, 1],
[1, 0],
[1, -1],
[1, 1],
];
let safezone = 0;
board.forEach((row, y, self) =>
row.forEach((it, x) => {
if (it === 1) return false;
return outside.some(([oy, ox]) => !!self[oy + y]?.[ox + x])
? false
: safezone++;
})
);
return safezone;
}
outside
는 말그대로 체크하는, 순회하는 좌표의 바깥 좌표들(8개)을 나타낸다. 그런데 왜 -1,0,1의 향연이냐고?! 현재 좌표를 기준으로 -1, 1을 더하면 바깥 좌표들을 만들어 낼 수 있기때문이다. 그래서 내부 forEach에서 outside를 기준으로 체크를 해주는 것이다. 여기서도 옵셔널체이닝을 사용해서 undefined 에러를 회피하였다.(나만 고생한듯...ㅠㅠ)
진짜 별거 아닌 문제였고, 실제로 지뢰찾기게임도 구현해봤어서 금방 풀거라고 생각했다. 하지만 역시 프로그래밍은 어디서 어떻게 문제가 발생할지 모른다. 어떻게보면 아이디어 + 구현까지 끝난부분인데, 하나를 디버깅하는데 오래걸려서 이모양 이꼴이 되다니... 역시 디버깅을 얼마나 빠르고 정확하게 잘할 수 있느냐가 진짜 실력이라고 생각한다!! 나는 언젠가 실력자가 될 수 있을까?! 🚀