Scala:通过谓词将Iterable分组为Iterarable Iterable

时间:2011-11-22 18:39:47

标签: scala iterator

我有非常大的迭代器,我想分成几块。我有一个查看项目的谓词,如果它是新作品的开头,则返回true。我需要将这些碎片作为迭代器,因为即使碎片也不适合记忆。有很多部分,我会担心一个递归的解决方案吹出你的堆栈。情况类似于this question,但我需要迭代器而不是列表,并且“哨兵”(谓词为真的项目)在一个片段的开头出现(并且应该包括在内)。生成的迭代器将仅按顺序使用,但有些可能根本不使用,它们应该只使用O(1)内存。我想这意味着它们应该共享相同的底层迭代器。表现很重要。

如果我要对函数签名进行攻击,那就是:

def groupby[T](iter: Iterator[T])(startsGroup: T => Boolean): Iterator[Iterator[T]] = ...

我本来喜欢使用takeWhile,但它失去了最后一个元素。我调查了span,但它缓冲了结果。我目前最好的想法涉及BufferedIterator,但也许有更好的方法。

你会知道你做得对,因为这样的事情不会让你的JVM崩溃:

groupby((1 to Int.MaxValue).iterator)(_ % (Int.MaxValue / 2) == 0).foreach(group => println(group.sum))
groupby((1 to Int.MaxValue).iterator)(_ % 10 == 0).foreach(group => println(group.sum))

5 个答案:

答案 0 :(得分:5)

你有一个固有的问题。 Iterable意味着您可以获得多个迭代器。 Iterator意味着您只能通过一次。这意味着您的Iterable[Iterable[T]]应该能够生成Iterator[Iterable[T]]个。但是当它返回一个元素 - 一个Iterable[T] - 并且要求多个迭代器时,如果没有缓存列表的结果(太大)或调用原始的可迭代并再次完成所有事情(非常低效)。

所以,虽然你可以这样做,但我认为你应该以不同的方式设想你的问题。

如果您可以从Seq开始,则可以将子集作为范围获取。

如果您已经知道如何使用iterable,则可以编写方法

def process[T](source: Iterable[T])(starts: T => Boolean)(handlers: T => Unit *)

每次starts触发“true”时,通过处理程序集递增。如果有任何方法可以在一次扫描中进行处理,那么这样就可以了。 (但是,处理程序必须通过可变数据结构或变量来保存状态。)

如果你可以在外部列表上允许迭代来破坏内部列表,那么你可以拥有一个带有附加约束的Iterable[Iterator[T]],一旦你迭代到后面的子迭代器,所有以前的子迭代器都是无效的。


以下是最后一种类型的解决方案(从Iterator[T]Iterator[Iterator[T]];可以将其换行以构成外层Iterable

class GroupedBy[T](source: Iterator[T])(starts: T => Boolean)
extends Iterator[Iterator[T]] {
  private val underlying = source
  private var saved: T = _
  private var cached = false
  private var starting = false
  private def cacheNext() {
    saved = underlying.next
    starting = starts(saved)
    cached = true
  }
  private def oops() { throw new java.util.NoSuchElementException("empty iterator") }
  // Comment the next line if you do NOT want the first element to always start a group
  if (underlying.hasNext) { cacheNext(); starting = true }
  def hasNext = {
    while (!(cached && starting) && underlying.hasNext) cacheNext()
    cached && starting
  }
  def next = {
    if (!(cached && starting) && !hasNext) oops()
    starting = false
    new Iterator[T] {
      var presumablyMore = true
      def hasNext = {
        if (!cached && !starting && underlying.hasNext && presumablyMore) cacheNext()
        presumablyMore = cached && !starting
        presumablyMore
      }
      def next = {
        if (presumablyMore && (cached || hasNext)) { 
          cached = false
          saved
        }
        else oops()
      }
    }
  }
}

答案 1 :(得分:5)

这是我使用BufferedIterator的解决方案。它不会让你正确地跳过迭代器,但它相当简单和实用。即使!startsGroup(first),第一个元素也会进入一个组。

def groupby[T](iter: Iterator[T])(startsGroup: T => Boolean): Iterator[Iterator[T]] =
  new Iterator[Iterator[T]] {
    val base = iter.buffered
    override def hasNext = base.hasNext  
    override def next() = Iterator(base.next()) ++ new Iterator[T] {
      override def hasNext = base.hasNext && !startsGroup(base.head) 
      override def next() = if (hasNext) base.next() else Iterator.empty.next()
    }
  }

更新:保持一点状态可以跳过迭代器,防止人们搞乱以前的情况:

def groupby[T](iter: Iterator[T])(startsGroup: T => Boolean): Iterator[Iterator[T]] =
new Iterator[Iterator[T]] {
  val base = iter.buffered
  var prev: Iterator[T] = Iterator.empty
  override def hasNext = base.hasNext  
  override def next() = {
    while (prev.hasNext) prev.next()        // Exhaust previous iterator; take* and drop* do NOT always work!!  (Jira SI-5002?)
    prev = Iterator(base.next()) ++ new Iterator[T] {
      var hasMore = true
      override def hasNext = { hasMore = hasMore && base.hasNext && !startsGroup(base.head) ; hasMore } 
      override def next() = if (hasNext) base.next() else Iterator.empty.next()
    }
    prev
  }
}

答案 2 :(得分:1)

如果您正在查看内存限制,那么以下内容将起作用。只有在底层可迭代对象支持视图时才能使用它。此实现将迭代Iterable,然后生成IterableViews,然后可以迭代。这个实现并不关心第一个元素是否作为一个起始组进行测试,因为它是无论如何。

def groupby[T](iter: Iterable[T])(startsGroup: T => Boolean): Iterable[Iterable[T]] = new Iterable[Iterable[T]] {
  def iterator = new Iterator[Iterable[T]] {
    val i = iter.iterator
    var index = 0
    var nextView: IterableView[T, Iterable[T]] = getNextView()
    private def getNextView() = {
      val start = index
      var hitStartGroup = false
      while ( i.hasNext && ! hitStartGroup ) {
        val next = i.next()
        index += 1
        hitStartGroup = ( index > 1 && startsGroup( next ) )
      }
      if ( hitStartGroup ) {
        if ( start == 0 ) iter.view( start, index - 1 )
        else iter.view( start - 1, index - 1 )
      } else { // hit end
        if ( start == index ) null
        else if ( start == 0 ) iter.view( start, index )
        else iter.view( start - 1, index )
      }
    }
    def hasNext = nextView != null
    def next() = {
      if ( nextView != null ) {
        val next = nextView
        nextView = getNextView()
        next
      } else null
    }
  }
}

答案 3 :(得分:1)

使用Streams可以保持低内存占用量。如果再次使用迭代器,请使用result.toIterator。

使用流,没有可变状态,只有一个条件,它几乎与Jay Hacker的解决方案一样简洁。

 def batchBy[A,B](iter: Iterator[A])(f: A => B): Stream[(B, Iterator[A])] = {
    val base = iter.buffered
    val empty = Stream.empty[(B,  Iterator[A])]

    def getBatch(key: B) = {
      Iterator(base.next()) ++ new Iterator[A] {
        def hasNext: Boolean = base.hasNext && (f(base.head) == key)
        def next(): A = base.next()
      }
    }

    def next(skipList: Option[Iterator[A]] = None): Stream[(B, Iterator[A])] = {
      skipList.foreach{_.foreach{_=>}}

      if (base.isEmpty) empty
      else {
        val key = f(base.head)
        val batch = getBatch(key)

        Stream.cons((key, batch), next(Some(batch)))
      }
    }

    next()
  }

我跑了测试:

scala> batchBy((1 to Int.MaxValue).iterator)(_ % (Int.MaxValue / 2) == 0)
         .foreach{case(_,group) => println(group.sum)}
-1610612735
1073741823
-536870909
2147483646
2147483647

第二个测试打印太多而无法粘贴到Stack Overflow。

答案 4 :(得分:0)

import scala.collection.mutable.ArrayBuffer

object GroupingIterator {

  /**
   * Create a new GroupingIterator with a grouping predicate.
   *
   * @param it The original iterator
   * @param p Predicate controlling the grouping
   * @tparam A Type of elements iterated
   * @return A new GroupingIterator
   */
  def apply[A](it: Iterator[A])(p: (A, IndexedSeq[A]) => Boolean): GroupingIterator[A] =
    new GroupingIterator(it)(p)
}

/**
 * Group elements in sequences of contiguous elements that satisfy a predicate. The predicate
 * tests each single potential next element of the group with the help of the elements grouped so far.
 * If it returns true, the potential next element is added to the group, otherwise
 * a new group is started with the potential next element as first element
 *
 * @param self The original iterator
 * @param p Predicate controlling the grouping
 * @tparam A Type of elements iterated
 */
class GroupingIterator[+A](self: Iterator[A])(p: (A, IndexedSeq[A]) => Boolean) extends Iterator[IndexedSeq[A]] {

  private[this] val source = self.buffered
  private[this] val buffer: ArrayBuffer[A] = ArrayBuffer()

  def hasNext: Boolean = source.hasNext

  def next(): IndexedSeq[A] = {
    if (hasNext)
      nextGroup()
    else
      Iterator.empty.next()
  }

  private[this] def nextGroup(): IndexedSeq[A] = {
    assert(source.hasNext)

    buffer.clear()
    buffer += source.next

    while (source.hasNext && p(source.head, buffer)) {
      buffer += source.next
    }

    buffer.toIndexedSeq
  }
}