【问题标题】:Kotlin's Iterable and Sequence look exactly same. Why are two types required?Kotlin 的 Iterable 和 Sequence 看起来完全一样。为什么需要两种类型?
【发布时间】:2016-02-25 13:48:52
【问题描述】:

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

public operator fun iterator(): Iterator<T>

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

【问题讨论】:

    标签: kotlin iterable lazy-sequences


    【解决方案1】:

    完成热键的回答:

    请务必注意 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")
    }
    

    过滤器-过滤器-地图-地图-每个-每个

    【讨论】:

    • 这是一个很好的例子来说明两者之间的差异。
    • 这是一个很好的例子。
    【解决方案2】:

    主要区别在于Iterable&lt;T&gt;Sequence&lt;T&gt; 的语义和stdlib 扩展函数的实现。

    • 对于Sequence&lt;T&gt;,扩展函数尽可能延迟执行,类似于Java Streams 中间 操作。例如,Sequence&lt;T&gt;.map { ... } 返回另一个 Sequence&lt;R&gt;,并且在调用像 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&lt;T&gt; 用于在您希望尽可能减少 终端 操作中完成的工作时的惰性使用和高效流水线,与 Java Streams 相同。然而,惰性引入了一些开销,这对于较小集合的常见简单转换来说是不可取的,并且会降低它们的性能。

      一般来说,没有很好的方法来确定何时需要它,因此在 Kotlin 标准库中将惰性显式化并提取到 Sequence&lt;T&gt; 接口,以避免默认情况下在所有 Iterables 上使用它。

    • 对于Iterable&lt;T&gt;,相反,具有中间操作语义的扩展函数急切地工作,立即处理项目并返回另一个Iterable。例如,Iterable&lt;T&gt;.map { ... } 返回一个 List&lt;R&gt;,其中包含映射结果。

      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&lt;T&gt; 默认情况下是非惰性的,这个解决方案很好地展示了自己:在大多数情况下它具有良好的locality of reference 从而利用 CPU 缓存、预测、预取等,因此甚至可以多次复制的集合仍然足够好,并且在具有小集合的简单情况下表现更好。

      如果您需要对评估管道进行更多控制,可以使用 Iterable&lt;T&gt;.asSequence() 函数显式转换为惰性序列。

    【讨论】:

    • 对于Java(主要是Guava)的粉丝来说可能是一个很大的惊喜
    • @VenkataRaju 对于功能性人士来说,他们可能会对默认情况下懒惰的替代品感到惊讶。
    • Lazy 默认情况下对于更小和更常用的集合通常性能较差。如果利用 CPU 缓存等,副本可能比惰性 eval 更快。所以对于常见的用例最好不要偷懒。不幸的是,像mapfilter 和其他函数的常见合同没有携带足够的信息来决定除了源集合类型之外,而且由于大多数集合也是可迭代的,这不是一个很好的标记“懒惰”,因为它通常无处不在。为了安全,惰性必须是显式的。
    • @naki 最近 Apache Spark 公告中的一个例子,他们显然担心这一点,请参阅databricks.com/blog/2015/04/28/… 的“缓存感知计算”部分......但他们担心数十亿的事情迭代所以他们需要走极端。
    • 此外,惰性求值的一个常见缺陷是捕获上下文并将产生的惰性计算与所有捕获的本地变量以及它们所持有的任何内容一起存储在一个字段中。因此,很难调试内存泄漏。
    【解决方案3】:

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

    sequence中,每个项目在处理下一个项目之前都经过完整管道.

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

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

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

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

    【讨论】:

      【解决方案4】:

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

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

      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&lt;String&gt;)。最后,有一个 创建的最后一个新列表以包含 上一个列表。

      相比之下,Sequence 是 Kotlin 中的一个新概念,用来表示一个懒惰的 评估值的集合。相同的集合扩展是 可用于Sequence 接口,但这些会立即返回 表示日期的已处理状态的序列实例,但 没有实际处理任何元素。要开始处理, Sequence 必须由终端操作员终止,这些是 基本上是对序列的请求以实现它的数据 以某种具体形式表示。示例包括toListtoSet、 和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 在这种情况下, 序列中的每个人实例都会检查他们的年龄,如果 他们通过了,他们的名字被提取出来,然后添加到 结果列表。对原始列表中的每个人重复此操作 直到找到五个人。此时,toList函数 返回一个列表,而Sequence 中的其余人则不是 已处理。

      序列还有一些额外的能力:它可以包含 无限数量的项目。从这个角度来看,这是有道理的 运营商按照他们的方式工作 - 一个无限的运营商 如果它急于完成它的工作,序列将永远不会返回。

      作为一个例子,这里有一个序列会产生尽可能多的 2 根据其终端运营商的要求(忽略这一事实 会很快溢出):

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

      您可以找到更多here

      【讨论】:

        猜你喜欢
        • 2022-11-19
        • 1970-01-01
        • 2013-02-23
        • 1970-01-01
        • 2015-10-11
        • 1970-01-01
        • 1970-01-01
        • 2019-01-03
        • 1970-01-01
        相关资源
        最近更新 更多