我有一个元素的迭代器,我想要消耗它们,直到下一个元素满足条件,如:
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
因为它来自一个非常大的文件。
答案 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
之后应用withFilter
或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))
答案 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)
非常简短,但你必须使用新的迭代器。
对于任何不可变的集合,它都是类似的:
答案 3 :(得分:0)
您可以在toStream
上使用方法Iterator
。
Stream
是List
的懒惰等价物。
正如您从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
)时,这会“输入”输入序列。在上面的例子中,条件是相等的,因此,只要一个元素等于它的后继元素,它们就会在同一个块中。