본문 바로가기

Kotlin

[Kotlin] 4.람다 In Collection(Sequence)

# 람다 활용법 In Collection(Sequence)

  • 컬렉션 함수를 연달아 사용하면 연산 결과마다 새로운 컬렉션들이 생겨나게 되는데, 원소의 갯수가 많은 경우 효율이 떨어 질수 가 있다.
  • 이 경우 Sequence를 이용해서 연산 조건을 다~ 적용하고 나~중에, 결과 list가 필요한 시점에 list를 한번만 생성해서 얻을 수 있다 (실제로 연산/계산은 Sequence를 list로 변환 시점에 이루어 지기때문에 lazy, 지연계산이라 표현한다)
  • Sequence는 iterator를 반환하는 함수 1개만 있는 interface이다.
  • 원소 갯수가 많은 Collection에서는 Sequence를 사용하는 것을 기본으로 하자

 

Collection.asSequence()를 통해 Collection을 ->Sequence로 변환하는 것을 볼 수 있다.

[Collection.kt]

/**
 * Creates a [Sequence] instance that wraps the original collection returning its elements when being iterated.
 * 
 * @sample samples.collections.Sequences.Building.sequenceFromCollection
 */
public fun <T> Iterable<T>.asSequence(): Sequence<T> {
    return Sequence { this.iterator() }
}

 

iterator() 메소드 1개만 포함하는 interface임을 확인

public interface Sequence<out T> {
    /**
     * Returns an [Iterator] that returns the values from the sequence.
     *
     * Throws an exception if the sequence is constrained to be iterated once and `iterator` is invoked the second time.
     */
    public operator fun iterator(): Iterator<T>
}

 

예제) 4학년 이상인 student의 name  list 구하기  

아래 코드처러 사용하면 filter 에서 1번 map 에서 1번의 list 생성이 이루 어진다

원소 갯수가 많은 Collection에서는 성능 고려를 하지 않을 수 없다

    val students = listOf(Student("Tina", 4), Student("Scott", 1), Student("July", 4))
    println(students.filter { it.grade >= 4 }.map { it.name }) //[Tina, July]

 

예제) 4학년 이상인 student의 name  list 구하기 using Sequence

asSequence() : Collection ->Sequence 변경 

filter/map : 아래에서의 filter,map은 Sequence의 확장 함수로, 리턴 형태로 Sequence를 반환한다.

toList : Sequence -> Collection으로 변경

(Colleciton API를 사용하려면 변경해야 하지만, 단순히 원소 나열이 목표라면 Sequence 그대로 iterator 사용해도 된다)

students.asSequence().filter { it.grade >= 4 }.map { it.name }.toList()

 

# Sequence 중간 연산/최종 연산

  • 위 예제에서의 Sequence filter,map은 리턴 값으로 다른Sequence를 반환한다. 이를 중간 연산 이라고 하며, 'toList()'처럼 실제 계산을 통해 Colleftion을 얻는 것을 최종 연산 이라고 한다. (중간 연산은 항상 lazy, 지연계산)

 

중간 연산만 있는 경우

val students = listOf(Student("Tina", 4), Student("Scott", 1), Student("July", 4))
    students.asSequence()
        .filter {print("Sequence filter"); it.grade >= 4}
        .map { print("Sequence map");it.name}

위 예제를 실행 시켜보면, 아무 메시지도 print 되지 않는다.

Sequence의 중간 연산은, 최종 연산이 있기 전까지 호출 되지 않는다는 것을 볼 수 있다. 

 

최종 연산까지 있는 경우 (.toList)

    val students = listOf(Student("Tina", 4), Student("Scott", 1), Student("July", 4))
    students.asSequence()
        .filter {print("Sequence filter"); it.grade >= 4}
        .map {print("Sequence map");it.name}
        .toList()
        
        [결과]
        Sequence filter  //Tina
        Sequence map     //Tina
        Sequence filter  //Scott
        Sequence filter  //July
        Sequence map     //July

최종 연산을 호출해보면 연기 시켜놨던 모든 계산이 수행됨을 볼 수 있다. 

 

또 확인할 수 있는 점은 Collection과 Sequence는 연산 수행 순서가 다르다는 점이다.

만약 위 예제가 Collection으로 구현됐다면, 결과 출력 순서는 아래와 같이 순서가 달랐을 것이다

    val students = listOf(Student("Tina", 4), Student("Scott", 1), Student("July", 4))
    students
        .filter {print("Collection filter"); it.grade >= 4}
        .map {print("Collection map");it.name}
            
       [결과]
        Collection filter  //Tina
        Collection filter  //Scott
        Collection filter  //July
        Collection map     //Tina
        Collection map     //July

Collection은 filter에 대한 결과를 얻고, 그 결과에 대해 다시 map을 수행 하는 방식이라면,

Sequence는 각 원소에 대해 filter->map 연산이 순차적으로 수행된다. 

 

그래서 Sequence 사용의 경우 연산의 순서를 잘 배치 하면 수행 성능을 개선 할 수가 있는데

예제를 통해 확인해 봅시다.

 

예제) 이름이 'J'로 시작하는 Student name  list 구하기  

******* map  -> filter 순 *******

    val students = listOf(Student("John", 4), Student("Scott", 1), Student("July", 4))
    students.asSequence()
        .map { println("Sequence map"); it.name }
        .filter { println("Sequence filter"); it.startsWith("J") }.toList()
        
        [Print 결과] //6번 연산 수행
        Sequence map
        Sequence filter
        Sequence map
        Sequence filter
        Sequence map
        Sequence filter
        
        [최종 연산 결과] = [John, July]

******* filter- > map  순 *******

    students.asSequence()
        .filter {  println("Sequence filter"); it.name.startsWith("J") }
        .map {  println("Sequence map"); it.name }.toList()
                 
        [Print 결과] //5번 연산 수행
        Sequence filter
        Sequence map
        Sequence filter
        Sequence filter
        Sequence map
        
        [최종 연산 결과] = [John, July]

*map  -> filter 순  : 6번 연산 수행 

*filter -> map  : 5번 연산 수행 

 

이렇게 Sequence 연산 순서에 따라 최종 연산 결과가 같더라도

변환 수행 횟수가 달라지므로

Sequence 연쇄 연산시 순서 배치를 고려하는게 좋겠습니다


 

출처 : Kotlin In Action - 에이콘 출판사

(위 도서를 학습하고 개인 학습용으로 정리한 내용입니다)

 

잘못된 내용이나 궁금한 내용 있으시면 댓글 달아주세요

좋은 하루 되세요!

'Kotlin' 카테고리의 다른 글

[Kotlin] 4. 람다 - with/apply  (1) 2021.12.05
[Kotlin] 4.람다 - 함수형 인터페이스(SAM 인터페이스)  (0) 2021.12.04
[Kotlin] 4.람다 In Collection  (0) 2021.11.16
[Kotlin] 4.람다 - 기본  (0) 2021.11.15
[Kotlin] 3. Object  (0) 2021.11.15