我有非常大的迭代器,我想分成几块。我有一个查看项目的谓词,如果它是新作品的开头,则返回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))
答案 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
}
}