如何在Scala中使用带有Iterator的takeWhile

时间:2013-07-17 13:50:03

标签: scala iterator iteration

我有一个元素的迭代器,我想要消耗它们,直到下一个元素满足条件,如:

val it = List(1,1,1,1,2,2,2).iterator
val res1 = it.takeWhile( _ == 1).toList
val res2 = it.takeWhile(_ == 2).toList

res1给出了预期的List(1,1,1,1)res2返回List(2,2),因为迭代器必须检查位置4中的元素。

我知道列表会被排序,所以没有必要像partition一样遍历整个列表。我希望在条件不满足时立即完成。是否有任何聪明的方法与迭代器一起做这个?我无法对迭代器执行toList因为它来自一个非常大的文件。

5 个答案:

答案 0 :(得分:3)

我有类似的需求,但@oxbow_lakes的solution没有考虑列表只有一个元素的情况,或者即使列表包含不重复的元素。此外,该解决方案并不适用于无限迭代器(它希望"在它给出结果之前看到"所有元素)。

我需要的是能够对与谓词匹配的顺序元素进行分组,但也包括单个元素(如果我不需要,我总是可以过滤掉它们)。我需要连续交付这些组,而不必等待原始迭代器在生成之前完全消耗。

我提出了以下方法,该方法适合我的需求,并认为我应该分享:

implicit class IteratorEx[+A](itr: Iterator[A]) {
  def groupWhen(p: (A, A) => Boolean): Iterator[List[A]] = new AbstractIterator[List[A]] {
    val (it1, it2) = itr.duplicate
    val ritr = new RewindableIterator(it1, 1)

    override def hasNext = it2.hasNext

    override def next() = {
      val count = (ritr.rewind().sliding(2) takeWhile {
        case Seq(a1, a2) => p(a1, a2)
        case _ => false
      }).length

      (it2 take (count + 1)).toList
    }
  }
}

以上是使用一些辅助类:

abstract class AbstractIterator[A] extends Iterator[A]

/**
 * Wraps a given iterator to add the ability to remember the last 'remember' values
 * From any position the iterator can be rewound (can go back) at most 'remember' values,
 * such that when calling 'next()' the memoized values will be provided as if they have not
 * been iterated over before.
 */
class RewindableIterator[A](it: Iterator[A], remember: Int) extends Iterator[A] {
  private var memory = List.empty[A]
  private var memoryIndex = 0

  override def next() = {
    if (memoryIndex < memory.length) {
      val next = memory(memoryIndex)
      memoryIndex += 1
      next
    } else {
      val next = it.next()
      memory = memory :+ next
      if (memory.length > remember)
        memory = memory drop 1
      memoryIndex = memory.length
      next
    }
  }

  def canRewind(n: Int) = memoryIndex - n >= 0

  def rewind(n: Int) = {
    require(memoryIndex - n >= 0, "Attempted to rewind past 'remember' limit")
    memoryIndex -= n
    this
  }

  def rewind() = {
    memoryIndex = 0
    this
  }

  override def hasNext = it.hasNext
}

使用示例:

List(1,2,2,3,3,3,4,5,5).iterator.groupWhen(_ == _).toList

给出:List(List(1), List(2, 2), List(3, 3, 3), List(4), List(5, 5))
如果您想过滤掉单个元素,只需在filter之后应用withFiltergroupWhen

Stream.continually(Random.nextInt(100)).iterator
      .groupWhen(_ + _ == 100).withFilter(_.length > 1).take(3).toList

给出:List(List(34, 66), List(87, 13), List(97, 3))

答案 1 :(得分:2)

我的另一个答案(由于它们在很大程度上不相关而分开),我认为您可以在groupWhen上实施Iterator,如下所示:

def groupWhen[A](itr: Iterator[A])(p: (A, A) => Boolean): Iterator[List[A]] = {
  @annotation.tailrec 
  def groupWhen0(acc: Iterator[List[A]], itr: Iterator[A])(p: (A, A) => Boolean): Iterator[List[A]] = {
    val (dup1, dup2) = itr.duplicate
    val pref = ((dup1.sliding(2) takeWhile { case Seq(a1, a2) => p(a1, a2) }).zipWithIndex collect {
      case (seq, 0)       => seq
      case (Seq(_, a), _) => Seq(a)
    }).flatten.toList
    val newAcc = if (pref.isEmpty) acc else acc ++ Iterator(pref)
    if (dup2.nonEmpty)
      groupWhen0(newAcc, dup2 drop (pref.length max 1))(p)
    else newAcc
  }
  groupWhen0(Iterator.empty, itr)(p)
}

当我在一个例子上运行时:

println( groupWhen(List(1,1,1,1,3,4,3,2,2,2).iterator)(_ == _).toList )

我得到List(List(1, 1, 1, 1), List(2, 2, 2))

答案 2 :(得分:2)

我找到的最简单的解决方案:

val it = List(1,1,1,1,2,2,2).iterator
val (r1, it2) = it.span( _ == 1)

println(s"group taken is: ${r1.toList}\n rest is: ${it2.toList}")

输出:

group taken is: List(1, 1, 1, 1)
rest is: List(2, 2, 2)

非常简短,但你必须使用新的迭代器。

对于任何不可变的集合,它都是类似的:

  • 当你只想要一些集合前缀
  • 时使用takeWhile
  • 在您需要休息时使用跨度。

答案 3 :(得分:0)

您可以在toStream上使用方法Iterator

StreamList的懒惰等价物。

正如您从toStream的{​​{3}}所看到的那样,它会创建一个Stream而不会遍历整个Iterator

Stream将所有元素保留在内存中。您应该在某些本地范围内本地使用链接到Stream的链接,以防止内存泄漏。

使用Stream,您应该使用span,如下所示:

val (res1, rest1) = stream.span(_ == 1)
val (res2, rest2) = rest1.span(_ == 2)

答案 4 :(得分:0)

我猜这里有点但是通过语句“直到下一个元素”满足条件,听起来你可能想看看groupWhen方法 scalaz

中的ListOps
scala> import scalaz.syntax.std.list._
import scalaz.syntax.std.list._

scala> List(1,1,1,1,2,2,2) groupWhen (_ == _)
res1: List[List[Int]] = List(List(1, 1, 1, 1), List(2, 2, 2))

基本上,当元素与其后继元素之间满足条件(a (A, A) => Boolean)时,这会“输入”输入序列。在上面的例子中,条件是相等的,因此,只要一个元素等于它的后继元素,它们就会在同一个块中。