如何无突变地条件式消费Scala的io.Source迭代器?
无突变实现Scala文件按条件分块读取
问题背景
需要基于scala.io.Source按条件分块读取文件,处理完一个块后,后续读取能从当前位置继续。现有实现依赖可变变量,希望找到无需突变源的更优方案,同时要注意Iterator.takeWhile无法满足需求——它会跳过第一个不满足条件的元素,导致数据丢失。
为什么takeWhile不可用
takeWhile会遍历迭代器直到遇到第一个不满足条件的元素,但这个元素会被直接跳过,不会留在迭代器中供后续读取。比如下面的示例中,数字5就丢失了:
val it = List(1,2,3,4,5,6,7,8,9).iterator val res1 = it.takeWhile(_<5).toList // List(1, 2, 3, 4) val res2 = it.takeWhile(_<10).toList // List(6, 7, 8, 9), 5 is missing
现有可变实现
通过类内部的可变变量保存迭代器状态,每次调用get方法时用span拆分满足条件的元素和剩余迭代器,再更新可变变量:
import scala.io.Source class Reader(filePath: String) { var src = Source.fromFile(filePath).getLines def get(f: String => Boolean) = { val (a, b) = src.span(f) src = b a } } object Reader { def apply(filePath: String) = new Reader(filePath) } val file = Reader("file") val res1 = file.get(_.toInt < 5).toList // List("0", "1", "2", "3", "4") val res2 = file.get(_.toInt < 10).toList // List("5", "6", "7", "8", "9")
无突变的纯函数式实现
可以用case class封装迭代器状态,每次调用get方法时返回匹配结果和包含剩余迭代器的新实例,全程避免可变变量:
import scala.io.Source case class PureReader(src: Iterator[String]) { def get(f: String => Boolean): (List[String], PureReader) = { val (matching, remaining) = src.span(f) (matching.toList, PureReader(remaining)) } } object PureReader { def apply(filePath: String): PureReader = PureReader(Source.fromFile(filePath).getLines()) } // 使用示例 val initialReader = PureReader("file") val (res1, readerAfterRes1) = initialReader.get(_.toInt < 5) // res1: List("0", "1", "2", "3", "4") val (res2, _) = readerAfterRes1.get(_.toInt < 10) // res2: List("5", "6", "7", "8", "9")
实现说明
span方法会将迭代器拆分为两部分:前缀(所有满足条件的元素)和剩余部分(从第一个不满足条件的元素开始),完美解决了takeWhile丢失元素的问题。- 每次调用
get都返回新的PureReader实例,没有可变状态,符合函数式编程的无副作用原则,同时保证了读取位置的连续性。
内容的提问来源于stack exchange,提问作者Sergey Bushmanov
相关产品推荐
相关产品推荐

