ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [코틀린] 내부 반복(map, filter, groupBy, reduce)
    코틀린 2022. 3. 19. 17:17

    1. 내부 반복

    코틀린에서는 여러가지 내부 반복자를 제공한다. 반복이 필요한 경우에 명령형 스타일의 외부 반복자를 사용할 수도 있지만 내부반복자와 람다를 이용하면 간단하게 구현할 수도 있다. 아래는 외부 반복자를 이용하여 3의 배수만을 구한 것이다.

    val multipleOfThree = mutableListOf<Int>()
    for(x in l) {
        if(x % 3 == 0) {
            multipleOfThree.add(x)
        }
    }
    
    println(multipleOfThree) [3, 6, 9]

    아래는 내부 반복자인 filter를 이용해 구한 것이다.

    val multipleOfThreeWithMap = l.filter { it % 3 == 0 }
    println(multipleOfThreeWithMap) //[3, 6, 9]

    코드가 간결해졌고 이해하기 쉽다. 아래는 코틀린 공식 문서에서 찾은 filter의 API 이다. 어떤 타입의 원소를 받아 Boolean을 리턴하는 람다를 제공해주면 된다.

     

    2. 다양한 내부 반복자

    어떤 리스트의 원소들을 변형시킨 새로운 리스트를 얻고 싶다면 map을 사용할 수도 있다. 아래 정의에서 볼 수 있듯 T 타입의 원소를 R타입으로 변경시키는 람다 파라미터 transform을 받으며 R타입의 리스트가 반환된다.

    val setOfInteger = setOf(1, 2, 3, 4, 5)
    val twice = setOfInteger.map { "$it * 2 will be ${it * 2}" }
    println(twice) // 출력
    // [1 * 2 will be 2, 2 * 2 will be 4, 3 * 2 will be 6, 4 * 2 will be 8, 5 * 2 will be 10]

     

    groupBy는 리스트의 원소를 받아 Key로 변형시키는 람다를 받아 그 결과를 키, 원래 원소들을 리스트로 하는 맵을 리턴한다.

    val l = 1..10
    val groupByResult = l.groupBy { it % 3 }
    println(groupByResult) // {1=[1, 4, 7, 10], 2=[2, 5, 8], 0=[3, 6, 9]}

     예를들어 위의 예제에서는 1부터 10까지의 정수를 담은 리스트에서 각 원소를 3으로 나눈 나머지를 키, 그 원소를 담은 리스트를 값으로 하는 맵을 만들었다.

    val tuples = listOf(
        ("park" to "ji sung"),
        ("son" to "heung min"),
        ("lee" to "chung young"),
        ("park" to "joo young"),
        ("lee" to "seung woo")
    )
    
    val mapFromListOfTuples = tuples.groupBy({it.first}, {it.second})
    println(mapFromListOfTuples) // {park=[ji sung, joo young], son=[heung min], lee=[chung young, seung woo]}

    위의 예제는 튜플로 이루어진 리스트를 이용하여 사람의 성을 키, 이름들의 리스트를 값으로 하는 맵으로 변형시켰다.

     

    reduce는 각 원소들을 연산하여 전체 연산의 결과를 반환한다. 아래 예시는 리스트의 합을 reduce로 구하는 것을 보여준다.

    val l = 1..10
    val sumOfL = l.reduce { acc, i -> acc + i }
    println(sumOfL) // 55

     

    3. 지연 연산

    내부 반복자를 여러개를 이어 붙여서 수행하면 중간 연산결과가 계속 계산된다. 아래 예시에서 원하는것은 1..5 의 리스트의 원소에 1을 더한 뒤 2배를 한 리스트의 첫번째 원소를 구하면 된다. 실제 연산은 map의 중간 결과를 다 만들면서 진행하기 때문에 아래 같은 현상이 발생한다.

    fun incremental(x: Int): Int {
        println("Incremental")
        return x + 1
    }
    
    fun double(x: Int): Int {
        println("Double")
        return x * 2
    }
    
    (1..5).map(::incremental)
        .map(::double).first()
    // 출력 결과
    Incremental
    Incremental
    Incremental
    Incremental
    Incremental
    Double
    Double
    Double
    Double
    Double
    // 결과 4

    시퀀스는 연산을 필요한 시점까지 최대한 미루는데, 컬렉션들은 asSequence 메소드로 시퀀스를 얻을 수 있다. 아래는sequence로 바꿔 위와 같은 연산을 한 결과이다.

    println("sequence")
    (1..5).asSequence().map(::incremental)
        .map(::double).first()
    //
    sequence
    Incremental
    Double
    4

    꼭 필요한 연산만 1번씩 일어났다. 컬렉션의 크기가 작을경우는 큰 문제가 되지 않지만 컬렉션의 크기가 클 경우 asSequence 사용을 고려해봐야한다.

     

    4. 시퀀스

    시퀀스는 generateSequence를 이용하거나 yield를 이용하여 만들 수 있다. 아래는 generateSequence를 이용해서 10부터 시작하고, 각 원소가 1씩 증가하는 시퀀스를 선언하는 예제다.

    val seq = generateSequence(10) {it + 1}
    println(seq.take(10).toList()) // [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
    println(seq.take(2).contains(13)) // false
    println(seq.take(5).contains(13)) // true

    yield를 사용하는 시퀀스는 아래처럼 선언하면 된다.

    val multipleOfSix = sequence {
        var start = 6
        while (true) {
            yield(start)
            start += 6
        }
    }
    
    multipleOfSix.take(10).toList() // [6, 12, 18, 24, 30, 36, 42, 48, 54, 60]

    위의 6의 배수를 얻을 수 있는 시퀀스는 yield를 이용해서 구현되었다. yield는 원소를 반환하고 계속 루프를 돈다고 생각하면 된다. 위에서는 take의 인자로 10을 주었기 때문에 10개의 원소를 가진 리스트가 반환되었다.

    '코틀린' 카테고리의 다른 글

    [코틀린] 확장함수와 확장속성  (0) 2022.03.20
    [코틀린] 연산자 오버로딩  (0) 2022.03.20
    [코틀린] 람다  (0) 2022.03.19
    [코틀린] 델리게이션  (0) 2022.03.16
    [코틀린] 클래스와 상속  (0) 2022.03.13

    댓글

Designed by Tistory.