[js] 안전지대

sookyoung.k·2024년 7월 4일
1
post-thumbnail

다음 그림과 같이 지뢰가 있는 지역과 지뢰에 인접한 위, 아래, 좌, 우 대각선 칸을 모두 위험지역으로 분류합니다.

지뢰는 2차원 배열 board에 1로 표시되어 있고 board에는 지뢰가 매설 된 지역 1과, 지뢰가 없는 지역 0만 존재합니다.
지뢰가 매설된 지역의 지도 board가 매개변수로 주어질 때, 안전한 지역의 칸 수를 return하도록 solution 함수를 완성해주세요.

제한사항

  • board는 n * n 배열입니다.
  • 1 ≤ n ≤ 100
  • 지뢰는 1로 표시되어 있습니다.
  • board에는 지뢰가 있는 지역 1과 지뢰가 없는 지역 0만 존재합니다.

나의 풀이

// 지뢰찾기
const findMine = (board) => {
    let mine = [];
    board.forEach((row, i) => {
        row.forEach((ceil, j) => {
            if (ceil === 1) mine.push([i, j]);
        })
    })
    return mine
}

// 안전하지 않은 좌표를 0에서 1로 바꾸기 (단, 이미 1일 경우 그대로 1로 두기)
const changeBoard = (mines, board) => {
    // 지뢰 주변의 안전하지 않은 좌표를 notSafe 배열에 추가 
    let notSafe = [];
    mines.forEach(([x, y]) => {
        for (let i = -1; i <= 1; i++) {
            for (let j = -1; j <= 1; j++) {
                let newX = x + i;
                let newY = y + j;
                // 유효한 좌표인지 확인
                if (newX >= 0 && newY >= 0 && newX < board.length && newY < board[0].length) {
                    notSafe.push([newX, newY]);
                }
            }
        }
    });
    
    // notSafe 배열을 참고하여 해당하는 곳의 좌표를 1로 변경
    notSafe.forEach(([x, y]) => {
        board[x][y] = 1;
    });
    
    return board;
}

// 안전지역 숫자 세기
const countSafeSide = (board) => {
    let safeArea = 0;
    board.forEach((row, i) => {
        row.forEach((ceil, j) => {
            if (ceil === 0) safeArea++;
        })
    })
    return safeArea;
}

function solution(board) {
    // 지뢰 좌표 배열을 구한다
    let mines = findMine(board);
    
    // 지뢰에 안전한 칸을 위험지역으로 바꾸기 
    let remarkBoard = changeBoard(mines, board);
    
    // 안전한 지역 칸 수 계산
    return countSafeSide(remarkBoard)
    
}

하 안전지대 찾는거 정말 어렵다... 세상에 모든 지뢰는 없어져야 합니다.
문제를... 어떻게 풀어야 할지 정말 막막하다 생각을 했다. 그래서 일단 순서를 나눴다.

  1. 지뢰가 있는 좌표를 찾는다. 하나 이상일 수 있으니 배열에 담는다.
  2. 지뢰 근처의 안전지역을 위험지역으로 바꾼다. (0 → 1)
  3. 안전한 지역 칸 수를 계산한다.

이 과정에서 어려웠던 건... 이중배열이라는 점... 어떻게 손 대야 할지 막막하다가, 손대면서도 '이게 맞나?' 하며 혼란스럽고 에러떴는데 어찌어찌... 일단 해결은 했습니다.

function solution(board) {
    // 지뢰 좌표 배열을 구한다
    let mines = findMine(board);
    
    // 지뢰에 안전한 칸을 위험지역으로 바꾸기 
    let remarkBoard = changeBoard(mines, board);
    
    // 안전한 지역 칸 수 계산
    return countSafeSide(remarkBoard)
    
}

먼저 솔루션 함수 부분을 본다.

위에서 1, 2, 3으로 순서를 나눴던 부분을 각각 함수로 분리해서 따로 기능하도록 만들었다. 그리고 솔루션 함수에서 만들어둔 함수를 호출하며 최종 답을 구할 수 있게 했다.

  1. findMine(board)

    • findMine은 이름에서 알 수 있듯이 인자로 받는 board에서 지뢰의 위치를 찾는 함수이다.
    • 지뢰의 좌표를 담을 mine 배열을 선언한다.
    • forEach 이중 루프를 사용하며 보드의 모든 칸을 검사한다. 먼저 첫 번째 forEach는 바깥 배열을 돌며 각 요소(row)와 인덱스 번호(i)를 인자로 받는다.
    • row는 배열이다. 이를 다시 한 번 forEach로 돌며 ceil, j를 인자로 받는다.
    • ceil === 1이라는 조건을 만족시키면 mine 배열에 [i, j]를 넣어준다.
    • board를 전부 순환한 뒤에는 최종적으로 완성된 mine 배열을 반환한다. 이는 지뢰의 위치를 담고 있다.
  2. changeBoard(mines, board)

    • 이번에는 인자로 받은 mines(지뢰의 좌표)를 바탕으로 잠재적 위험 발생 지역의 좌표를 찾는 함수이다.
    • 지뢰의 주변 좌표를 담을 notSafe 배열을 선언한다.
    • mines 배열에 있는 지뢰의 좌표 [x, y]에 대해 반복문을 실행한다.
    • 이중 for문을 사용하여 i의 값을 -1에서 1까지, j의 값을 -1에서 1까지 반복한다. 이는 각각 (x, y)의 위, 아래, 현재 위치와 왼쪽, 오른쪽, 현재 위치를 의미한다.
    • 새로운 좌표 newXnewY를 계산한다. (x + i), (y + j)를 통해서 x좌표와 y좌표 기준 위, 아래, 현재 위치/왼쪽, 오른쪽, 현재 위치를 나타낸다.
    • if (newX >= 0 && newY >= 0 && newX < board.length && newY < board[0].length) { ... } ➡️ newX와 newY가 board 배열의 범위를 벗어나지 않도록 조건을 추가한다.
    • 조건을 모두 만족할 경우 새로운 좌표(안전하지 않은 곳의 위치 정보를 담은 좌표) (newX, newY)를 notSafe 배열에 추가한다.
  3. countSafeSide(board)

    • 보드에서 안전한 칸의 개수를 세는 함수이다. 인자는 원본 board를 받는다.
    • 안전한 곳의 수를 나타낼 safeArea를 0으로 초기화한다.
    • board를 이중forEach루프를 통해 또 전부 순환한다.
    • 위에서 지뢰의 자표를 찾는 것과는 반대로 ceil이 0인 조건을 만족한다면 safeArea의 카운트를 올린다.
    • board를 전부 순회하고 나면 최종적으로 safeArea의 값을 반환한다.

가독성 있게 만들고 싶어서 노력을 많이 했다. 갠적으로 이번주에 풀었던 문제 중에서 제일 빡쎘음...

다른 풀이 1

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;
}
  1. 변수를 초기화
    • outside는 현재 셀을 기준으로 인접한 8 방향의 좌표를 정의한 배열이다.
    • safezone은 안전 영역의 개수를 저장할 변수로 0으로 초기화 해주었다.
  2. 이중 forEach루프
    • board의 각 행(row)를 순회하며, 각 행의 요소(it)를 다시 순회한다.
    • y는 현재 행의 인덱스, x는 현재 셀의 인덱스이고, self는 board의 배열 자체를 참조한다.
    • 현재 셀이 1(폭탄)인 경우, 더 진행하지 않고 다음 셀로 넘어간다.
    • outside 배열을 사용해서 현재 셀에서 인접한 8 방향을 검사한다.
    • some() 메서드는 배열의 요소 중 하나라도 조건을 만족한다면 true를 반환한다.
    • !!self[oy + y]?.[ox + x] ➡️ 인접한 셀이 1인지 검사하는 것으로 ?. 연산자는 인덱스가 배열 범위를 벗어날 때 발생할 수 있는 오류를 방지한다.
    • 인접한 셀 중 하나라도 폭탄이 있으면 false를 반환하고, 그렇지 않으면 safezone을 증가시킨다.
    • self[oy + y]?.[ox + x]의 값이 undefined가 아니고, null이 아니고, 0이 아니고, false가 아니고, 빈 문자열이 아니면 참(true)이 됩
    • !!는 논리 부정 연산자를 두 번 사용한 것이다. 원래 값이 참이면 참, 거짓이면 거짓으로 변환한다. 때문에 위 코드는 self[oy + y]?.[ox + x]가 존재하고 그 값이 truthy한지 확인하는 표현이다.

다른 풀이 2

function solution(b) {
    const directions = [[0,0],[0,1],[0,-1],[1,1],[1,0],[1,-1],[-1,-1],[-1,0],[-1,1]]
    let bombSet = new Set();

    for(let i = 0; i < b.length; i++) {
        for(let j = 0; j < b[i].length; j++) {
            if(b[i][j] == 1) {
                directions.forEach(el => {
                    let [nextX, nextY] = el;
                    [nextX, nextY] = [i+nextX, j+nextY];
                    if(nextX >= 0 && nextX < b.length && nextY >= 0 && nextY < b[i].length) {
                        bombSet.add(nextX+' '+nextY);
                    }
                })
            }
        }
    }
    return b.length * b[0].length - bombSet.size;
}
  1. directions 배열은 2차원 평면에서의 8방향(상하좌우, 대각선)과 현재 위치를 나타낸다. [0,0]은 현재 위치를 나타낸다.
  2. 폭탄 위치를 저장할 집합을 초기화한다.
    • bombSet은 폭탄이 있는 위치와 그 주변 위치를 저장하는 집합이다.
    • Set은 중복 값을 허용하지 않기 때문에, 같은 위치가 여러 번 추가되어도 하나의 값으로만 저장된다.
  3. 이중 for문을 사용하여 2차원 배열의 모든 요소(b)를 순회한다.
    • b[i][j]가 1이면 그 위치에 폭탄이 있다는 의미이다.
    • directions 배열을 순회하며 폭탄이 있는 위치와 그 주변 위치를 계산한다.
    • nextXnextY는 폭탄 위치 i, j와 방향 벡터 el을 더한 값이다.
    • nextX와 nextY가 유효한 배열 인덱스 범위 내에 있으면, 그 위치를 bombSet에 추가합니다. 위치는 문자열 형태로 저장됩니다 ("nextX nextY").
  4. 안전한 위치의 개수를 계산하고 반환한다.
    • b.length * b[0].length ➡️ 2차원 배열 b의 총 요소 개수를 나타낸다.
    • bombSet.size는 폭탄이 있는 위치와 그 주변 위치의 개수를 나타낸다.
    • 전체 요소에서 폭탄이 있는 위치와 그 주변 위치 개수를 뺀 값을 반환한다.

아 ... 진짜 어려웠다. 이중 배열, 좌표 개념이 들어가니까 0단계지만 미친듯이 어려웠다... 파이팅...

profile
영차영차 😎

0개의 댓글