Algorithm

[Algorithm] Kotlin - programmers Lv0 [ 안전지대 ] - 테스트 8, 9번 에러 해결

정상호소인 2024. 3. 20. 06:14

[ Algorithm Link ]

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

 


 

최종 코드 미리보기

 

[ Programmers - sinoso 님 풀이 참고 및 해석 ]

private fun solution(board: Array<IntArray>): Int =
    board.indices.map { row ->
        board[row].indices.filter { board[row][it] == 1 }.forEach { col ->
            ((row - 1).coerceAtLeast(0)..(row + 1).coerceAtMost(board.lastIndex)).map { row2 ->
                ((col - 1).coerceAtLeast(0)..(col + 1).coerceAtMost(board.first().lastIndex)).forEach { col2 ->
                    if (board[row2][col2] == 0)
                        board[row2][col2] = 2
                }
            }
        }
    }.run { board.indices.map { board[it].count { value -> value == 0 } } }.sum()

 

 

 

 

1. 각각의 열의 위험지역 위치를 찾는다

board.indices.map { row ->
    board[row].indices.filter { board[row][it] == 1}::also { println(it) }
}

// 테스트 케이스 var testC = arrayOf(intArrayOf(0,0,0), intArrayOf(0,1,0), intArrayOf(1,1,1))
// 출력
// []
// [1]
// [0, 1, 2]

 

 

 

2. 지뢰가 영향을 주는 위험지역 범위를 찾는다

board.indices.map { row ->
    board[row].indices.filter { board[row][it] == 1}.forEach {col ->
        ((row - 1).coerceAtLeast(0)..(row + 1).coerceAtMost(board.lastIndex)).map { row2 ->
            ((col - 1).coerceAtLeast(0)..(col + 1).coerceAtMost(board.first().lastIndex))::also{ println("row2 : $row2 it : $it") }
        }
    }
}

// 출력
//row : 0 it : 0..2
//row : 1 it : 0..2
//row : 0 it : 0..2
//row : 1 it : 0..2
//row : 2 it : 0..2
//row : 1 it : 0..1
//row : 2 it : 0..1
//row : 1 it : 0..2
//row : 2 it : 0..2
//row : 1 it : 1..2
//row : 2 it : 1..2
  • (row - 1).coerceAtLeast(0), ( column + 1)coerceAtMost(board.lastIndex)
    • 보드를 시각화 했을 때, 위험지대가 영향을 미치는 세로 열의 범위를 나타낸다. 이때, coerceAtLeast ( 최소치 ), coerceAtMost( 최대치 ) 메서드를 통해서 보드에 존재하지 않는 범위는 제외하도록 처리 하였다.
  • (col - 1).coerceAtLest(0), ( column +1 )coerceAtMost(board.first().lastIndex)
    • row와 마찬가지로 보드를 시각화 했을 떄, 위험지대가 영향을 미치는 가로 열의 범위를 나타내며 최소치와 최대치를 지정함으로 보드에 존재하지 않는 범위는 제외하도록 처리.
  • 위의 과정을 통해 얻고자 하는 solution 리턴 값에 포함되지 않는 위험지대 범위를 알아낼 수 있다.

 

 

 

3. 지뢰를 제외한 위험지역에 값을 2로 할당하고 보드에 0 인 범위만 카운팅 하면 끝

private fun solution(board: Array<IntArray>): Int =
board.indices.map { row ->
    board[row].indices.filter { board[row][it] == 1 }.forEach { col ->
        ((row - 1).coerceAtLeast(0)..(row + 1).coerceAtMost(board.lastIndex)).map { row2 ->
            ((col - 1).coerceAtLeast(0)..(col + 1).coerceAtMost(board.first().lastIndex)).forEach { col2 ->
                if (board[row2][col2] == 0)
                    board[row2][col2] = 2
            }
        }
    }
}.run { board.indices.map { board[it].count { value -> value == 0 } } }.sum()

 

 

 

 

첫번째 제출 코드 - 테스트 9번 실패

 

 

더보기

[ 제출코드 1 ]

 

class Solution {
    fun solution(board: Array<IntArray>): Int {


    var boardLine = mutableListOf<MutableList<MutableList<Int>>>()
    for ((index, item) in board.withIndex()) {
        var boardSequence = mutableListOf<MutableList<Int>>()
        println(board.size - 1)
        println("index : $index")

        item.mapIndexed { bombIndex, i ->
            boardSequence.add(mutableListOf<Int>(bombIndex, i))

        }

        boardLine.add(boardSequence)
        println("boardLines${boardLine}")

    }

    var bombLocation = mutableListOf<List<List<Int>>>()
    for (line in boardLine) {
        bombLocation.add(line.filter { it[1] == 1 })
    }

    println(bombLocation
    )

    for ((index, item) in bombLocation.withIndex()) {

        for ( a in item.indices) {
            println("a : $a , item : ${item[a]}")
            when (index) {
                0 -> {
                    when (item[a].first()) {
                        0 -> {
                            boardLine[index][item[a][0]][1] ++
                            boardLine[index][item[a][0]+1][1] ++
                             println("아래 한칸 , 대각선 오른쪽")
                            boardLine[index+1][item[a][0]][1] ++
                            boardLine[index+1][item[a][0]+1][1] ++
                        }
                        board.size - 1 -> {
                            println("test ${item[0].size -1}")
                            boardLine[index][item[a][0]-1][1] ++
                            boardLine[index][item[a][0]][1] ++
                             println("아래 한칸 , 대각선 왼쪽")
                            boardLine[index+1][item[a][0]-1][1] ++
                            boardLine[index+1][item[a][0]][1] ++
                        }
                        else -> {
                            boardLine[index][item[a][0]-1][1] ++
                            boardLine[index][item[a][0]][1] ++
                            boardLine[index][item[a][0]+1][1] ++
                             println("아래, 대각선 좌,우")
                            boardLine[index+1][item[a][0]-1][1] ++
                            boardLine[index+1][item[a][0]][1] ++
                            boardLine[index+1][item[a][0]+1][1] ++
                        }
                    }

                    println("setSafe ${boardLine[index][item[a][0]][1]}")
                    println("setSafe ${boardLine[index][item[a][0]]}")

                }
                board.size - 1 -> {
                    when (item[a].first()) {
                        0 -> {
                            boardLine[index][item[a][0]][1] ++
                            boardLine[index][item[a][0]+1][1] ++
                            println("위 한칸 , 대각선 오른쪽")
                            boardLine[index-1][item[a][0]][1] ++
                            boardLine[index-1][item[a][0]+1][1] ++
                        }
                        board.size - 1 -> {
                            boardLine[index][item[a][0]-1][1] ++
                            boardLine[index][item[a][0]][1] ++
                            println("위 한칸 , 대각선 왼쪽")
                            boardLine[index-1][item[a][0]-1][1] ++
                            boardLine[index-1][item[a][0]][1] ++
                        }
                        else -> {
                            boardLine[index][item[a][0]-1][1] ++
                            boardLine[index][item[a][0]][1] ++
                            boardLine[index][item[a][0]+1][1] ++
                            println("위, 대각선 좌,우")
                            boardLine[index-1][item[a][0]-1][1] ++
                            boardLine[index-1][item[a][0]][1] ++
                            boardLine[index-1][item[a][0]+1][1] ++
                        }
                    }
                }

                else     -> {
                    when (item[a].first()) {
                        0 -> {
                            boardLine[index][item[a][0]][1] ++
                            boardLine[index][item[a][0]+1][1] ++
                             println("아래 한칸 , 대각선 오른쪽")
                            boardLine[index+1][item[a][0]][1] ++
                            boardLine[index+1][item[a][0]+1][1] ++
                             println("위 한칸 , 대각선 오른쪽")
                            boardLine[index-1][item[a][0]][1] ++
                            boardLine[index-1][item[a][0]+1][1] ++
                        }
                        board.size - 1 -> {
                            boardLine[index][item[a][0]-1][1] ++
                            boardLine[index][item[a][0]][1] ++
                             println("아래 한칸 , 대각선 왼쪽")
                            boardLine[index+1][item[a][0]-1][1] ++
                            boardLine[index+1][item[a][0]][1] ++
                             println("위 한칸 , 대각선 왼쪽")
                            boardLine[index-1][item[a][0]-1][1] ++
                            boardLine[index-1][item[a][0]][1] ++
                        }
                        else -> {
                            boardLine[index][item[a][0]-1][1] ++
                            boardLine[index][item[a][0]][1] ++
                            boardLine[index][item[a][0]+1][1] ++
                             println("아래, 대각선 좌,우")
                            boardLine[index+1][item[a][0]-1][1] ++
                            boardLine[index+1][item[a][0]][1] ++
                            boardLine[index+1][item[a][0]+1][1] ++
                             println("위, 대각선 좌, 우")
                            boardLine[index-1][item[a][0]-1][1] ++
                            boardLine[index-1][item[a][0]][1] ++
                            boardLine[index-1][item[a][0]+1][1] ++

                        }
                    }

                    println("setSafe ${boardLine[index][item[a][0]][1]}")
                    println("setSafe ${boardLine[index][item[a][0]]}")

                }
            }
        }
    }

    var answer: Int = (board.size*board.size)

    for ( i in boardLine) {
        for ( j in i) {
            if ( j[1] >= 1) {
                answer--
            }
        }
    }

    println("answer : ${answer}")
    return answer
}
}

[ 반려 사유 ]

코드 실행은 넘어가지만, 테스트 9 번 실패 

 

[ 원인 ]

  • 문제1. board.size가 1일 경우의 런타임 오류 발생
    • 테스트 9번은 board의 size가 1일 경우이다

 

 

 

 

 

제출코드 - 통과 → 리팩토링 전

 

class Solution {
    fun solution(board: Array<IntArray>): Int {

    if (board.size ==1 && board[0][0] == 0) {
        return 1
    } else if (board.size ==1 && board[0][0] == 1) {
        return 0
    } else {
        // 제출코드 1의 코드
    }
}

 

[ 작성코드 정리 ]

  • 초기에 작성 코드 구상 시, 보드의 넓이 ( n * n ) 의 구석자리 ( 0 , n - 1( lastIndex라고 얘기할 것. ) ) 에 위치한 인덱스는 각각 음수 인덱스, n의 크기를 초과하는 인덱스로 영향을 끼치지 못하게 when으로 0, lastIndex, else로 구분하여 노가다로 작성하기로 했다.
    • 1. 보드의 각 열에서 0, lastIndex, else로 구분하였으면, 첫번째 열은 맨 아래 열이므로 해당 열의 아래칸에 접근을 시도하지 않고, 마찬가지로 lastIndex는 맨 위에 열이므로 해당 열의 위 칸에 접근을 시도하지 않게 하여서 런타임 오류가 나지 않도록 했다
    • 2. 각 열 안에 0, lastIndex도 맨 왼쪽, 맨 오른쪽에 해당 하는 위치기 때문에 보드에 존재하지 않는 인덱스에 접근하지 않도록 했다.
  • 1, 2 의 노가다 과정에서 코드가 많이 작성될 것 같아 간소화할 수 있는 방법을 생각해보았는데 당장 떠오르는게 없어 일단 진행하고, 완성한 후 추가 학습을 해보려고 했다.
  • [ 제출코드 1 ] 에서 board.size == 1인 상황에 런타임이 나기에 board.size가 1일 경우의 예외처리를 추가하니 통과가 되었다.

 

 

 

알고리즘 회고
  • 알고리즘을 푸는 도중에 다른 풀이 방법들이 이따금씩 떠올랐는데, 기존 틀을 깨버리고 다시 만들다가 실패하게 되었다.  그제서야 백업해둔 것이 없어 브랜치에서 작업하는것이 중요하다는 것을 느꼈다.
  • bombLocation을 변수로 선언한 이유는 위험지역에 해당하는 인덱스의 값을++ 을 하기 때문에 위험 지역으로 끝나야 할 인덱스 범위가 반복문을 돌다보면 지뢰와 같이 인식되어 범위가 확장되기 때문에 초기 위치로만 반복문을 돌도록 작성하기 위함인데, 위험지역을 ++ 하는 방법 말고 2처럼 다른 값을 할당하고, 지뢰가 위치한 곳은 1로 두면 초기 지뢰 위치를 굳이 bombLocation으로 찾을 필요가 없다는 것을 끝나고나서야 깨달아서 되게 허탈했다.

 

[ 새롭게 배운 것 ]

  • collection.size 는 컬렉션 안에 요소의 갯수가 다 나오고, collection.lastIndex는 0부터 시작하는 총 갯수기 때문에 .size -1 과 동일한 결과가 나온다. 가독성도 lastIndex가 더 좋은 것 같아. 원소의 총 갯수와 마지막 인덱스를 구분해서 사용할 것이다

 

 

 

 

 


[Reference]