【问题标题】:How to use takeWhile with an Iterator in Scala如何在 Scala 中将 takeWhile 与迭代器一起使用
【发布时间】:2013-07-16 02:42:43
【问题描述】:

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

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,因为它来自一个非常大的文件。

【问题讨论】:

    标签: scala iterator iteration


    【解决方案1】:

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

    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。

    【讨论】:

      【解决方案2】:

      对于我的其他答案(我将其分开,因为它们基本上不相关),我认为您可以在 Iterator 上实现 groupWhen,如下所示:

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

      【讨论】:

      • 请注意,此实现将删除谓词返回 false 的元素。更好地使用 borice 实现。
      【解决方案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))
      如果要过滤掉单个元素,只需在groupWhen 之后应用filterwithFilter

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

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

      【讨论】:

        【解决方案4】:

        您可以在Iterator 上使用方法toStream

        StreamList 的惰性等效项。

        toStreamimplementation 可以看出,它创建了一个Stream,而不遍历整个Iterator

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

        对于Stream,您应该像这样使用span

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

        【讨论】:

        • 但是 Stream 有一个必须知道的巨大缺点:与迭代器不同,它 保留所有项目他已在内存中读取。
        • @om-nom-nom:如果他想在收集时重申,OP 需要所有项目。而Stream 仅在存在指向第一个元素的链接时才保留元素。
        • 但是我第一次执行 takeWhile 我得到一个 Stream(1, 1, 1, 1, 2, ?) 并且第二个 takeWhile 从 Stream(1, 1 , 1, 1, 2, ?) 给出一个空流
        • @ancechu:对不起,我误解了这个问题。如果你想要与Iterator 相同的行为,你可以使用Streamspan 方法,但最好使用Iterator,正如@om-nom-nom 提到的那样。
        【解决方案5】:

        我在这里猜测了一下,但是通过语句“直到在下一个元素中满足条件”,听起来您可能想查看@987654322 上的groupWhen 方法@ in scalaz

        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) =&gt; Boolean)时将输入序列“分块”。在上面的例子中,条件是相等,因此,只要一个元素与其后继元素相等,它们就会在同一个块中。

        【讨论】:

        • 是的,这就是我正在寻找的功能,但问题是我无法在内存中保存 groupWhen 的结果。我通过从大文件中读取行的迭代器获取值。 scalaz中是否存在迭代器的groupWhen?
        • 否 - scalaz 不“喜欢”迭代器(它们不是纯的)。他们有一个名为EphemeralStream 的类。它没有groupWhen,但你可以很容易地写一个,因为它是一个monad。我不保证它不会溢出堆栈!
        • 我在下面添加了一个不同的答案,展示了如何通过使用 iterator.duplicate 功能将 groupBy 添加到迭代器。
        猜你喜欢
        • 2013-04-27
        • 2019-08-09
        • 1970-01-01
        • 2018-08-02
        • 2022-10-07
        • 2013-05-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多