[24.06.17] TIL - 012

0. 오늘은 무엇을 했는가

오늘은 Kotlin 심화 주차의 마지막 날입니다.

먼저, 코드 카타를 풀고, 과제의 남은 부분을 진행했습니다.

오늘은 꽤 순조롭게 진행됐습니다. 이후에는 코틀린을 조금 더 공부했습니다.

앞으로도 코틀린을 계속해서 공부하겠지만, 오늘은 어느 정도 마무리하는 느낌으로 진행했습니다.

아직도 좀 더 공부할 내용이 많이 남아있어, 앞으로 열심히 해야 할 것 같습니다.

1. 코드 카타

[ 09:00 ~ 10:00 ]

오늘의 코드 카타는 연속 부분 수열 합의 개수 였습니다.

처음엔 이게 무슨 소리인가 했는데, 몇 번 보다보니까 대충 이해가 갔습니다.

철호는 수열을 가지고 놀기 좋아합니다. 어느 날 철호는 어떤 자연수로 이루어진 원형 수열의 연속하는 부분 수열의 합으로 만들 수 있는 수가 모두 몇 가지인지 알아보고 싶어졌습니다. 원형 수열이란 일반적인 수열에서 처음과 끝이 연결된 형태의 수열을 말합니다. 예를 들어 수열 [7, 9, 1, 1, 4] 로 원형 수열을 만들면 다음과 같습니다.

원형 수열은 처음과 끝이 연결되어 끊기는 부분이 없기 때문에 연속하는 부분 수열도 일반적인 수열보다 많아집니다.
원형 수열의 모든 원소 elements가 순서대로 주어질 때, 원형 수열의 연속 부분 수열 합으로 만들 수 있는 수의 개수를 return 하도록 solution 함수를 완성해주세요.

즉, 수열 [7, 9, 1, 1, 4]가 있을 때 원형으로 만들고, 1자리씩 늘려가면서 합을 구하는 것입니다.

예를 들어, 길이가 1인 연속 부분 수열의 합은 [7, 9, 1, 4] 4종류이며,
2인 연속 부분 수열의 합은 [7+9, 9+1, 1+1, 1+4, 4+7] 이렇게 해서 [16, 10, 2, 5, 11] 5종류입니다.
이렇게 해서 모든 부분의 합까지 구하고, 이를 집합으로 반환해주면 됩니다.

그렇기에, Set을 사용해 집합을 만들어주었고, 삼중 for문을 사용하여 짜주었습니다.

class Solution {
    fun solution(elements: IntArray): Int {
        var answer: Int = 0
        val eleset:MutableSet<Int> = mutableSetOf()
        for(i in 0..elements.size-1) {
            for(j in 0..elements.size-1) {
                var sum = 0
                for(k in 0..i) {
                    if(j+k < elements.size) 
                        sum += elements[j+k]
                    else sum += elements[j+k-(elements.size)]
                }
                eleset.add(sum)
            }
        }
        answer = eleset.size
        return answer
    }
}

보기보다 난이도가 쉬운 알고리즘이었던 것 같습니다.

2. 심화 과제 마무리 및 제출

[ 11:00 ~ 12:00 ]

이전에 말했던 심화 과제에 Lv. 4와 Lv. 5를 적용시켜서 제출했습니다.

먼저, Lv. 4는 다음과 같이 적용했습니다.

Lv.4

  • 문자 입력, 없는 숫자 입력 시 예외 처리로 다시 입력할 수 있도록 바꾸기
  • 잔액과 가격 비교해서 구매 가능한지 확인 - 캐릭터 만들기
    => object로 소비자, 키오스크 만들고, 키오스크는 메뉴 선택이랑 가격 계산
  • 오류별 다른 문구 출력

이를 위해, try-catch문을 이용해 주었고, Object를 이용해 객체를 만들었습니다.

fun main() {
    var nowMenu: CorrectMenu = MainMenu()
    while (true) {
        try {
            showMenu(nowMenu)
            val selectNum = selectNum(nowMenu)
            if(selectNum == -1) break
            var selectedMenu: CorrectMenu = nowMenu
            if(nowMenu is MainMenu)
                selectedMenu = selectMenu(selectNum)
            else if(nowMenu is Food) selectedMenu = selectFood(nowMenu, selectNum)
            nowMenu = selectedMenu
        } catch (e: NumberFormatException) {
            println("숫자가 아닌 다른 문자를 입력하지 마십시오.\n")
        } catch (e: IndexOutOfBoundsException) {
            println("메뉴에 존재하지 않는 숫자를 입력하지 마십시오.\n")
        }
        catch (e: Exception) {
            println("$e 알 수 없는 오류가 발생했습니다.")
            nowMenu = MainMenu()
            println("메인 화면에서 재시작합니다.\n")
            continue
        }
    }
}

먼저, 잘못된 숫자와 숫자가 아닌 문자에 대해 예외처리를 해주었고,

Kiosk라는 장바구니를 위주로 작동하는 객체와 Consumer라는 돈을 쓰는 고객 객체를 만들었기에,

해당 객체를 각 함수 필요한 곳에 추가해주었습니다.

이것이 Lv. 4의 주된 내용입니다.

Lv. 5는 쓰레드를 주로 이용해서 실시간 기능을 구현하는 게 목표였습니다.

Lv.5

  • 특정 작업 종료 후 3초 뒤 다른 작업 수행 -> 쓰레드
  • 결제 시간이 특정 시간대면 결제 불가
  • 결제 완료 시 시간 띄움
  • 프로그램 종료할 때까지 5초마다 현재 주문 대기수 실시간 출력

저같은 경우에는 은행 작업이 진행되는 데에 3초의 지연 시간을 주었습니다.

그리고, 5초마다 주문 대기수를 출력하는 데 코루틴을 사용했습니다.

@OptIn(DelicateCoroutinesApi::class)
fun startWaitingCoroutine() = GlobalScope.launch(Dispatchers.IO) {
    while (true) {
        delay(5000)
        println("\n현재 주문 대기수 : $waiting, 입력 : ")
    }
}

위와 같이 코루틴을 실행해서 5초마다 현재 주문 대기수가 보이게 만들었습니다. 하지만

println("현재 시각은 ${nowHour}시 ${nowMin}분입니다.")
println("결제가 진행 중입니다. 잠시만 기다려주십시오.")
repeat (9) {
	print(".")
	Thread.sleep(300)
}
println("완료!")

해당 부분에서 0.3초마다 "."을 출력해 로딩을 표현하는 부분에 있어, 자꾸 겹쳐서 나올 때가 있어서

이 부분 전에

waitingCoroutine.cancel()

을 추가하고, 작업이 끝난 뒤에는

waitingCoroutine = startWaitingCoroutine()

을 추가해서 다시 실행했습니다.

심화 과제 Lv. ??도 그렇게 오래 걸릴 것 같진 않지만, 코틀린을 좀 더 공부하고 싶어서 일단을 보류했습니다.

3. Kotlin 심화 강의 복습

[ 13:00 ~ 20:00 ]

오늘은 제가 기존에 갖고 있던 책으로 Kotlin 심화 강의에서 배운 내용들을 다시 복습했습니다.

당시엔 대충 보고 넘어갔던 부분이 몇 군데 있었는데, 다시 보니까 중요한 내용들도 몇몇 있었습니다.

예를 들어, List(개수) { index를 이용한 형태 } 방식이었습니다.

이게 생성자인 줄 알았지만, 생성자가 아니라 그냥 함수라고 하더라고요.

이 부분이 심화 학습이 아닌 것 같지만, 컬렉션이기도 하고, 무엇보다 람다 함수가 들어가 있습니다.

{ } 이 부분에서 it을 사용하지만, 기본적인 형태는 아마 { (Int) -> T } 일 가능성이 높습니다.

어쨌든, 람다, 고차 함수랑 Scope 함수에 대해 공부한 내용들을 적어보겠습니다.

3-1. 람다 함수, 고차 함수

먼저, 람다 함수는

{ (매개변수, ... ) -> 반환값 }

입니다.

정말 간단하게 표현한 건데, 람다 함수를 쓰는 이유는 짧은 함수를 따로 생성까지 하지 않고,

위처럼 표현해서 코드를 간결하게 하기 위함입니다.

람다 자체는 실행 시점에 부가적인 비용이 발생해서 오히려 성능 면에서는 조금 떨어진다고 합니다.

어쨌든, 이런 부가 비용을 감안하고서라도 간단하게 작성하고, 함수형 프로그래밍을 할 수 있습니다.

함수형 프로그래밍은 작은 함수들을 만들어, 하나의 함수를 이루는 식으로, 함수들을 조합해서

튼튼한 코드를 만들 수 있다는 장점이 있습니다.

코틀린은 객체 지향 프로그래밍과 함수형 프로그래밍을 지원하는 언어이며, 특히 람다를 정말 많이 사용합니다.

이런, 람다의 표현 방식 자체는 앞서 말했듯, { (매개변수, ... ) -> 반환값 } 이고,

인자를 전달할 때는 map( { it -> "[$it]" } ) 처럼 표현할 수 있습니다.

이걸 map { "[$it]" } 까지도 줄일 수 있습니다.

그리고 이렇게 함수에서 반환값으로 함수를 돌려주면, 고차 함수를 지원한다고 말합니다.

3-2. 영역 함수 ( Scope 함수 )

영역 함수는 객체 이름을 사용하지 않고, 그 객체에 접근할 수 있게 해줍니다.

말이 어렵지만, 사실 그냥 객체에 하고 싶은 일들을 한번에 해결할 수 있다는 뜻입니다.

기본적으로 영역 함수는 다음과 같이 씁니다.

// 마지막 식의 값 반환
with(객체) {
	this.~~
    30
}
객체.run {
	this.~~
    30
}
객체.let {
	it.~~
    30
}

// 수신 객체 반환
객체.apply {
	this.~~
    this = 30
}
객체.also {
	it.~~
    it = 30
}

30이라는 값은 단순히 이해를 위해 적었습니다.

with, run은 사실상 같은 기능을 하지만, with의 경우는 확장 함수가 아니라는 점이 차이점입니다.

사실 특별할 게 없습니다. 객체에 여러 번 뭔가 적용할 걸 한 번에 넣어주겠단 의미밖에 없습니다.

전 그것도 모르고 좀 어려워 했었습니다. 이렇게 쉬운 걸...

3-3. SAM 변환

후에 다시 다룰 것 같지만, 중요한 내용이라 가져와봤습니다.

SAM(Single Abstract Method) 변환은 인터페이스에 추상 메소드가 하나뿐일 때 이를 확 줄일 수 있는 코틀린의 기능입니다.

예를 들어,

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
            	//기능
            }
        });

위와 같은 Java 코드에서 setOnClickListener 안의 onClick을 불러서 씁니다. 하지만, 코틀린에서는 단 하나뿐인 추상 메소드인데, 굳이 그럴 필요가 있나? 라는 생각에

button.setOnClickListener {
	// it 사용
}

까지 줄여버립니다. 즉, 인터페이스가 하나의 함수 역할을 대신하는 것처럼 표현할 수 있습니다.

이 부분은 위에서 보셨듯이, 안드로이드 개발 강의가 시작되고, 시간이 지나면 지날수록 다시 언급될 가능성이 높습니다.

코틀린은 정말 편하고 짧은 걸 좋아하는 것 같습니다.

4. 끝

오늘로 코틀린 강의 주차가 끝이 났습니다.

앞으로는 안드로이드를 배우면서 프로젝트도 하는 바쁘지만 재밌는 날들이 있을 것 같습니다.

더불어, 챌린지 반에 신청했기에, 저 스스로도 좀 더 도전적으로 바뀔 수 있었으면 좋겠습니다.

끝.

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

0개의 댓글