Scala,扩展迭代器

10

我希望扩展迭代器以创建一个新的方法takeWhileInclusive,它的操作方式类似于takeWhile,但会包含最后一个元素。

我的问题是如何最佳实践地扩展迭代器以返回一个我想要进行惰性求值的新迭代器。来自C#背景,我通常使用IEnumerableyield关键字,但在Scala中似乎没有这样的选项。

例如,我可以有

List(0,1,2,3,4,5,6,7).iterator.map(complex time consuming algorithm).takeWhileInclusive(_ < 6)

在这种情况下,takeWhileInclusive仅会对值应用条件函数,直到得到一个大于6的结果,并且它将包括第一个结果。
到目前为止,我已经有:
object ImplicitIterator {
  implicit def extendIterator(i : Iterator[Any]) = new IteratorExtension(i)
}

class IteratorExtension[T <: Any](i : Iterator[T]) {
  def takeWhileInclusive(predicate:(T) => Boolean) = ?
}

你看过Stream了吗? - user unknown
在这个示例中,流可能更合适,但是我仍然面临如何构建扩展方法的相同问题。 - J Pullar
2
哦,takeWhileInclusive。我的老朋友takeTo.... - Daniel C. Sobral
5个回答

11

您可以使用Iteratorspan方法来完成这个操作:

class IteratorExtension[A](i : Iterator[A]) {
  def takeWhileInclusive(p: A => Boolean) = {
    val (a, b) = i.span(p)
    a ++ (if (b.hasNext) Some(b.next) else None)
  }
}

object ImplicitIterator {
  implicit def extendIterator[A](i : Iterator[A]) = new IteratorExtension(i)
}

import ImplicitIterator._

现在,例如(0 until 10).toIterator.takeWhileInclusive(_ < 4).toList得到的结果是List(0, 1, 2, 3, 4)


1
你的方法最后一行可以更简洁地写成 a ++ (b take 1) - Aaron Novstrup

7
这是一个我认为可变解决方案更好的例子:
class InclusiveIterator[A](ia: Iterator[A]) {
  def takeWhileInclusive(p: A => Boolean) = {
    var done = false
    val p2 = (a: A) => !done && { if (!p(a)) done=true; true }
    ia.takeWhile(p2)
  }
}
implicit def iterator_can_include[A](ia: Iterator[A]) = new InclusiveIterator(ia)

这绝对是我的问题的一个优雅解决方案,干杯! - J Pullar
谢谢,我会选择没有 varval 的函数版本! - oxbow_lakes
@oxbow_lakes - 如果你不介意额外的开销,那是一个很好的选择。 (通常我不会为函数使用val; 我只是在这里尝试将事物分开以便更清晰。) - Rex Kerr
我反对的更多是 var!而且我也没有特别认真。 - oxbow_lakes

3
以下内容需要使用scalaz才能对元组 (A, B) 进行折叠操作。
scala> implicit def Iterator_Is_TWI[A](itr: Iterator[A]) = new { 
     | def takeWhileIncl(p: A => Boolean) 
     |   = itr span p fold (_ ++ _.toStream.headOption)
     | }
Iterator_Is_TWI: [A](itr: Iterator[A])java.lang.Object{def takeWhileIncl(p: A => Boolean): Iterator[A]}

这是它的工作原理:

scala> List(1, 2, 3, 4, 5).iterator takeWhileIncl (_ < 4)
res0: Iterator[Int] = non-empty iterator

scala> res0.toList
res1: List[Int] = List(1, 2, 3, 4)

您可以像这样自己制作一个折叠功能:

scala> implicit def Pair_Is_Foldable[A, B](pair: (A, B)) = new { 
    |    def fold[C](f: (A, B) => C): C = f.tupled(pair) 
    |  } 
Pair_Is_Foldable: [A, B](pair: (A, B))java.lang.Object{def fold[C](f: (A, B) => C): C}

2
class IteratorExtension[T](i : Iterator[T]) {
  def takeWhileInclusive(predicate:(T) => Boolean) = new Iterator[T] {
    val it = i
    var isLastRead = false

    def hasNext = it.hasNext && !isLastRead
    def next = {
      val res = it.next
      isLastRead = !predicate(res)
      res
    }
  }
}

你的隐式声明中有一个错误,这里已经修正:

object ImplicitIterator {
  implicit def extendIterator[T](i : Iterator[T]) = new IteratorExtension(i)
}

这正是我的思路,谢谢你替我想到了!它提供了一个很好的通用方法。但我希望有一种更优雅的通用解决方案,而不是不得不构建一个新的迭代器。 - J Pullar

0
scala> List(0,1,2,3,4,5,6,7).toStream.filter (_ < 6).take(2)
res8: scala.collection.immutable.Stream[Int] = Stream(0, ?)

scala> res8.toList 
res9: List[Int] = List(0, 1)

更新后:

scala> def timeConsumeDummy (n: Int): Int = {
     | println ("Time flies like an arrow ...") 
     | n }
timeConsumeDummy: (n: Int)Int

scala> List(0,1,2,3,4,5,6,7).toStream.filter (x => timeConsumeDummy (x) < 6) 
Time flies like an arrow ...
res14: scala.collection.immutable.Stream[Int] = Stream(0, ?)

scala> res14.take (4).toList 
Time flies like an arrow ...
Time flies like an arrow ...
Time flies like an arrow ...
res15: List[Int] = List(0, 1, 2, 3)

timeConsumeDummy被调用了4次。我有什么遗漏吗?


抱歉,这个例子并不是我想要解决的具体情况,我会提供一个更详细的例子来说明我的需求。 - J Pullar
@JPullar:你的第二个观点已经消失并被(<6)所取代,而timeConsumingMethod现在位于(<6)的左侧。那么(timeConsumingMethod)是否会产生一个Int作为结果,与(_<6)进行比较,还是初始的List元素必须低于6? - user unknown
你展示的是正确的,也是我在惰性求值中需要的。然而,我的问题是如何在自定义扩展方法中模拟过滤函数的惰性求值。 - J Pullar
所以如果 timeConsumeDummy 返回 2*n - 1,那么所有的计算都会执行,这是正确的。 - user unknown

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接