코틀린-안드로이드

68일차)알고리즘 문제(퍼즐 조각 채우기), 특강(Room, 인터페이스로 데이터 동기화, adapter MVVM구조에서 유의점, 인터페이스를 이용한 데이터 동기화)

songyooho 2024. 8. 9. 21:20

>알고리즘 문제

1. 문제

테이블 위에 놓인 퍼즐 조각을 게임 보드의 빈 공간에 적절히 올려놓으려 합니다. 게임 보드와 테이블은 모두 각 칸이 1x1 크기인 정사각 격자 모양입니다. 이때, 다음 규칙에 따라 테이블 위에 놓인 퍼즐 조각을 게임 보드의 빈칸에 채우면 됩니다.

  • 조각은 한 번에 하나씩 채워 넣습니다.
  • 조각을 회전시킬 수 있습니다.
  • 조각을 뒤집을 수는 없습니다.
  • 게임 보드에 새로 채워 넣은 퍼즐 조각과 인접한 칸이 비어있으면 안 됩니다.

다음은 퍼즐 조각을 채우는 예시입니다.

위 그림에서 왼쪽은 현재 게임 보드의 상태를, 오른쪽은 테이블 위에 놓인 퍼즐 조각들을 나타냅니다. 테이블 위에 놓인 퍼즐 조각들 또한 마찬가지로 [상,하,좌,우]로 인접해 붙어있는 경우는 없으며, 흰 칸은 퍼즐이 놓이지 않은 빈 공간을 나타냅니다. 모든 퍼즐 조각은 격자 칸에 딱 맞게 놓여있으며, 격자 칸을 벗어나거나, 걸쳐 있는 등 잘못 놓인 경우는 없습니다.

이때, 아래 그림과 같이 3,4,5번 조각을 격자 칸에 놓으면 규칙에 어긋나므로 불가능한 경우입니다.

  • 3번 조각을 놓고 4번 조각을 놓기 전에 위쪽으로 인접한 칸에 빈칸이 생깁니다.
  • 5번 조각의 양 옆으로 인접한 칸에 빈칸이 생깁니다.

다음은 규칙에 맞게 최대한 많은 조각을 게임 보드에 채워 넣은 모습입니다.

최대한 많은 조각을 채워 넣으면 총 14칸을 채울 수 있습니다.

현재 게임 보드의 상태 game_board, 테이블 위에 놓인 퍼즐 조각의 상태 table이 매개변수로 주어집니다. 규칙에 맞게 최대한 많은 퍼즐 조각을 채워 넣을 경우, 총 몇 칸을 채울 수 있는지 return 하도록 solution 함수를 완성해주세요.


제한사항

  • 3 ≤ game_board의 행 길이 ≤ 50
  • game_board의 각 열 길이 = game_board의 행 길이
    • 즉, 게임 보드는 정사각 격자 모양입니다.
    • game_board의 모든 원소는 0 또는 1입니다.
    • 0은 빈칸, 1은 이미 채워진 칸을 나타냅니다.
    • 퍼즐 조각이 놓일 빈칸은 1 x 1 크기 정사각형이 최소 1개에서 최대 6개까지 연결된 형태로만 주어집니다.
  • table의 행 길이 = game_board의 행 길이
  • table의 각 열 길이 = table의 행 길이
    • 즉, 테이블은 game_board와 같은 크기의 정사각 격자 모양입니다.
    • table의 모든 원소는 0 또는 1입니다.
    • 0은 빈칸, 1은 조각이 놓인 칸을 나타냅니다.
    • 퍼즐 조각은 1 x 1 크기 정사각형이 최소 1개에서 최대 6개까지 연결된 형태로만 주어집니다.
  • game_board에는 반드시 하나 이상의 빈칸이 있습니다.
  • table에는 반드시 하나 이상의 블록이 놓여 있습니다.

2. 솔루션

class Solution {
    fun solution(game_board: Array<IntArray>, table: Array<IntArray>): Int {
        var answer: Int = 0
        
        val size=table.size
        
        
        val gamePiece=BFS(0,game_board)
        val tablePiece=BFS(1,table)
        
        val gamePieceWithRotationIdx=Array(gamePiece.size){HashSet<List<Int>>()}
        val tablePieceIdx=tablePiece.map{indexing(it,size)}
        
        for(i in 0..gamePiece.size-1){
            for(j in 0..3){
                gamePieceWithRotationIdx[i]+=indexing(gamePiece[i],size)
                rotation(gamePiece[i])
            }
        }
        
        
        
        
        val isUsed=BooleanArray(gamePieceWithRotationIdx.size){false}
        
        for(i in tablePieceIdx){
            for((idx,j) in gamePieceWithRotationIdx.withIndex()){
                if(isUsed[idx]) continue
                var flag=false
                for(k in j){
                    if(i==k&&!isUsed[idx]){
                        flag=true
                        answer+=i.size
                        isUsed[idx]=true
                        break
                    }
                }
                if(flag) break
            
            }
        }
        
        
        return answer
    }
}

fun indexing(piece:Piece, size:Int):List<Int>{
    val result=HashSet<Int>()
    
    val visited=HashSet<Block>()
    
    val q=ArrayDeque<Pair<Int,Block>>()
    q.addLast(Pair(0,piece.blocks[0]))
    visited+=piece.blocks[0]
    
    while(q.isNotEmpty()){
        val (idx,b)=q.removeFirst()
        result+=idx
        
        for(i in 0..3){
            b.adj[i]?.let{
                if(!visited.contains(it)){
                    when(i){
                        0 -> q.addLast(Pair(idx-size,it))
                        1 -> q.addLast(Pair(idx+1,it))
                        2 -> q.addLast(Pair(idx+size,it))
                        else -> q.addLast(Pair(idx-1,it))
                    }
                    visited+=it
                }
            }
        }
    }
    return result.sorted().let{it.map{b -> b-it[0]}}
}

fun blockRotation(block:Block){
    block.adj.also{
        val tmp=it[0]
        it[0]=it[1]
        it[1]=it[2]
        it[2]=it[3]
        it[3]=tmp
    }
}

fun rotation(piece:Piece){
    for(i in piece.blocks){
        blockRotation(i)
    }
}

fun BFS(type:Int, arr:Array<IntArray>):ArrayList<Piece>{
    val size=arr.size
    val visited=BooleanArray(size*size){false}
    val result=ArrayList<Piece>()
    
    for(i in arr.indices){
        for(j in arr.indices){
            if(arr[i][j]!=type){
                visited[i*size+j]=true
            }
        }
    }
    
    val q=ArrayDeque<Triple<Int,Block,Piece>>()
    
    var initIdx=0
    
    for(i in visited.indices){
        if(!visited[i]){
            visited[i]=true
            initIdx=i
            break
        }
    }
    
    val piece=Piece()
    val block=Block()
    piece.blocks+=block
    result+=piece
    
    q.addLast(Triple(initIdx,block,piece))
    
    val dx=listOf(0,0,1,-1)
    val dy=listOf(1,-1,0,0)
    
    while(q.isNotEmpty()){
        val (idx,b,p)=q.removeFirst()
        
        val x=idx/size
        val y=idx%size
        
        for(i in 0..3){
            val nx=x+dx[i]
            val ny=y+dy[i]
            if(nx !in 0..size-1||ny !in 0..size-1) continue
            val nextIdx=nx*size+ny
            if(!visited[nextIdx]){
                visited[nextIdx]=true
                val newB=Block()
                p.blocks+=newB
                when(i){
                    0 ->{
                        b.adj[2]=newB
                        newB.adj[0]=b
                    }
                    1 ->{
                        b.adj[0]=newB
                        newB.adj[2]=b
                    }
                    2->{
                        b.adj[1]=newB
                        newB.adj[3]=b
                    }
                    else->{
                        b.adj[3]=newB
                        newB.adj[1]=b
                    }
                }
                q.addLast(Triple(nextIdx,newB,p))
            }
        }
        
        if(q.isEmpty()){
            for(i in visited.indices){
                if(!visited[i]){
                    visited[i]=true
                    val piece=Piece()
                    val block=Block()
                    piece.blocks+=block
                    result+=piece
                    q.addLast(Triple(i,block,piece))
                    break
                }
            }
        } 
    }
    return result
}


class Block( ){
    val adj:MutableList<Block?> =mutableListOf(null,null,null,null)//위 오른 아래 왼
}

class Piece(){
    val blocks=ArrayList<Block>()
}

-BFS로 각 Piece들을 구함

-table과 game에 대한 piece모음을 구함

-game에 대하여 각 piece에 대해 rotation을 돌린 경우도 모아둠

-각 piece에 대해 indexing을 하여 결과를 비교함.

 

 

>특강

1. Room: 스마트폰 내장 DB에 데이터 저장을 위한 라이브러리
1)세가지 요소
-Database:DB객체를 만들기 위한 추상 클래스
-Entity: table
-DAO: 쿼리

-DB는 Application의 Context가 필요

@Context받는것 유의점: Context는 받은 스코프의 수명이 끝나면 context도 파기됨
=>프래그먼트에서 받으면 프래그먼트 수명이 끝나면 context도 파기됨
=>그때문에 DB는 Application에서 받는게 좋음
=>받는 방법은 Application class를 만들어서 그 안에 public인 applicationContext변수를 선언해둔다.
=넣을때는 requireContext()?.applicationContext as ~applicationContext

-entity는 유니크 아이디가 필요
=>유니크한 프로퍼티위에 어노테이션을 primaryKey를 넣으면 됨. 없으면 autogenerate=true로

-Room은 Date타입이 적용이 안되므로 Long타입으로 바꿔주는 converter를 추가해야함 =>데이터베이스에 @TypeConverters로 적용

-DB도 코루틴에서 사용할거면 Dao에서 쿼리 메소드를 suspend로 만들기

-데이터베이스 객체를 생성하고 쿼리 쓰는건 코루틴에서 하기

@viewModel은 View의 context를 가지면 안됨 =>둘은 라이프사이클이 다르므로 메모리누수가 발생. 
=>필요하면 어플리케이션 컨텍스트를 제공

@유의점:Adapter역시도 뷰이기 때문에 비즈니스 로직을 뷰모델이나 유스케이스로 최대한 빼줘야 한다.

@viewModels<뷰모델타입> 과 activityViewModels의 차이점
=>viewModels는 해당 viewModel을 초기화 하는 Component의 라이프사이클을 따름
=>ActivityViewModels는 root Activity의 LifeCycle을 따름
=>즉, 위에 것은 component가 프래그먼트먼 오너가 프래그먼트가 되어 해당 라이프사이클을 따름. 
=>오너가 같은 동일타입 뷰모델은 한 인스턴스가 공유되는 상태이다.

@인터페이스를 이용하여 데이터 동기화
1) 데이터 변경할 인터페이스 생성
2) 컨테이너 역할을 하는 액티비티가 인터페이스 상속후 메소드 오버라이드.
=>첫번째 프레그먼트가 컨텍스트를 인터페이스로 타입변환하여 받은뒤에 해당 메소드로 값 변경
=>바뀐데이터를 액티비티가 저장
3) 두번째 프래그먼트 생성시 바뀐 데이터를 건네줌

interface LikeUserEvent{
	fun like(item:User)
}


class MainActivity: AppCompatActivity(), LikeUserEvent{
	private var favoerite:User? = null
    
    override fun onCreate(saveInstanceState:Bundle?){
    	.
        .
        .
        suppporFragmentManager.beginTransaction().
        	replace(
            	R.id.container,
                secondFragment.newInstance(favorite)
            )
            .commit()
    .
    .
    .
    override fun like(item:User){
    	favorite = item
    }
}


class FirstFragment: Fragment(){
	
    private var likeUserEvent : LikeUserEvent? = null
    
    override fun onAttach(context:Context){
    	super.onAttach(context)
        likeUserEvent = context as LikeUserEvent
    }
    
    override fun onViewCreated(view:View, savedInstanceState: Bundle?){
    	//likeuser메소드 활용 코드
    }
    .
    .
    .
}