[내일배움캠프][TIL] 24.01.05 (금) - n^2 배열 자르기, Android 앱개발 숙련 - Fragment
1. 코드카타
오늘 푼 알고리즘 문제는 'n^2 배열 자르기' 이다.
n^2 배열 자르기 |
![]() |
![]() |
오랜만에 이걸 적게 됐는데, 저번주에는 팀 프로젝트에 온 정신을 쏟아서, 그리고 이번주는 슬슬 문제의 난이도가 올라감에 따라서 하루만에 문제를 해결하지 못해서였다.
이번 문제도 한 2~3일은 붙잡은 끝에 겨우겨우 푼 문제이다.
그렇다면 어떻게 이 문제를 풀었는지 한 번 알아보기로 하자.
위 영상은 이 문제를 푸는 과정을 이해시키기 위해 만들어진 영상이다.
이번 문제의 가장 큰 관건은 역시나 '어떻게 규칙을 찾아내서 적용시키는가' 였다.
행렬을 일렬로 붙인 다음에 left부터 right까지 잘라서 표현하는 건 할만한데 가장 처음에 n x n 행렬에 숫자를 채우는 규칙을 찾는 것부터 난항을 겪었다. 아니, 규칙 자체는 눈에 보이는데 이걸 for문으로 적용시키려니 이렇게 해도 안 되고, 저렇게 해도 안 되더라.
하도 규칙이 안 보이길래 그냥 이렇게 한 번 그려봤다.
(밑에 있는 숫자는 x행 x열을 나타내는 것으로, 컴퓨터에서는 0부터 시작하기 때문에 0을 집어 넣었다.)
이걸 곰곰이 살펴보다가 순간 아! 하고 규칙성이 눈에 나타났다.
이렇게 노란색 중앙 부분을 기준으로, 노란색으로 칠해진 부분은 행이 열보다 더 큰 구간이고, 파란색으로 칠해진 부분은 반대로 열이 행보다 더 큰 구간이다.
이렇게 볼 때, 각 구간에 있는 값은 그 구간의 행과 열을 비교했을 때 더 큰 수에서 1을 더한 값이 된다.
예를 들어, [0][1]은 열의 값이 더 크므로 여기서 1을 더한 2가 되고, 반대로 [3][1]은 행의 값이 더 크므로 여기서 1을 더한 4가 된다.
이를 코드로 나타내면 다음과 같다.
for (i in 0 until n) {
for (j in 0 until n) {
if (i >= j) matrix[i][j] += i+1
else matrix[i][j] += j+1
}
}
여기까지 왔으면 나머지를 적는 건 쉽다.
class Solution {
fun solution(n: Int, left: Long, right: Long): IntArray {
var answer: IntArray = intArrayOf()
var matrix = Array(n) { IntArray(n) }
var arr: IntArray = intArrayOf()
for (i in 0 until n) {
for (j in 0 until n) {
if (i >= j) matrix[i][j] += i+1
else matrix[i][j] += j+1
}
}
for (i in matrix.indices) {
for (j in matrix.indices) {
arr += matrix[i][j]
}
}
var l: Int = left.toInt()
var r: Int = right.toInt()
for (i in l..r) {
answer += arr[i]
}
return answer
}
}
위의 for문에서 만든 matrix 행렬을 바탕으로 이중 for문을 만들어 arr 행렬에 일렬로 배치시키고, 그걸 다시 left와 right까지만 반복시켜서 answer에 저장한다.
이렇게 이번 문제도 해결!
…했으면 애초에 3일 씩이나 걸리지도 않았다.
여기서 나는 굉장히 생소한 방식의 오답 유형과 마주쳤는데 바로 '메모리 초과'.
시간 초과도 아니고 메모리 초과라니.
뭘 어떻게 해야 메모리 초과가 뜨는 건가하고 자세히 살펴보니 세상에.
왜 이게 메모리 초과가 뜨는지 금방 알게 됐다.
그 이유는 바로 제한사항에 있는데
보다시피 n의 범위가 10^7까지다.
그러니까 숫자의 용량을 1byte로 잡는다고 쳐도 10^7을 처리하려면 10^7byte 씩이나 필요하다는 뜻.
심지어 이중 for문을 썼으니까 이걸 또 한 번 처리하면 무려 10^14byte다. 이걸 환산하면 무려 9TB라는 어마어마한 양이 나오는데 일반 컴퓨터가 어떻게 이걸 처리하겠냐...
그러니까 위와 같은 방식으로 코드를 짜면 컴퓨터가 계산 돌리다가 터져도 할 말이 없다.
따라서 문제 접근 방식은 그대로 가되, 메모리를 효율적으로 처리할 수 있는 방법을 찾아야 한다는 것.
그렇다면 그 방법이 뭘까?
자, 위의 행렬을 다시 한 번 보자.
이걸 일렬로 붙인다고 가정했을 때, 0에서 15까지 인덱스가 나올 거다. n^2이니까.
그러면 이렇게 인덱스가 부여되겠지.
여기서 또다른 규칙을 알아낼 수 있는데 행은 인덱스를 n으로 나누었을 때 몫, 열은 그 나머지가 된다.
예를 들어, 인덱스 5를 4로 나누면 몫이 1이고, 나머지가 1이므로 1행 1열이 나오게 된다.
이를 코드로 나타내면 다음과 같이 나온다.
var x = it/n
var y = it%n
여기서 x는 행, y는 열을 뜻한다.
이렇게 변환한 값을 토대로 다시 만든 코드는 다음과 같다.
class Solution {
fun solution(n: Int, left: Long, right: Long): IntArray {
var answer: IntArray = intArrayOf()
for (i in left..right) {
var x: Int = (i/n).toInt()
var y: Int = (i%n).toInt()
if (x >= y) answer += x+1
else answer += y+1
}
return answer
}
}
2. Android 앱개발 숙련 - Fragment
저번 시간에는 RecyclerView에 대해서 알아봤다. (물론 아직은 완벽하게 숙지된 건 아니지만)
이번 시간에는 Fragment에 대해서 알아보도록 하겠다.
이것 또한 앱개발에서 중요한 개념이기 때문에 잘 알아두는 것이 좋다.
Fragment란?
액티비티 위에서 동작하는, 모듈화된 사용자 인터페이스
○ Fragment 사용하는 이유
- Fragment를 사용하면 Activity 끼리 이동시키는 것보다 자원 이용량이 적음
- Activity의 복잡도 감소
- 재사용할 수 있는 레이아웃을 분리해서 관리 가능
- 여러 Activity를 만들지 않아도 여러 화면을 보여줄 수 있음
○ Fragment 생명주기 (Fragment Lifecycle)
- onAttach()
- 프래그먼트가 액티비티에 연결될 때 호출
- 프래그먼트와 액티비티가 아직 완전히 연결되지는 않은 상태
- onCreate()
- 프래그먼트가 생성될 때 호출
- 초기화 작업, 리소스 바인딩 등을 수행
- onCreateView()
- 프래그먼트의 레이아웃을 인플레이트하는 곳
- 뷰를 생성하고, 레이아웃을 설정
- onActivityCreated()
- 액티비티의 onCreate() 메소드가 완료된 후 호출
- 액티비티와 프래그먼트의 뷰가 모두 생성된 상태이므로, 뷰와 관련된 초기화를 수행
- onStart()
- 프래그먼트가 사용자에게 보여질 준비가 되었을 때 호출
- 필요한 리소스를 할당하거나, 애니메이션을 시작
- onResume()
- 프래그먼트가 사용자와 상호작용할 수 있는 상태가 되었을 때 호출
- 프래그먼트가 포그라운드에 있을 때 실행되는 작업을 여기서 처리
- onPause()
- 프래그먼트가 일시정지될 때 호출
- 상태 저장, 스레드 중지 등의 작업을 수행
- onStop()
- 프래그먼트가 더 이상 사용자에게 보이지 않을 때 호출
- 리소스 해제, 스레드 정지 등을 수행
- onDestroyView()
- 프래그먼트의 뷰와 관련된 리소스를 정리할 때 호출
- onDestroy()
- 프래그먼트가 파괴될 때 호출
- 프래그먼트의 상태를 정리하고, 모든 리소스를 해제
- onDetach()
- 프래그먼트가 액티비티로부터 분리될 때 호출
- 프래그먼트가 액티비티와의 모든 연결을 해제
Fragment 만들기
액티비티를 만들 때와 비슷하게, 하나의 Kotlin 소스 파일과 하나의 XML레이아웃로 정의
Kotlin 소스 파일 생성
- 프래그먼트를 생성하려면 Fragment의 서브클래스(또는 이의 기존 서브클래스)를 생성
- 프래그먼트에 대해 레이아웃을 제공하려면 반드시 onCreateView()콜백 메서드를 구현
class FirstFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_first, container, false) }
- inflate()함수를 통해서 fragment_first.xml 파일로부터 레이아웃을 로드
- 프래그먼트에 대해 레이아웃을 제공하려면 반드시 onCreateView()콜백 메서드를 구현
- XML 레이아웃 생성
- 프래그먼트도 부분 화면이므로 화면에 표시될 뷰들을 정의하는 XML 파일을 /res/layout 폴더 안에 생성한다.
액티비티의 레이아웃 파일에 정적 프래그먼트를 추가하기
- 프래그먼트를 액티비티의 레이아웃 파일 안에서 선언
- <fragment> 안의 android:name 특성은 레이아웃 안에서 인스턴스화할 Fragment 클래스를 지정
- [중요] 각 프래그먼트에는 액티비티가 재시작되는 경우 프래그먼트를 복구하기 위해 시스템이 사용할 수 있는 고유한 식별자가 필요.
- 프래그먼트에 ID를 제공하는 방법
- 고유한 ID와 함께 android:id 속성을 제공
- 고유한 문자열과 함께 android:tag 속성을 제공
- 위의 두 가지 중 어느 것도 제공하지 않으면, 시스템은 컨테이너 뷰의 ID를 사용
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
<fragment
android:name="com.skmns.fragmentbasic.FirstFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/fragment" />
</LinearLayout>
Kotlin 코드에서 동적으로 프래그먼트 추가하기
supportFragmentManager.commit {
replace(R.id.frameLayout, frag)
setReorderingAllowed(true)
addToBackStack("")
}
- supportFragmentManager
- 사용자 상호작용에 응답해 Fragment를 추가하거나 삭제하는등 작업을 할 수 있게 해주는 매니저
- replace
- 어느프레임 레이아웃에 띄울것이냐, 어떤 프래그먼트냐 등을 나타냄
- setReorderingAllowed
- 애니메이션과 전환이 올바르게 작동하도록 트랜잭션과 관련된 프래그먼트의 상태 변경을 최적화
- addToBackStack
- 뒤로가기 버튼 클릭시 다음 액션 (이전 fragment로 가거나 앱이 종료되거나)
Fragment의 데이터 전달
1. Activity → Fragment
MainActivity.kt (보내는 코드)
binding.run {
fragment1Btn.setOnClickListener{
// [1] Activity -> FirstFragment
val dataToSend = "Hello First Fragment! \n From Activity"
val fragment = FirstFragment.newInstance(dataToSend)
setFragment(fragment)
}
fragment2Btn.setOnClickListener {
// [1] Activity -> SecondFragment
val dataToSend = "Hello Second Fragment!\n From Activity"
val fragment = SecondFragment.newInstance(dataToSend)
setFragment(fragment)
}
FirstFragment.kt (받는 코드)
private var param1: String? = null
companion object {
@JvmStatic
fun newInstance(param1: String) =
// [1] Activity -> FirstFragment
FirstFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// [1] Activity -> FirstFragment
binding.tvFrag1Text.text = param1
}
2. Fragment → Fragment
FirstFragment.kt (보내는 코드)
// [2] Fragment -> Fragment
binding.btnGofrag2.setOnClickListener{
val dataToSend = "Hello Fragment2! \n From Fragment1"
val fragment2 = SecondFragment.newInstance(dataToSend)
requireActivity().supportFragmentManager.beginTransaction()
.replace(R.id.frameLayout, fragment2)
.addToBackStack(null)
.commit()
}
SecondFragment.kt (받는 코드)
private const val ARG_PARAM1 = "param1"
class SecondFragment : Fragment() {
private var param1: String? = null
private var _binding: FragmentSecondBinding? = null
private val binding get() = _binding!!
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentSecondBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// [2] Fragment -> Fragment
binding.tvFrag2Text.text = param1
}
companion object {
@JvmStatic
fun newInstance(param1: String) =
// [1] Activity -> FirstFragment
SecondFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
}
}
}
override fun onDestroyView() {
super.onDestroyView()
// Binding 객체 해제
_binding = null
}
}
3. Fragment → Activity
SecondFragment.kt (보내는 코드)
private const val ARG_PARAM1 = "param1"
interface FragmentDataListener {
fun onDataReceived(data: String)
}
class SecondFragment : Fragment() {
// [3] SecondFragment -> Activity
private var listener: FragmentDataListener? = null
private var param1: String? = null
private var _binding: FragmentSecondBinding? = null
private val binding get() = _binding!!
override fun onAttach(context: Context) {
super.onAttach(context)
// [3] SecondFragment -> Activity
if (context is FragmentDataListener) {
listener = context
} else {
throw RuntimeException("$context must implement FragmentDataListener")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentSecondBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// [2] Fragment -> Fragment
binding.tvFrag2Text.text = param1
// [3] SecondFragment -> Activity
binding.btnSendActivity.setOnClickListener{
val dataToSend = "Hello from SecondFragment!"
listener?.onDataReceived(dataToSend)
}
}
companion object {
@JvmStatic
fun newInstance(param1: String) =
// [1] Activity -> FirstFragment
SecondFragment().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
}
}
}
override fun onDestroyView() {
super.onDestroyView()
// Binding 객체 해제
_binding = null
listener = null
}
}
MainActivity.kt (받는 코드)
class MainActivity : AppCompatActivity(), FragmentDataListener {
private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
binding.run {
fragment1Btn.setOnClickListener{
// [1] Activity -> FirstFragment
val dataToSend = "Hello First Fragment! \n From Activity"
val fragment = FirstFragment.newInstance(dataToSend)
setFragment(fragment)
}
fragment2Btn.setOnClickListener {
// [1] Activity -> SecondFragment
val dataToSend = "Hello Second Fragment!\n From Activity"
val fragment = SecondFragment.newInstance(dataToSend)
setFragment(fragment)
}
}
setFragment(FirstFragment())
}
private fun setFragment(frag : Fragment) {
supportFragmentManager.commit {
replace(R.id.frameLayout, frag)
setReorderingAllowed(true)
addToBackStack("")
}
}
// [3] SecondFragment -> Activity
override fun onDataReceived(data: String) {
// Fragment에서 받은 데이터를 처리
Toast.makeText(this, data, Toast.LENGTH_SHORT).show()
}
}