Kotlin의 Iterable과 Sequence는 정확히 동일하게 보입니다. 두 가지 유형이 필요한 이유는 무엇입니까?


88

이 두 인터페이스 모두 하나의 메서드 만 정의합니다.

public operator fun iterator(): Iterator<T>

문서에 따르면 Sequence게으르다는 의미입니다. 그러나 Iterable게으르지 Collection않습니까 (으로 뒷받침되지 않는 한 )?

답변:


138

의미의 차이점 거짓말하고 대한 다음 stdlib 확장 기능의 구현 Iterable<T>Sequence<T>.

  • 의 경우 Sequence<T>확장 기능은 Java Streams 중간 작업 과 유사하게 가능한 경우 느리게 수행 됩니다. 예를 들어, Sequence<T>.map { ... }다른 Sequence<R>항목을 반환 하고 또는 같은 터미널 작업이 수행 될 때까지 항목을 실제로 처리하지 않습니다.toListfold 이 호출 .

    이 코드를 고려하십시오.

    val seq = sequenceOf(1, 2)
    val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
    print("before sum ")
    val sum = seqMapped.sum() // terminal
    

    다음을 인쇄합니다.

    before sum 1 2
    

    Sequence<T>터미널 에서 수행되는 작업을 줄이고 싶을 때 지연 사용 및 효율적인 파이프 라이닝을위한 것입니다.Java Streams와 마찬가지로 작업 작업을 최대한 . 그러나 게으름으로 인해 약간의 오버 헤드가 발생하여 작은 컬렉션의 일반적인 단순 변환에는 바람직하지 않으며 성능이 떨어집니다.

    일반적으로 필요한시기를 결정하는 좋은 방법이 없으므로 Kotlin에서는 stdlib 게으름이 명시 적으로 만들어 지고 기본적 Sequence<T>으로 모든에서 사용하지 않도록 인터페이스로 추출됩니다 Iterable.

  • 들어 Iterable<T>반대와 확장 기능, 중간 동작의 의미가 열심히 일을, 바로 항목을 처리하고 다른 반환 Iterable. 예를 들어, 매핑 결과와 함께 Iterable<T>.map { ... }a List<R>를 반환 합니다.

    Iterable에 해당하는 코드 :

    val lst = listOf(1, 2)
    val lstMapped: List<Int> = lst.map { print("$it "); it * it }
    print("before sum ")
    val sum = lstMapped.sum()
    

    다음과 같이 출력됩니다.

    1 2 before sum
    

    위에서 언급했듯이 Iterable<T>기본적으로 지연되지 않으며이 솔루션은 잘 보여줍니다. 대부분의 경우 참조 지역성 이 좋으 므로 CPU 캐시, 예측, 프리 페치 등을 활용하여 컬렉션을 여러 번 복사해도 여전히 잘 작동합니다. 작은 컬렉션이있는 간단한 경우에 더 잘 수행됩니다.

    평가 파이프 라인에 대한 더 많은 제어가 필요한 경우 Iterable<T>.asSequence()기능이 있는 지연 시퀀스로의 명시 적 변환이 있습니다.


3
대한 아마 깜짝 놀랄 Java(대부분 Guava) 팬
벤 카타 라주

기능적인 사람들을 위해 @VenkataRaju는 기본적으로 게으른 대안에 놀랄 수 있습니다.
Jayson Minard

9
기본적으로 Lazy는 일반적으로 더 작고 일반적으로 사용되는 컬렉션의 성능이 떨어집니다. CPU 캐시 등을 활용하면 복사본이 지연 평가보다 빠를 수 있습니다. 따라서 일반적인 사용 사례의 경우 게으르지 않는 것이 좋습니다. 그리고 안타깝게도 map, filter및 기타와 같은 함수에 대한 일반적인 계약 은 소스 컬렉션 유형 이외의 다른 항목을 결정할 수있는 충분한 정보를 가지고 있지 않으며 대부분의 컬렉션도 Iterable이기 때문에 "게으르다"에 대한 좋은 표식이 아닙니다. 일반적으로 어디에서나. lazy는 안전하려면 명시 적이어야합니다.
Jayson Minard 2016

1
@naki 최근 Apache Spark 발표의 한 예입니다. 그들은 분명히 이것에 대해 걱정하고 있습니다. databricks.com/blog/2015/04/28/…의 "캐시 인식 계산"섹션을 참조하십시오 . ...하지만 수십억에 대해 걱정하고 있습니다. 일이 반복되므로 극한으로 가야합니다.
Jayson Minard

3
또한 지연 평가의 일반적인 함정은 컨텍스트를 캡처하고 결과로 발생하는 지연 계산을 캡처 된 모든 로컬 및 보유하고있는 필드와 함께 필드에 저장하는 것입니다. 따라서 메모리 누수를 디버깅하기가 어렵습니다.
Ilya Ryzhenkov 2017 년

50

핫키의 답변 완료 :

Sequence 및 Iterable이 요소 전체에서 어떻게 반복되는지 확인하는 것이 중요합니다.

시퀀스 예 :

list.asSequence().filter { field ->
    Log.d("Filter", "filter")
    field.value > 0
}.map {
    Log.d("Map", "Map")
}.forEach {
    Log.d("Each", "Each")
}

로그 결과 :

필터-지도-각각; 필터-지도-각각

반복 가능한 예 :

list.filter { field ->
    Log.d("Filter", "filter")
    field.value > 0
}.map {
    Log.d("Map", "Map")
}.forEach {
    Log.d("Each", "Each")
}

필터-필터-지도-지도-각각-각각


5
그것은 둘 사이의 차이를 보여주는 훌륭한 예입니다.
Alexey Soshin

이것은 좋은 예입니다.
frye3k

2

Iterablejava.lang.Iterable인터페이스에 매핑되고 JVMList 또는 Set과 같이 일반적으로 사용되는 컬렉션에 의해 구현됩니다. 이들에 대한 컬렉션 확장 함수는 열심히 평가됩니다. 즉, 모두 입력의 모든 요소를 ​​즉시 처리하고 결과를 포함하는 새 컬렉션을 반환합니다.

다음은 컬렉션 함수를 사용하여 나이가 21 세 이상인 목록에서 처음 5 명의 이름을 가져 오는 간단한 예입니다.

val people: List<Person> = getPeople()
val allowedEntrance = people
    .filter { it.age >= 21 }
    .map { it.name }
    .take(5)

타겟 플랫폼 : JVMRunning on kotlin v. 1.3.61 첫째, 목록에있는 모든 사람에 대해 연령 검사가 수행되고 결과가 새로운 목록에 포함됩니다. 그런 다음 필터 연산자 뒤에 남아있는 모든 사람에 대해 이름에 대한 매핑이 수행되어 또 다른 새 목록 (이제 List<String>)이됩니다. 마지막으로 이전 목록의 처음 5 개 요소를 포함하도록 생성 된 마지막 새 목록이 하나 있습니다.

반대로 Sequence는 느리게 평가 된 값 모음을 나타내는 Kotlin의 새로운 개념입니다. Sequence인터페이스에 대해 동일한 컬렉션 확장을 사용할 수 있지만 이러한 확장은 실제로 요소를 처리하지 않고 처리 된 날짜 상태를 나타내는 Sequence 인스턴스를 즉시 반환합니다. 처리를 시작하려면 Sequence터미널 운영자로를 종료해야합니다. 기본적으로 시퀀스에 대한 요청으로 구체적인 형식으로 데이터를 구체화해야합니다. 예는 toList, toSet그리고 sum몇 가지를 언급. 이러한 항목이 호출되면 요구되는 결과를 생성하기 위해 필요한 최소 요소 수만 처리됩니다.

기존 컬렉션을 시퀀스로 변환하는 것은 매우 간단하며 asSequence확장 기능 만 사용하면 됩니다. 위에서 언급했듯이 터미널 연산자도 추가해야합니다. 그렇지 않으면 시퀀스가 ​​처리를 수행하지 않습니다 (다시, 게으른!).

val people: List<Person> = getPeople()
val allowedEntrance = people.asSequence()
    .filter { it.age >= 21 }
    .map { it.name }
    .take(5)
    .toList()

대상 플랫폼 : JVMRunning on kotlin v. 1.3.61이 경우 시퀀스의 Person 인스턴스는 각각 나이를 확인하고 통과하면 이름을 추출한 다음 결과 목록에 추가합니다. 이것은 5 명의 사람이 발견 될 때까지 원래 목록의 각 사람에 대해 반복됩니다. 이 시점에서 toList 함수는 목록을 반환하고 나머지 사람들은 Sequence처리되지 않습니다.

시퀀스가 할 수있는 추가 기능도 있습니다. 무한한 수의 항목을 포함 할 수 있습니다. 이를 관점에서 볼 때 연산자가 자신이하는 방식으로 작업하는 것이 합리적입니다. 무한 시퀀스의 연산자는 열심히 작업을 수행하면 절대로 돌아올 수 없습니다.

예를 들어, 터미널 운영자가 요구하는만큼 2의 거듭 제곱을 생성하는 시퀀스가 ​​있습니다 (이게 빠르게 오버플로된다는 사실을 무시 함).

generateSequence(1) { n -> n * 2 }
    .take(20)
    .forEach(::println)

여기에서 더 많은 정보를 찾을 수 있습니다 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.