728x90

객체의 타입을 체크하기 위해 is 키워드를 사용한다.

fun main() {
    println(check(10))          //Integer 타입
    println(check("문자열"))     //String 타입
    println(check('a'))         //Character 타입
    println(check(true))        //Boolean 타입
    println(check(1.1))         //그외

}
fun check(any: Any): String {
    return when (any) {
        is Int -> "Integer 타입"
        is String -> "String 타입"
        is Char -> "Character 타입"
        is Boolean -> "Boolean 타입"
        else -> "그외"
    }
}

 when문에서 is를 사용해도 되고, if 문안에서 사용가능 하다. 

fun check(any: Any): String {
    if (any is Int) {
        return "Int"
    }else if(any is String){
        return "String"
    }
    return "그외"
}

이런식으로 해도 똑같다. 

 

캐스팅 

fun main() {
    println(cast("안녕"))     //안녕
    println(cast(1))         //Exception
}
fun cast(any: Any): String {
    val result = any as String
    return result
}

as 키워드로 데이터형을 변환시켜줌

any의 타입이 확실할 때,  캐스팅해서 원래 타입에서 사용할 수 있는 함수들을 사용할 수 있음 

 

 

as 에 ? 를 붙여 any가 String이 아니라면 Excenption을 발생시키지 않고 null을 반환함  

 

?: (엘비스 오퍼레이션 사용가능)

 

 

스마트 캐스팅

fun main() {
    println(smartCast(10))          //11
    println(smartCast("안녕"))      //2
    println(smartCast(true))       //-1
}
fun smartCast(any: Any): Int {
    return if (any is String) {
        any.length
    } else if (any is Int) {
        any.inc()
    } else {
        -1
    }
}

타입 체크를 해주고, 블록안에서 해당 타입으로 취급해서 원하는 값에 접근가능함 

728x90

생성자

  • 기본 생성자와 명시적 생성자가 존재
  • 기본 생성자는 이전까지 클래스를 만들던 행위와 차이가 없음
  • 명시적 생성자는 주 생성자와 부 생성자로 구분할 수 있음 // Init (주 생성자), Constructor (부 생성자)

주생성자의 예시 

fun main() {
    val people1 = People("사람1", 10, 100.0, 30.0)
    println(people1.name) //주생성자
                          //사람1
    people1.run()   // 뛴다.
}
class People(_name: String, _age: Int, _height: Double, _weight: Double) {
    var name = ""
    var age = 0
    var height = 0.0
    var weight = 0.0
    init {                  //생성시 프로퍼티 초기화 
        println("주생성자")
        name = _name
        age = _age
        height = _height
        weight = _weight
    }
    fun run() {
        println("뛴다.")
    }
}

또는

fun main() {
    val people1 = People("사람1", 10, 100.0, 30.0)
    println(people1.name) //주생성자
                          //사람1
    people1.run()   // 뛴다.
}
class People(val name: String,val age: Int, val height: Double,val weight: Double) {

    init {                  //생성시 프로퍼티 초기화
        println("주생성자")

    }
    fun run() {
        println("뛴다.")
    }
}

val 키워드를 붙여주면 생성과 동시에 초기화 작업을 할 수 있음 

 

부생성자

fun main() {
    val people1 = People("사람1", 10, 100.0, 30.0)
//    gender 없는 생성자
//    이름 : 사람1
//    나이 : 10
//    몸무게 : 100.0
//    키 : 30.0 
    val people2 = People("사람2", 20, 160.3, 56.7, "남자")
//    gender 있는 생성자
//    이름 : 사람2
//    나이 : 20
//    몸무게 : 160.3
//    키 : 56.7
//    성별: 남자 
}

class People {
    var name = ""
    var age = 0
    var weight = 0.0
    var height = 0.0
    var gender = ""

    constructor(_name: String, _age: Int, _weight: Double, _height: Double) {
        println("gender 없는 생성자")
        println("이름 : $_name")
        println("나이 : $_age")
        println("몸무게 : $_weight")
        println("키 : $_height")
    }

    constructor(_name: String, _age: Int, _weight: Double, _height: Double, _gender: String) {
        println("gender 있는 생성자")
        println("이름 : $_name")
        println("나이 : $_age")
        println("몸무게 : $_weight")
        println("키 : $_height")
        println("성별: $_gender")
    }
}

 

주생성자와 부생성자의 차이

  • 한 가지의 형태로 클래스를 실체화할때는 주 생성자를 활용할 수 있다.
  • 항상 <이름, 나이,몸무게,키>만 생성자에서 최초로 코드를 실행할 때는 주 생성자를 사용한다.
  • 여러 형태로 클래스를 실체화할때는 보조 생성자(부생성자)를 활용할 수 있다.
  • <이름, 나이,몸무게>  또는 < 이름, 나이,몸무게,키,성별> 처럼 여러개의 생성자를 통해 최초로 코드를 실행할 때는 부 생성자를 이용한다. 

만약, 100명의 사람의 인스턴스를 만든다고 했을 때, 절반의 사람이 1995년생 이라면? 

보조 생성자를 이용해서 코드량을 줄일 수도 있다. 

fun main() {
    val a = People("김",1990) //init 실행: 이름은 : 김 , 출생년도는 : 1990 입니다.
    val b = People("이",1980) //init 실행: 이름은 : 이 , 출생년도는 : 1980 입니다.
    val c = People("박",1995) //init 실행: 이름은 : 박 , 출생년도는 : 1995 입니다.
    val d = People("최") /*init 실행: 이름은 : 최 , 출생년도는 : 1995 입니다.
                                 보조 생성자 생성 :이름 : 최 , 출생년 : 1995      */

    val e = People("강") /*init 실행: 이름은 : 강 , 출생년도는 : 1995 입니다.
                                 보조 생성자 생성 :이름 : 강 , 출생년 : 1995      */

    val f = People("문") /*init 실행: 이름은 : 문 , 출생년도는 : 1995 입니다.
                                 보조 생성자 생성 :이름 : 문 , 출생년 : 1995      */
}
class People(val name: String, val birthYear: Int){
    init {
        print("init 실행: ")
        println("이름은 : $name , 출생년도는 : $birthYear 입니다.")
    }
    constructor(name: String) :this(name,birthYear = 1995){
        print("보조 생성자 생성 :")
        println("이름 : $name , 출생년 : $birthYear")
    }
}

 

728x90

github에 동료들과 같이 작업해서 프로젝트를 진행 하려면 각자 작업을 할 수 있는 공간을 만들기 위해 브랜치(Branch)를 사용한다.

- 브랜치(Branch)를 사용하게 되면 나뭇가지가 뻗어나오듯 기능에 맞게 나누어 작업할 수 있다.

 

작업 목적에 맞게 branch를 만들어서 관련된 작업을 하고 나중에 main 에 병합(merge) 한다. 

 

(Github 레퍼지토리가 있다는 가정하에, git clone 이후)

 

브랜치를 만드는 명령어 

git branch "브랜치명" ex) git branch login 

git branch 만 입력하면 작업 위치를 볼 수 있음 (*표시) 

git checkout or git switch 명령어로 작업공간 전환  

예시

 

한 번에 브랜치를 만들고 작업공간으로 전환하는 명령어 

git switch -c "브랜치명" or git checkout -b "브랜치명" 둘 중 하나 아무거나(switch는 최근에 나옴) 

 

예시

작업을 마치고 git add . & git commit -m "커밋메세지" 이후 git push origin "브랜치명" ex) git push origin login을 하고, github에 접속해서 pull request 를 해줌 Create pull request를 해주고, 리뷰를 받고나서 Merge pull request 누르고 Confirm merge를 누르면 작업이 합쳐짐 하지만, 충돌이 있을 경우, git pull origin main (dev 공간에서 병합을 한다면dev) 명령어로 작업을 당겨와서 수정 할 부분이 있으면 수정하고 테스트 해본 후에 다시 git push origin login 명령어로 github에 push해주고 이상이 없으면 병합을 해줌  

Merge pull request
Confirm merge

이후에는 git pull origin "브랜치명"(main or main에 병합하기전 브랜치 dev) -> git switch -c "브랜치명"(생성) -> git add . & git commit -m "커밋메세지"(작업완료) -> git pull origin dev(충돌해결,테스트 등) -> git push origin "브랜치명"(작업한 브랜치 푸쉬) 을 반복 

728x90

 

코루틴(coroutine)

  • 최적화된 비동기 함수를 사용한다.
  • 하드웨어 자원의 효율적인 할당을 가능하게 한다.
  • 안정적인 동시성, 비동기 프로그래밍을 가능하게 한다.
  • 코루틴 쓰레드보다 더욱 가볍게 사용할 수 있다.(쓰레드를 경량화 한것 쓰레드의 대체가 아님) 
  • 구글에서 적극 권장한다. 

 

  • 쓰레드(Thread)
    • 로직을 동시에 실행할 수 있도록 함로직을 동시에 실행할 수 있도록 함 
    • 쓰레드프로세스보다 더 작은 단위 (프로세스 안에서 더 작은 작업의 단위를 쓰레드 라고 부름)
    • 쓰레드는 생성되서 수행할때 각 독립된 메모리 영역인 STACK 을 가지고, 스택메모리의 일정 영역을 차지한다.
    • 프로그램은 하나의 메인 쓰레드(실행흐름)가 존재하는데, 하나의 메인 쓰레드는  fun main() (메인 함수)를 의미함
  • 프로세스(Process)
    • 프로그램이 메모리에 올라가서 실행될때 이를 프로세스 1개 라고 한다.

메모리 자료

코루틴을 사용하려면 외부종속성을 추가한다. 

build.gradle.kts(Module :app) 의 dependencies

fun main() {
    thread(start = true) {
        for(i in 1..10) {
            println("Thread1: 현재 숫자는 ${i}")
            runBlocking {
                launch {
                    delay(1000) //1초 딜레이
                }
            }
        }
    }
    thread(start = true) {
        for(i in 100..110) {
            println("Thread2: 현재 숫자는 ${i}")
            runBlocking {
                launch {
                    delay(1000) //1초 딜레이
                }
            }
        }
    }
}

두 개의 쓰레드가 각각 1초를 지연하고 println() 을 실행하는데, 1초 후에 어느 쓰레드가 먼저 자원을 할당하는지에 따라 먼저 실행되는 쓰레드가 달라진다. (경주마 1번 2번 이라고 생각하면됨)

 

코루틴은 일반적으로 launchasync 빌더를 가장 많이 사용하고  launch는 결과값이 없는 코루틴 빌더를 의미한다. 

 

lanuch

launch는 결과값이 없는 코루틴 빌더를 의미한다.

Job객체로 코루틴을 관리한다. (Job객체는 다양한 함수를 가지고 있음 join,cancel)

  • join: 현재의 코루틴이 종료되기를 기다린다.
  • cancel: 현재의 코루틴을 즉시 종료한다. 

async

async는 결과값이 있는 코루틴이다. 

 

코루틴은 스코프로 범위를 지정할 수 있음

  • GlobalScope: 앱이 실행된 이후에 계속 수행되어야할때 사용한다.
  • CoroutineScope: 필요할때만 생성하고 사용후에 정리가 필요하다. 

 

코루틴을 실행할 쓰레드를 Dispatcher로 지정할 수 있음

 

withContext

Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns the result. The resulting context for the block is derived by merging the current coroutineContext with the specified context using coroutineConte

kotlinlang.org

 

fun main() {
    println("메인쓰레드 시작")
    var job = CoroutineScope(Dispatchers.Default).launch {
        var fileDownloadCoroutine = async(Dispatchers.IO) {
            delay(10000)  
            "파일 다운로드 완료"
        }
        var databaseConnectCoroutine = async(Dispatchers.IO) {
            delay(5000)  
            "데이터베이스 연결 완료"
        }
        println("${fileDownloadCoroutine.await()}")
        println("${databaseConnectCoroutine.await()}")
    }
    runBlocking {
        job.join()
    }
    println("메인쓰레드 종료")
    job.cancel()
}

사용 후 정리가 필요한 CoroutineScope로 Dispatchers.Default로 지정해서 결과 값이 없는 launch로 열어주고 async로(Dispatchers.IO)가 10초 기다림 (10초 걸리는 파일 다운로드을 받아줌) 데이터베이스도 마찬가지 작업을 기다리고 작업이 끝나면 job.cancel()로 코루틴 종료 (runBlocking은 jvm 환경에서 코루틴을 사용해보기 위함 없으면 10초 되기전에 프로그램종료)

 

쓰레드 와 코루틴은 각자의 방법으로 동시성(비동기)을 보장하는 기술이고, 코루틴은 쓰레드를 대체하는 기술이아닌 쓰레드를 경량화한 기술임 

 

728x90

상속(Inheritance)이란?

클래스 간의 관계를 구축하는 방법인데, 부모 클래스와 자식 클래스의 관계가 기본이고 자식 클래스는 부모 클래스의 속성과 동작을 포함한다. 부모 클래스 상위 클래스, 기반 클래스라고도 하고 자식 클래스 하위 클래스, 파생 클래스라고도 한다.

 

상속을 하는 이유 

  • 클래스(설계도) 간의 관계를 더욱 끈끈하게 만들 수 있다.
  • 공통적인 요소들이 있다면 부모/자식 클래스를 구분해서 상속관계를 만들 수 있다. 
  • 코틀린은 다른 언어들과 달리 생략된 final 키워드로 기본적으로 상속을 막아둠
  • 무분별한 상속으로 예상치 못한 흐름을 방지하기 위함
  • 코틀린은 open 키워드를 활용해서 상속 관계를 만들 수 있음 
fun main() {
    val animal = Animal("동물울음소리")
    val dog = Dog("멍멍")
    val cat = Cat("야옹")
    val cow = Cow("음메")
    animal.cry() //동물울음소리~~
    dog.cry()   //멍멍~~
    cat.cry()   //야옹~~
    cow.cry()   //음메~~
}
open class Animal(private val crying:String){
     fun cry() {
        println("$crying~~")
    }
}
class Dog(crying:String) : Animal(crying){}
class Cat(crying:String):Animal(crying){}
class Cow(crying: String):Animal(crying){}

 

부모클래스 Animal 앞에 open 키워드로 상속을 가능하게 만듬 

상속을 하면, 자식클래스(Dog, Cat, Cow class)의 인스턴스도 부모클래스(Animal class) 에 있는 cry() 를 사용할 수 있음(private 접근제한자가 붙으면 사용불가)

 

상속의 이점

  • 다형성을 구현할 수 있음
  • 클래스의 내용을 변경해야하는경우 부모 클래스만 변경하는것으로 공수를 줄일 수 있음

Overriding(오버라이딩)

  • 상속받은 부모 클래스의 정보(프로퍼티)나 행위(메소드)를 재설계할 수 있다. 
  • 주로 부모 클래스의 행위(메소드)를 재설계한다.
  • 이러한 행위를 오버라이딩 (Overriding)이라고 한다. 
fun main() {
    val animal = Animal("동물울음소리")
    val dog = Dog("멍멍",10)
    val cat = Cat("야옹")
    val cow = Cow("음메")
    animal.cry() //동물울음소리~~
    dog.cry()   //멍멍!!!! 짖는 강아지의 나이는 10 살
    cat.cry()   //야옹~~
    cow.cry()   //음메~~
}
open class Animal(private val crying:String){
        open fun cry() {
        println("$crying~~")
    }
}
class Dog(private val crying:String,age:Int) : Animal(crying){
    var age = 0
    init {
        this.age = age
    }
    override fun cry() {
        println("$crying!!!! 짖는 강아지의 나이는 $age 살")
    }
}
class Cat(crying:String):Animal(crying){}
class Cow(crying: String):Animal(crying){}

 

그냥 상속했을 때와 비교하면, fun앞에 open 키워드를 붙여주고 Dog class에서 변수를 추가 해주고, cry() 를 overriding 해주면, "멍멍~~"이 아닌, "멍멍!!!! 짖는 강아지의 나이는 10 살" 이 출력된다. 

 

Overloading(오버로딩)

  • 매개변수의 갯수를 다르게하면 동일한 이름으로 메소드를 만들 수 있다.
  • 매개변수의 자료형을 다르게하면 동일한 이름으로 메소드를 만들 수 있다.
  • 반환자료형(반환형)은 오버로딩에 영향을 주지 않음
fun main() {
    val dog = Dog()
    dog.info(true,3) //성별: 수컷, 나이: 3
    dog.info(1,"왈왈") //나이는: 1살 울음소리는 왈왈
}

class Dog {
    fun info(gender: Boolean, age: Int) {
        if (gender) println("성별: 수컷, 나이: $age") else println("성별: 암컷, 나이: $age")
    }
    fun info(age: Int, cry: String) {
        println("나이는: ${age}살 울음소리는 $cry")
    }
}

 

오버로딩은 메소드의 이름은 같은데 매개변수의 수나 자료형을 다르게해서 다른 결과값을 얻기위해 사용됨 

Dog class에 info 메소드는 두 개 이지만, 하나는 boolean 과 int 를 받고, 하나는 int 와 string타입을 받음, 

매개변수에 따라 출력되는 값이 차이가 난다.  

 

Interface(인터페이스)

공통적으로 필요한 기능을 외부에서 추가해줄 수 있다. 

fun main() {
    var dog = Dog("강아지")
    var cat = Cat("고양이")
    var cow = Cow("소")
    var bird = Bird("새")
    dog.run() // 강아지는 뛰는 동물
    cat.run() // 고양이는 뛰는 동물
    cow.run() // 소는 뛰는 동물
    bird.fly() // 새는 나는 동물~
}

open class Animal(private val type: String) {
    fun run() {
        println("${type}는 뛰는 동물")
    }
}

class Dog(type: String) : Animal(type) {}
class Cat(type: String) : Animal(type) {}
class Cow(type: String) : Animal(type) {}
class Bird(private val type:String):Animal(type),FlyAnimal{
    override fun fly() {
        println("${type}는 나는 동물~")
    }
}
interface FlyAnimal{
    fun fly()
}

 

새는 동물이라는 것은 개, 고양이, 소 랑 같지만 새는 뛰지않고 날아다님 

그래서 별도의 기능을 추가하기 위해 interface를 사용한다. (interface안에는 추상메소드로 구현부는 만들어 놓지 않음)

 

 

 

 

728x90

코틀린에서의 접근 제한자

    • public : 모든 곳에서 접근 가능, 명시하지 않으면 기본적으로 public (어디서나 접근가능,보통 명시하지 않음) 
    • protected : 선언된 클래스 또는 하위 클래스에서만 접근 가능 (기본적으로 private지만 상속을 받은경우 타 모듈에서 접근가능)
    • internal : 같은 모듈에서만 접근 가능
    • private : 선언된 클래스 내에서만 접근 가능(동일한 클래스 내부에서만 접근가능)

프로젝트란 최상단의 개념이고 <모듈> <패키지> <클래스>를 포함한다.

Project -> app -> pakage -> class

 

모듈이란 프로젝트 아래의 개념이고 <패키지><클래스>를 포함한다.

모듈 : app -> pagakge -> class


패키지란 모듈 아래의 개념이고 <클래스>를 포함한다

pakage : pakage -> class

 

접근제한자는 변수, 함수, 클래스, 생성자, 프로퍼티등에 쓰임 

public class Ex{ //보통 public은 따로 명시하지 않음
    protected var value = "value"
    private fun publicFunction(){}
    internal constructor() 
}

 

 

 

내부클래스서만 사용하던걸 특정 권한이 있는 클래스에게만 접근할 수 있게 해준다.

여기서 특정 권한을 가지려면 protected가 존재하는 부모클래스를 상속받는다. 

open class Parent{
    protected var name = "이름"
}
class Child: Parent() {
    val childInstance = Child()
    init {
        childInstance.name
    }
}

//상속관계에서는 protected에 접근가능 

 

 

접근제한자는 적절하게 사용해야함

  • 접근권한을 통해 데이터에 무분별한 접근을 막을 수 있다. ex) private을 이용해서 보호해야할 정보를 감춤
  • 클래스들간에 접근하면 안되는 상황을 구분하기 때문에 향후에 유지보수 하기에 용이해요

 

 

728x90
  • 동기(synchronous) 처리
    • 직렬적으로 태스크를 수행하는 방식이다.
    • 요청 보내고 결과값을 받을 때까지 작업을 멈춤 한 가지씩 작업을 처리함
    • 실제로 cpu가 느려지는 것은 아니지만 시스템의 전체적인 효율이 저하된다고 할 수 있다.
  • 비동기(asynchronous) 처리
    • 병렬적으로 태스크를 수행하는 방식이다.
    • 요청 보내고 결과값을 받을 때까지 멈추지 않고 또 다른 일을 수행함
    • 다양한 일을 한 번에 수행함

예를 들자면, 게임을 하면서 메모를 하거나 전화를 대화를 하는 등의 한 가지 작업중에 다른 작업을 하지 못하면 동기,

노래를 들으면서 밥을 먹거나 씻는행위를 할 수 있으면 비동기 라고 할 수 있다. 

 

fun main() {
    // 시작
    
    //고용량 동영상 다운로드
    
    //다른작업
}

이런 작업을 할 때, 동기적으로 처리한다고 하면, 영상이 다운로드가 될때 까지 다른작업은 할 수 없다. 

 

그래서 ThreadCoroutine을 사용한 비동기 처리를 하는 것.

다운로드 요청을 하고 완료될때 까지 기다리는것이 아니라, 요청을 하고 다른 작업을 하다가 완료가 되면 다음 작업을 수행 할 수 있게한다. 

 

 

 

 

728x90

코딩 컨벤션 이란 ? 

- 네이밍 규칙, 네이밍 컨벤션, 코딩 컨벤션, 코딩 스타일 등 부르는 이름은 다양하나 이들은 모두 한 가지를 의미

- 팀(회사)에서 협업이 쉽게 코드의 작성 스타일을 정한 것

- 다른 사람들도 이해하기 쉽게 코드를 작성하는 규칙 

- 프로그래밍 언어마다 상이 

 

코딩 컨벤션 규칙을 지키는 이유는 ?

- 코드의 가독성과 유지보수의 용이성, 그로인한 효율적인 협업을 위함

 

자주 사용하는 컨벤션 3 가지 

 

- 카멜케이스 (camelCase) : 중간 글자들은 대문자로 시작하지만 첫 글자가 소문자인 경우에는 낙타와 모양이 비슷하다 하여 카멜 케이스라고 한다.

 

EX) 주로 변수나 함수명으로 사용 

var isTrue = true  // 카멜 케이스 변수
fun trueAndFalse(){} // 카멜 케이스 함수

 

 

 

 

- 스네이크케이스 (snake_case) : 언더바(_) 가 들어 있는 표현 방식을 뱀처럼 생겼다고 하여 스네이크 케이스라고 한다.

EX) 상수(Constant) 이름으로 많이 사용

companion object {
    private val REQUEST_CODE = 100 
    private val RESULT_CODE = 200 
}

 

 

 

 

- 파스칼케이스 (PascalCase)  : 첫 글자와 중간 글자들이 대문자인 경우 파스칼 언어의 표기법과 유사하다고 하여 파스칼 케이스라고 한다.

EX) 주로 Class 지정시 사용 

class TempClass{
    var isCamel : String = "카멜케이스"
    var is_snake : String = "스네이크 케이스"
}

 

 

 

 

패키지 이름 짓는 규칙 

패키지 이름은 모두 소문자이며 연속 단어는 밑줄 없이 연결됨 

// Okay
package com.example.deepspace
// WRONG!
package com.example.deepSpace
// WRONG!
package com.example.deep_space

 

참조자료 https://developer.android.com/kotlin/style-guide?hl=ko

728x90

문제 설명

어느 학교에 페인트가 칠해진 길이가 n미터인 벽이 있습니다. 벽에 동아리 · 학회 홍보나 회사 채용 공고 포스터 등을 게시하기 위해 테이프로 붙였다가 철거할 때 떼는 일이 많고 그 과정에서 페인트가 벗겨지곤 합니다. 페인트가 벗겨진 벽이 보기 흉해져 학교는 벽에 페인트를 덧칠하기로 했습니다.

넓은 벽 전체에 페인트를 새로 칠하는 대신, 구역을 나누어 일부만 페인트를 새로 칠 함으로써 예산을 아끼려 합니다. 이를 위해 벽을 1미터 길이의 구역 n개로 나누고, 각 구역에 왼쪽부터 순서대로 1번부터 n번까지 번호를 붙였습니다. 그리고 페인트를 다시 칠해야 할 구역들을 정했습니다.

벽에 페인트를 칠하는 롤러의 길이는 m미터이고, 롤러로 벽에 페인트를 한 번 칠하는 규칙은 다음과 같습니다.

  • 롤러가 벽에서 벗어나면 안 됩니다.
  • 구역의 일부분만 포함되도록 칠하면 안 됩니다.

즉, 롤러의 좌우측 끝을 구역의 경계선 혹은 벽의 좌우측 끝부분에 맞춘 후 롤러를 위아래로 움직이면서 벽을 칠합니다. 현재 페인트를 칠하는 구역들을 완전히 칠한 후 벽에서 롤러를 떼며, 이를 벽을 한 번 칠했다고 정의합니다.

한 구역에 페인트를 여러 번 칠해도 되고 다시 칠해야 할 구역이 아닌 곳에 페인트를 칠해도 되지만 다시 칠하기로 정한 구역은 적어도 한 번 페인트칠을 해야 합니다. 예산을 아끼기 위해 다시 칠할 구역을 정했듯 마찬가지로 롤러로 페인트칠을 하는 횟수를 최소화하려고 합니다.

정수 n, m과 다시 페인트를 칠하기로 정한 구역들의 번호가 담긴 정수 배열 section이 매개변수로 주어질 때 롤러로 페인트칠해야 하는 최소 횟수를 return 하는 solution 함수를 작성해 주세요.


제한사항
  • 1 ≤ m  n ≤ 100,000
  • 1 ≤ section의 길이 ≤ n
    • 1 ≤ section의 원소 ≤ n
    • section의 원소는 페인트를 다시 칠해야 하는 구역의 번호입니다.
    • section에서 같은 원소가 두 번 이상 나타나지 않습니다.
    • section의 원소는 오름차순으로 정렬되어 있습니다.
입출력 예
입출력 예 설명

입출력 예 #1

  • 예제 1번은 2, 3, 6번 영역에 페인트를 다시 칠해야 합니다. 롤러의 길이가 4미터이므로 한 번의 페인트칠에 연속된 4개의 구역을 칠할 수 있습니다. 처음에 3, 4, 5, 6번 영역에 페인트칠을 하면 칠해야 할 곳으로 2번 구역만 남고 1, 2, 3, 4번 구역에 페인트칠을 하면 2번 만에 다시 칠해야 할 곳에 모두 페인트칠을 할 수 있습니다.2번보다 적은 횟수로 2, 3, 6번 영역에 페인트를 덧칠하는 방법은 없습니다. 따라서 최소 횟수인 2를 return 합니다.

입출력 예 #2

  • 예제 2번은 1, 3번 영역에 페인트를 다시 칠해야 합니다. 롤러의 길이가 4미터이므로 한 번의 페인트칠에 연속된 4개의 구역을 칠할 수 있고 1, 2, 3, 4번 영역에 페인트칠을 하면 한 번에 1, 3번 영역을 모두 칠할 수 있습니다.따라서 최소 횟수인 1을 return 합니다.

입출력 예 #3

  • 예제 3번은 모든 구역에 페인트칠을 해야 합니다. 롤러의 길이가 1미터이므로 한 번에 한 구역밖에 칠할 수 없습니다. 구역이 4개이므로 각 구역을 한 번씩만 칠하는 4번이 최소 횟수가 됩니다.따라서 4를 return 합니다.

문제는 이런 문제인데, 

class Solution {
    fun solution(n: Int, m: Int, section: IntArray): Int {
        var answer: Int = 0
        var count = 0
        for(i in section.first()..section.last() step m){
            if(i <= section.last()){
                count++
            }
        }
        answer = count
        return answer
    }
}

나의 풀이로는 정확성이 54.0 이 나왔다.  

예외가 뭐가 있을까 생각을 해봐도 테스트 케이스가 50개 였기 때문에 알기 쉽지 않아서 풀이를 찾아본 결과 fold를 이용해서 푸는 방법이 있었고,

 

fold는 이런 함수다. 

출처 -&nbsp;https://gold.gitbook.io/kotlin/collections/aggregate-operations/fold

 

초기값을 0 으로 셋팅하고 acc에 연산 값을 누적 1 + 2 + 3 + 4 + 5 // 15 

list.fold(1, {acc, next -> acc * next})) // 120 

 

fold를 사용한 풀이 

class Solution {
    fun solution(n: Int, m: Int, section: IntArray): Int {
        var answer: Int = 0
        var endPoint = 0
        answer = section.fold(0){count,area->
            if(endPoint<area){//endPoint 가 area보다 작으면 페인트 칠 할 공간이 남았다는 것
                endPoint = area + m - 1 // 2부터 4(m)만큼 페인트칠 하고 끝 지점은 5이기 때문에 -1
                count + 1
            }else{
                count
            }
        }
        return answer
    }
}

 

fold 는 왼쪽에서 오른쪽으로 연산을 하고 오른쪽에서 왼쪽으로 연산하려면 foldRight()를 사용하면 된다.

 

우선순위가 있는, 순서가 있는 연산에 대해서는 두개의 특징을 잘 알고 사용하는 것이 좋다.

우선순위가 끝쪽이 더 높다 하는 경우에는 foldRight 를, 우선순위가 왼쪽에 있는 값부터 해야한다 하는 경우 일반적인 fold 를 사용하면 좋다.

 

번외 - reduce , reduceRight 

fold 와 비슷 하지만, reduce 는 초기값을 받지 않고 첫번째 인덱스부터 시작한다.  

 -> 초기값을 정해서 누적 연산을 누적하는 것과 초기값 지정없이 첫 번째 인덱스를 초기값으로 지정해 연산을 누적하는

차이  

fun main() {
    val list = listOf(1,2,3,4,5)
    var result = 0
    result = list.fold(0){total,next->
        total + next * 2 // result -> 초기값 1 결과값 30 -> 0 + (1*2) + (2*2) + (3*2) + (4*2) + (5*2)
    }
    result = list.reduce{total , next->
        total + next * 2 // result -> 초기값은 첫번째 인덱스(1) 결과값 29 -> 1 + (2*2) + (3*2) + (4*2) + (5*2)  
    }
}

 

예시를 보면 fold 는 1부터(첫번째 인덱스) 2를 곱한 결과를 total 에 누적시켜 result 값이 30 이고,

reduce는 1을(첫번째 인덱스) 초기값으로 가져 2(두번째 인덱스) 부터 2를 곱해 total 에 누적시킨 결과 result 는 29가 나옴

 

 

 

 

문제를 풀다보면 풀이 방식이 고정되고 그 방식을 벗어나 다른 풀이 방식으로 접근하기가 쉽지 않은 것 같다. 그래서 알고리즘을 풀면서 다른 사람들의 풀이도 보게 되는데 항상 부족함을 느낀다.   

 

 

728x90

git 을 사용하기 전 기본적인 Linux 기본 명령어

pwd : 현재 위치

ls : 현재 위치 모든 리스트 // ls -a : ls와 동일, 숨겨진 폴더나 파일까지 보여줌  

mkdir "파일명" : 폴더생성 

touch "파일명" : 파일생성

cd : 위치 이동 // cd .. : 상위폴더로 이동

cd 예

GitHub와 연동하기 위한 개인정보 설정 

 

git config --global user.name "유저 이름" 

git config --global user.email "유저 이메일" //깃허브 이메일 

git config --list // 설정한 정보 확인

 

 

Github에서 Repository 생성 후 연동시작 

 

작업이력을 관리할 작업공간으로 이동 

cd 명령어로 작업공간으로 이동

git init 명령어로 시작 -> 프로젝트 생성시 한 번만 하면 됨 여러번 할 필요없음 // 여러번 해도 문제는 없음

git add "파일명" 으로 저장할 파일 지정 후 git commit -m "메세지" 로 실제 저장 또는 

git add .  명령어로 한번에 모든 수정된 파일이나 신규 변경이력을 지정하고 git commit -m "메세지" // 메세지는 변경이력 을 적는게 좋음 -> text1수정 text2 수정// 변경 지점을 찾기 쉽도록  

git status 명령어로 상태 확인 

 

이후에 Github에서 생성한 Repository에서 해당 부분을 git bash에 복사 붙여넣기(Shift insert) 하면 저장소에 올라가게된다. 

윗부분은 git init add commit 전 과정을 한번에 설정하는 것

연동 끝 이후에는 변경 이력이 있을때 git add . 와 git commit -m "메세지" 를 계속 사용해서 저장 

 

추가로

git clone "Repository주소"  : Github 에 작업중인 Repository가 있으면 git clone "Repository주소" 이후 add . commi -m 으로 관리 

git pull  : 협업중인 경우 다른 사람들의 변경사항을 받아서 작업 

git push origin "master or main"(브랜치명) : 작업후 브랜치에 푸시 ... origin "브랜치명" 부분은 생략 가능 

+ Recent posts