[24.06.19] TIL - 014

0. 오늘은 무엇을 했는가

오늘은 정말 더웠습니다. 집에 있는데도 이렇게 더울 수 있다니..

오전에 코드 카타를 할 때는 괜찮았지만, 2시부터 5시까지는 정말 더워서 집중하기가 어려웠습니다.

그래도, 개인 과제 Lv. 1을 마무리했고, 다른 공부도 어느 정도 했습니다.

1. 코드 카타

[ 09:00 ~ 12:00 ]

오늘의 코드 카타는 n^2 배열 자르기 였습니다. 문제만 보면 그렇게 어려워 보이지는 않습니다.

정수 n, left, right가 주어집니다. 다음 과정을 거쳐서 1차원 배열을 만들고자 합니다.
n행 n열 크기의 비어있는 2차원 배열을 만듭니다.
i = 1, 2, 3, ..., n에 대해서, 다음 과정을 반복합니다.
1행 1열부터 i행 i열까지의 영역 내의 모든 빈 칸을 숫자 i로 채웁니다.
1행, 2행, ..., n행을 잘라내어 모두 이어붙인 새로운 1차원 배열을 만듭니다.
새로운 1차원 배열을 arr이라 할 때, arr[left], arr[left+1], ..., arr[right]만 남기고 나머지는 지웁니다.
정수 n, left, right가 매개변수로 주어집니다. 주어진 과정대로 만들어진 1차원 배열을 return 하도록 solution 함수를 완성해주세요.

당연히 제일 간단한 방법은 배열을 다 구하고, left, right 부분만 잘라내는 방식입니다.

그리고, 당연히 그 방법은 시간 초과로 틀렸습니다.

그렇다면, 규칙성을 찾아내 바로 left부터 right까지 배열에 추가해주면 될 것 같습니다.

규칙성은 단순합니다. n까지 1씩 증가하는 수들이지만, n번째에는 n개의 n이 있습니다.

예를 들어, n이 5라면, [1,2,3,4,5], [2,2,3,4,5], [3,3,3,4,5], [4,4,4,4,5], [5,5,5,5,5] 처럼 생겼습니다.

그래서, 이를 구현하기 위해서, left번부터 right번까지 반복문 안에서, 12345를 계속해서 주는 대신,

해당하는 부분에는 2, 3같은 n을 주었습니다. 다음은 제가 작성한 코드입니다.

class Solution {
    fun solution(n: Int, left: Long, right: Long): IntArray {
        var answer: IntArray = intArrayOf()
        var div:Long = 0
        var num:Long = 0
        for(i in left..right) {
            div = i / n
            num = i % n
            if(num <= div) num = div
            num++
            answer += num.toInt()
        }
        return answer
    }
}

근데, 위 코드로 작성하니까 문제가 하나 있었습니다.

그건 바로, 실행 시간이었습니다.

하나의 케이스를 진행하는 데, 3000ms나 걸리는 경우가 허다했습니다.

왜 이러는지 확인하기 위해, 다른 분의 풀이를 확인했지만, 어떤 게 문제인지 헷갈렸습니다.

그리고, 결국은 답을 찾아냈는데...

var answer: IntArray = intArrayOf()

이 부분이 문제였습니다.

IntArray는 기본적으로 고정 길이를 갖고 있습니다.

그래서 값을 추가할 때마다, 새롭게 배열을 만듭니다.

즉,

answer += num.toInt()

이 부분에서 계속해서 배열을 만들어내서, 엄청나게 실행 시간이 올라가게 된 겁니다.

그래서 해당 부분을 고려해 다음과 같이 변경해줬습니다.

class Solution {
    fun solution(n: Int, left: Long, right: Long): IntArray {
        var answer: ArrayList<Int> = arrayListOf()
        for(i in left..right) {
            var div = i / n
            var num = i % n
            if(num <= div) num = div
            num++
            answer += num.toInt()
        }
        return answer.toIntArray()
    }
}

ArrayList는 MutableList를 구현하는 객체이기 때문에, 가변 길이를 갖고 있어, 따로 생성하지 않고, 그 배열에 바로 추가합니다.

이후에, 20~30ms로 확 줄어들게 됐습니다.

이런 디테일적인 부분이 확실히 중요하다고 생각되는 날이었습니다.

2. 안드로이드 입문 개인 과제 - Lv. 1

[ 14:00 ~ 16:00 ]

사진이 너무 큰데, 위와 같은 UI로 제작했습니다. 그리고 다음과 같은 조건을 충족해 만들었습니다.

  • Lv1
    1️⃣ 로그인 페이지 만들기(SignInActivity)
    구현 조건을 복사해 빠짐 없도록 체크하며, 진행하는 것을 추천합니다.
    • [ v ] 새 프로젝트를 만들고 MainActivity의 이름을 SignInActivity로 바꿔주세요.
    • [ v ] 로고 이미지는 원하는 이미지로 넣어주세요.
    • [ v ] 아이디, 비밀번호를 입력 받는 EditText를 넣어주세요.(미리보기 글씨(플레이스 홀더) 포함)
    • [ v ] 비밀번호 EditText는 입력 내용이 가려져야 합니다.(●●● 처리)
    • [ v ] 로그인 버튼을 누르면 HomeActivity가 실행되도록 구현합니다.
      (Extra로 아이디를 넘겨줍니다.)
    • [ v ] 아이디/비밀번호 모두 입력 되어야만 로그인 버튼이 눌리도록 구현합니다.
      (“로그인 성공”이라는 토스트 메세지 출력하도록 구현)
    • [ v ] 아이디/비밀번호 중 하나라도 비어 있다면
      “아이디/비밀번호를 확인해주세요” 라는 토스트 메세지가 출력되도록 구현합니다.
    • [ v ] 회원가입 버튼을 누르면 SignUpActivity가 실행되도록 구현합니다.
package com.android.nbc_login

import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

class SignInActivity : AppCompatActivity() {
    @RequiresApi(Build.VERSION_CODES.S)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_signin)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }
        val btnLogin = findViewById<Button>(R.id.btn_login)
        val id = findViewById<EditText>(R.id.et_id)
        val pwd = findViewById<EditText>(R.id.et_pwd)
        btnLogin.setOnClickListener {
            if (id.text.toString().isBlank() || pwd.text.toString().isBlank()) {
                Toast.makeText(this, "ID/비밀번호를 전부 입력해주세요.", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this, "로그인 성공!", Toast.LENGTH_SHORT).show()
                val intent = Intent(this, HomeActivity::class.java)
                intent.putExtra("SignIn", id.text.toString())
                startActivity(intent)
            }
        }
        val btnSignUp = findViewById<Button>(R.id.btn_register)
        btnSignUp.setOnClickListener {
            val intent = Intent(this, SignUpActivity::class.java)
            startActivity(intent)
        }
    }
}

Lv. 1 자체는 되게 간단했습니다.

3. JetPack Compose - 컴포저블과 상태, 위임

[ 16:00 ~ 18:00, 19:00 ~ 20:00 ]

오늘은 오랜만에 JetPack Compose를 다시 다뤄보겠습니다.

그리고, 이후에 간단한 게임을 만들어볼 예정입니다.

막 휘황찬란한 3D가 아니라, 간단한 틱택토, 가위바위보, 오목같은 걸 생각하고 있습니다.

그전에, 컴포저블과 상태에 대해 제대로 잡고 갈 생각입니다.

오늘은 간단하게 개념에 대해 말해보겠습니다.

컴포저블 함수는 기존의 방식을 대체할 선언형 UI를 만드는 함수입니다.

기존의 방식보다 빠르고, 쉽고, 직관적입니다.

이런 컴포저블을 다룰 때는 상태라는 개념이 존재하는데요.

상태는 UI에서 특정한 값이 바뀔 때, 변경되는 점을 관찰하고, 변경되면 해당 상태가 있는 컴포저블을 재구성합니다.

이것이 컴포저블 함수의 특징 중 하나입니다. 컴포저블 함수는 계속해서 화면을 재구성합니다.

그 와중에 상태는 계속해서 기억되도록 이뤄지는데, 이를 코드로 쓰면,

var state by remember { mutableState() }

가 됩니다.

여기서 remember가 값을 기억해주기 때문에, 재구성을 해도 변경이 없는 상태는 계속 유지가 됩니다.

여기서 by는 위임을 쓸 때 사용하는 키워드인데, 이것이 중요합니다.

아직 100% 이해를 하진 못했지만, 위임에 대해 간단히 설명하면, 말 그대로 자신의 역할을 위임하는 것입니다.

기본적으로 인터페이스에서만 클래스 위임을 사용할 수 있습니다.

interface A { }

class B: A
class C(val b: B): A by b

위처럼 C에서 A를 받지만, A는 이 일을 B의 객체인 b에게 맡깁니다.

이외에도, 프로퍼티 위임이나, 함수를 위임하는 경우도 있던데, 아직 좀 더 공부해야 할 것 같습니다.

4. 끝

오늘은 이렇게 끝났습니다.

가면 갈수록 더워지니 집중하기가 어렵더라고요.

내일은 위임을 마무리하는 걸 목표로 열심히 해야겠습니다.

끝.

profile
여기는 공부 기록용 블로그

0개의 댓글