본문 바로가기
♞ | 공부일지/♝ | TIL

[Android, 내일배움캠프] 공부일지(2024-07-11)

by immgga 2024. 7. 11.
오늘 공부한 내용 정리(2024년 7월 11일)

 

출처: unsplash.com

 

코드카타 문제풀이

타일(Bronze 1, 5556번, 마라톤)

문제 내용

 

문제 풀이 방법

상근이가 N x N 정사각형에 타일을 바깥쪽에서 안쪽으로 빨강 - 파랑 - 노랑 순으로 타일을 구성했을 때, 창영이가 제거한 타일의 위치가 주어지고, 제거된 타일이 무슨 색인지 숫자로 출력.빨강: 1, 파랑: 2, 노랑: 3중앙을 기준으로 대각선 또는 직선으로 접으면 색상의 위치가 바뀌지 않은 채로 주어진 제거된 타일의 위치를 보기 편한 위치로 옮길 수 있다.이를 이용해 최대한 한쪽으로 제거 타일을 몰아서 위치를 확인할 것이다.

 

 

해결 코드(스포 주의)

더보기
import java.io.BufferedReader
import java.io.InputStreamReader

fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {
    val width = readLine().toInt()
    val removeCase = readLine().toInt()

    for (i in 0 until removeCase) {
        var (x, y) = readLine().split(" ").map { it.toInt() }

        if (x > (width+1) / 2) {
            x = width + 1 - x
        }
        if (y > (width+1) / 2) {
            y = width + 1 - y
        }
        if (x < y) {
            val move = x
            x = y
            y = move
        }

        if (y % 3 == 1) println(1)
        if (y % 3 == 2) println(2)
        if (y % 3 == 0) println(3)
    }
}

 

풀이 과정

맵의 크기(width)와 삭제될 타일의 개수(removeCase)를 입력받는다.removeCase의 개수만큼 반복해서 삭제된 타일의 위치를 x, y로 입력받는다.

 

이제 반복문에서 x와 y를 한쪽으로 몰아주는 작업을 해줄 것이다.x, y값이 중앙보다 크다면 아래 사진을 참고하면 전부 3, 4 사분면에 존재할 것이다. 이를 전부 2 사분면으로 옮겨주는 작업을 할 것이다.

타일 배치를 다음과 같이 자른다는 뜻이 담긴 사진.

x, y좌표를 전부 중앙이 넘는지 체크해서 전부 윗 사분면으로 보내줄 것이다.

또한 x가 y보다 크다면 서로의 값을 교환해 준다.

x와 y좌표가 바뀌어도 제거된 타일의 색이 변하지 않는다.

위 절차를 완료해 주면 모두 2 사분면을 대각선으로 잘랐을 때의 윗 삼각형의 위치로 이동될 것이다.

값을 전부 한 곳으로 몰아주면 y축을 기준으로 3으로 나눈 나머지를 이용해 타일의 색을 구한다.

 

 

문제 해결 과정

타일의 위치를 모두 한 곳으로 몰아줌으로써 x, y 중 하나의 경우를 제외하고 경우를 구할 수 있다.아이디어를 생각하지 못하면 실버 이상의 난이도를 가진 문제가 될 것 같다.나는 아이디어를 생각하기가 힘들어서 다른 사람의 문제 접근법을 참고했다.

 

 

부분 문자열(Silver 5, 6550번, 마라톤)

문제 내용

 

문제 풀이 방법

s와 t 문자열이 주어질 때, s의 문자들이 t에 순서대로 찾을 때, s 문자들이 모두 있으면 Yes, 아니면 No를 출력한다.

s: person,  t: compression일 때, t 안에 p, e, r, s, o, n이 모두 있지만, 순서대로 나와있지 않다(t에는 p, r, e, s, o, n 순서대로 주어진다). 이럼 정답은 No이다.

 

해결 코드(스포 주의)

더보기
import java.io.BufferedReader
import java.io.InputStreamReader

fun main() = with(BufferedReader(InputStreamReader(System.`in`))) {

    while (true) {
        var result = ""
        var startIndex = -1
        val data = readLine() ?: break
        val (str, delStr) = data.split(" ")

        for (c in str) {
            var saveIndex = -1

            // 찾은 마지막 자리수를 제외시키고 검색해야 함.
            for (i in startIndex + 1 until delStr.length) {
                if (c == delStr[i]) {
                    saveIndex = i
                    break
                }
            }

            if (saveIndex == -1) {
                result = "No"
                break
            }
            startIndex = saveIndex
        }

        if (result.isEmpty()) println("Yes")
        else println(result)
    }
}

 

풀이 과정

결과를 저장할 result를 생성하고, 찾기 시작할 index(startIndex)를 -1로 초기화한다. startIndex를 늘려 가면서 이전에 방문한 데이터는 다시 방문하지 않도록 구현해야 한다.

str, delStr을 생성해서 s와 t를 입력받는다.

 

이제 str번 반복해 str의 문자들이 delStr에 있는지 확인한다.

반복을 시작할 때, startIndex에 1을 더해 줘서 이전까지 방문했던 값이 다시 방문되지 않도록 해준다. 그래서 startIndex의 초기값을 -1로 설정했다(첫 검색 할 때, 1번째 문자부터 시작하면 안 되니까).

만약 delStr의 문자 중 str의 문자가 있다면 인덱스 저장용 변수인 saveIndex를 i값으로 바꿔준다.

반복 종료 후 saveIndex가 초기값과 같은 -1이라면 같은 값이 없었다는 뜻이므로 result에 No를 대입하고 break를 수행.

만약 saveIndex가 -1이 아니라면 startIndex를 saveIndex로 설정해 준다.

 

최종 반복 종료 후 result가 비어 있으면 모두 문제없이 str의 문자들이 검색되었다는 뜻이므로 Yes를 출력한다.

아니면 No를 출력한다.

 

 

문제 해결 과정

EOFError를 이용해야 하는 문제이다. 무한 반복으로 입력받아야 한다.문자 검색을 할 때 단어가 존재해서 saveIndex를 설정하고 startIndex를 받고 나서 다시 반복을 시작할 때, 시작 값을 startIndex로 설정하면 이전에 검색한 값을 포함해서 다음과 같은 반례가 발생한다.

반례: caa bcda
다음과 같이 검색을 진행한다.
c: b, c 순회
a: c, d, a 순회
a: a?
순회 하면서 이전에 체크한 값을 또 보게 되기 때문에 올바르지 않다.

이 문제를 고려하지 않아서 문제를 틀렸다.

 

 

주특기 숙련 완전 정복 강의

주특기 숙련 완전 정복 강의

View Rendering(뷰 렌더링)

View의 생명주기는 다음과 같다.

 

Activity는 안드로이드의 4대 컴포넌트 중 하나이고 UI 구성을 위해 가장 기본이 되는 요소Activity의 라이프사이클 함수를 알고 있어야 특수 상황에 대응할 수 있는 코드를 작성해 앱의 효율을 높일 수 있다.

 

 

View Binding(뷰 바인딩)

findViewById: xml과 view를 연결해 준다.findViewById에는 다음과 같은 단점이 존재한다.

사용할 id를 하나하나 연결해 주는 것이 귀찮다.
코드가 지저분해진다.
null 안정성을 위해할 가능성이 있다: 개발자의 실수로 유효하지 않은 id를 사용하면 오류가 발생.

 

다음과 같은 단점이 있어서 view binding이 생기게 되었다.

 

ViewBinding이 findViewById보다 좋은 이유findViewById: 모든 layout 파일의 id를 viewGroup에 저장해 놓는다(누가 봐도 느리겠지?).viewBinding: layout 파일 각각을 컴파일 시 Binding 클래스로 변환해 준다.

 

fragment에 ViewBinding을 사용할 때, fragment가 destroy 될 때 binding 변수를 null처리를 해주어야 하는데, 이를 넣어주는 이유가 fragment는 재사용을 염두에 두고 뷰를 전부 축적시켜 놓는데 fragment에 메모리 누수가 발생할 가능성이 있다.

 

 

앱 개발 숙련 개인 과제

사과 마켓 메인 페이지 기능 구현

메인 화면 구현

메인 화면에 구현해야 할 것들은

recyclerView 구현
뒤로 가기 클릭 시 alertDialog 띄우기
알림 아이콘 클릭 시 push notification 띄우기.
recyclerView 데이터 클릭 시 intent에 상품 정보를 putExtra로 넘겨주고 상품 정보 Activity로 화면 이동.

 

recyclerView 구현은 크게 어려운 부분이 없었으므로 코드 설명은 생략한다.

먼저 recyclerview에 사용할 item xml을 만들어 주었다.

 

그다음 adapter를 만들어서 recyclerview에 필요한 데이터를 전달해 주고, 받아온 상품 데이터를 item에 적용해 주었다.

// adapter 내부의 innter class인 ViewHolder의 일부
inner class ViewHolder(private val binding: ItemRecyclerViewGoodsBinding): RecyclerView.ViewHolder(binding.root) {
    fun bind(goods: GoodsData) {
        with(binding) {
            val priceFormat = DecimalFormat("#,###원")

            goodsImage.setImageResource(goods.goodsImage)
            goodsTitle.text = goods.title
            goodsAddress.text = goods.address
            goodsPrice.text = priceFormat.format(goods.price)
            goodsChatCnt.text = goods.chatCnt.toString()
            goodsLikeCnt.text = goods.likeCnt.toString()
        }
    }
}

 

그다음으로는 뒤로 가기 콜백을 받아서 콜백 호출 시 alertDialog를 부를 수 있도록 만들어 주었다.

기존에는 onBackPressed() 함수를 override 해서 사용했지만 Deprecated 되어서 콜백으로 만들어 주고, 콜백 내부에 Dialog 코드를 넣어 주었다.

// 콜백 코드
private val onBackPressedCallback: OnBackPressedCallback = object: OnBackPressedCallback(true) {
    override fun handleOnBackPressed() {
        // dialog 열기.
        AlertDialog.Builder(this@MainActivity)
            .setIcon(R.drawable.ic_chatting)
            .setTitle("종료")
            .setMessage("정말로 종료하시겠습니까?")
            .setPositiveButton("확인") { dialog, _ ->
                finish()
                dialog.dismiss()
            }
            .setNeutralButton("취소") { dialog, _ ->
                dialog.dismiss()
            }
            .create()
            .show()
    }
}

// onCreate() 내부의 콜백 호출 코드.
onBackPressedDispatcher.addCallback(onBackPressedCallback)

 

세 번째로는 알림을 만들어 주었다.

Android Tiramisu 버전 이후부터는 앱의 알림 권한이 활성화되어 있는지 체크하는 게 필수가 된 것 같다. 그래서 알림 권한 활성화 여부 확인 후 알림 권한 활성화 코드를 추가해 주었다.

private fun initNotificationPermission() {
    if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
        if (!NotificationManagerCompat.from(this).areNotificationsEnabled()) {
            val intent = Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
                putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
            }
            startActivity(intent)
        }
    }
}

 

그다음으로 알림을 불러 주었다.

private fun showNotification() {
    // notification channel도 android tiramisu 버전 이후부터는 필수 구현이 되었다.
    val channel = NotificationChannel(CHANNEL_ID, "apple market channel", NotificationManager.IMPORTANCE_DEFAULT)
    channel.description = "apple market channel description"
    val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
    notificationManager.createNotificationChannel(channel)

    val builder = NotificationCompat.Builder(this, CHANNEL_ID)
    builder.run {
        setSmallIcon(R.mipmap.ic_launcher)
        setWhen(System.currentTimeMillis())
        setContentTitle("키워드 알림")
        setContentText("설정한 키워드에 대한 알림이 도착했습니다!!")
    }

    notificationManager.notify(11, builder.build())
}

 

마지막으로는 recyclerView의 아이템 클릭 시 상품 데이터를 넘겨주면서 상품 정보 Activity로 넘어가는 코드를 써야 한다.

그러기 위해서는 기존의 Adapter 코드에 파라미터로 context를 받아 준다. intent에 필요하기 때문이다.

class GoodsAdapter(private val context: Context): RecyclerView.Adapter<GoodsAdapter.ViewHolder>()

 

그다음 ViewHolder에 뷰 클릭 이벤트를 달아 준다.

// GoodsAdapter.ViewHolder.bind의 코드 중 일부.
binding.root.setOnClickListener {
    val intent = Intent(context, GoodsInfoActivity::class.java)
    intent.putExtra("goods", goods)
    context.startActivity(intent)
}

binding.root가 아이템 전체를 뜻한다. 그러므로 위 이벤트는 recyclerView item 어떤 부분을 눌러도 클릭 이벤트가 작동한다.

리스너 안에 goods를 key로 설정해 goods 데이터를 전달하고 startActivity로 화면 전환을 한다.

 


 

오늘 공부 내용 정리 및 회고

알고리즘 불렙 문제가 나와서 시간이 좀 많이 뺏겼다.

오늘 푼 2문제 중 Bronze 1 문제가 Silver 5 문제보다 체감 난도가 높았다.

요즘 점점 나태해지는 것 같다. 이를 해결하기 위한 방법이 없을까?

그래도 한 번 개인 과제에 몰입하니까 또 과제를 65% 정도는 해치운 것 같다.

집중력을 오래 유지시킬 방법을 생각해 봐야겠다.

 

 

 

 

 

728x90