Kotlin的Iterable和Sequence看起来完全一样。为什么需要两种类型?

时间:2016-02-25 13:48:52

标签: kotlin iterable lazy-sequences

这两个接口都只定义了一个方法

public operator fun iterator(): Iterator<T>

文档说Sequence是懒惰的。但是Iterable也不是懒惰(除非由Collection支持)?

4 个答案:

答案 0 :(得分:113)

关键区别在于Iterable<T>Sequence<T>的语义和stdlib扩展函数的实现。

  • 对于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将laziness明确化并提取到Sequence<T>接口,以避免在所有Iterable上使用它默认值。

  • 对于Iterable<T>,相反,带有中间操作语义的扩展函数急切地工作,立即处理项目并返回另一个Iterable。例如,Iterable<T>.map { ... }会返回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>是非延迟的,这个解决方案很好地表现出来:在大多数情况下它具有良好的locality of reference,因此利用了CPU缓存,预测,预取等等。甚至多次复制一个集合仍然可以很好地工作,并且在小集合的简单情况下表现更好。

    如果您需要对评估管道进行更多控制,则可以使用Iterable<T>.asSequence()函数显式转换为延迟序列。

答案 1 :(得分:36)

完成热键的回答:

重要的是要注意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")
                }

filter - filter - Map - Map - Each - Each

答案 2 :(得分:0)

  

Iterable映射到   java.lang.Iterable,由常用集合(例如List或   组。评估这些上的集合扩展功能   急切地,这意味着它们都会立即处理其中的所有元素   他们的输入,并返回一个包含结果的新集合。

     

这是一个使用收集函数获取   列表中年龄至少21岁的前五个人的名字:

JVM
     

目标平台:在kotlin v。1.3.61上运行的JVM首先,检查年龄   为列表中的每个人完成的操作,结果放入   全新清单。然后,对每个人的姓名进行映射   仍在过滤器运算符之后但仍留在其中的人   另一个新列表(现在是val people: List<Person> = getPeople() val allowedEntrance = people .filter { it.age >= 21 } .map { it.name } .take(5) )。最后,有一个   创建的最后一个新列表包含该列表的前五个元素   以前的列表。

     

相反,Sequence是Kotlin中的一个新概念,代表懒惰   评估值的集合。相同的集合扩展是   可用于List<String>界面,但这些会立即返回   表示日期的已处理状态的序列实例,但是   而不实际处理任何元素。要开始处理,   Sequence必须由终端操作员终止,这是   基本上是对序列的要求,以实现其数据   以某种具体形式表示。示例包括SequencetoList,   和toSet,仅举几例。当这些被调用时,只有   最少需要的元素数量将被处理以生成   要求的结果。

     

将现有集合转换为序列很漂亮   直截了当,您只需要使用sum扩展名。如   上面提到的,还需要添加一个终端运算符,否则   该序列将永远不会进行任何处理(再次,懒惰!)。

asSequence
     

目标平台:在kotlin v。1.3.61上运行的JVM在这种情况下,   序列中的每个人物实例都会被检查年龄,如果   他们通过了,提取了他们的名字,然后将其添加到   结果列表。对原始列表中的每个人重复此操作   直到找到五个人。至此,toList函数   返回一个列表,val people: List<Person> = getPeople() val allowedEntrance = people.asSequence() .filter { it.age >= 21 } .map { it.name } .take(5) .toList() 中的其他人不是   已处理。

     

序列还具有一些其他功能:它可以包含   无限数量的项目。从这个角度来看,这很有意义   运算符以他们的方式工作-无限运算符   如果急切地工作,顺序就永远不会返回。

     

例如,这是一个序列,它将产生   2根据其终端操作员的要求(忽略此事实   会很快溢出):

Sequence

您可以找到更多here

答案 3 :(得分:0)

Iterable 对于大多数用例来说已经足够好了,对它们执行迭代的方式由于空间局部性,它与缓存一起工作得很好。 但它们的问题是整个集合必须通过第一个中间操作,然后才能移动到第二个等等。

sequence中,每个项目在下一个之前通过完整的管道处理。

此属性可能会影响代码的性能,尤其是在迭代大型数据集时。因此,如果您的终端操作很可能提前终止,那么 sequence 应该是首选,因为您可以通过不执行不必要的操作来节省成本。例如

sequence.filter { getFilterPredicate() }   
        .map    { getTransformation()  } 
        .first  { getSelector() }

在上述情况下,如果第一项满足 filter 谓词并且在 map 转换后满足选择条件,则仅调用 filtermapfirst一次。

如果是可迭代的整个集合,必须先过滤然后映射,然后开始第一次选择

相关问题