10

我有一个元素的迭代器,我想使用它们直到下一个元素满足条件,例如:

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对迭代器做 a,因为它来自一个非常大的文件。

4

5 回答 5

5

我发现的最简单的解决方案:

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,
  • 当你需要休息的时候也可以使用 span。
于 2015-10-17T09:29:44.103 回答
3

有了我的另一个答案(我已经分开了,因为它们在很大程度上是不相关的),我认为您可以groupWhenIterator如下方式实现:

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))

于 2013-07-17T16:17:56.210 回答
3

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

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

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

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))
如果要过滤掉单个元素,只需应用 afilterwithFilter之后groupWhen

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

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

于 2014-03-28T07:45:36.107 回答
0

您可以toStreamIterator.

Stream是 的惰性等价物List

实现中可以看出,toStream它创建了一个Stream而不遍历整个Iterator.

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

Stream你应该像这样使用span

val (res1, rest1) = stream.span(_ == 1)
val (res2, rest2) = rest1.span(_ == 2)
于 2013-07-17T14:01:59.887 回答
0

我在这里猜测了一下,但是通过“直到在下一个元素中满足条件”这句话,听起来您可能想查看scalaz中的groupWhen方法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) => Boolean基本上,这会在元素与其后继元素之间满足条件 (a ) 时将输入序列“分块” 。在上面的示例中,条件是相等,因此,只要一个元素与其后继元素相等,它们就会在同一个块中。

于 2013-07-17T15:27:18.440 回答