【发布时间】:2011-03-02 01:52:54
【问题描述】:
我有一个从集合 (a) 创建的迭代器 (ite):
var a = Set(1,2,3,4,5)
var ite = a.iterator
如果我删除我的集合中的 2 个元素:
a -= 2
现在,如果我移动所有元素的迭代器,我会得到所有元素(包括 2 个元素)。没关系,但是...我如何告诉迭代器删除 2 个元素?
【问题讨论】:
我有一个从集合 (a) 创建的迭代器 (ite):
var a = Set(1,2,3,4,5)
var ite = a.iterator
如果我删除我的集合中的 2 个元素:
a -= 2
现在,如果我移动所有元素的迭代器,我会得到所有元素(包括 2 个元素)。没关系,但是...我如何告诉迭代器删除 2 个元素?
【问题讨论】:
看起来你很困惑。 a -= 2 不会删除元素 2。如果可以,那么根据定义,a 将是一个 可变 集,因为您正在更改它。 a -= 2 创建一个看起来像旧集合的全新集合,并将其分配给变量 a。 ite 怎么会神奇地知道这件事发生了?
答案是,如果它已经开始运行,它就不能。实际上,这就是使用不可变数据结构的全部意义。如果其他人认为世界现在看起来不同了,您也不会陷入混乱 - 您可以继续处理看起来与以往一样的旧副本。
但是如果迭代器还没有已经被使用过,而你只想要一个方便的标签,意思是“获取a的迭代器”,你可以
var a = Set(1,2,3,4,5)
val ite = () => a.iterator
a -= 2
ite().foreach(println) // Prints 5,1,3,4 or the like--no 2
这里,ite 不是变量,它是一个恰好是a.iterator 的闭包的函数。每次您使用它时它都会调用a.iterator,因此它始终是最新的(至少最初是这样)。您还可以创建一个惰性迭代器,它实际上不会选择使用哪个版本 a,直到需要它:
var a = Set(1,2,3,4,5)
lazy val ite = a.iterator // Not a.iterator now, but will be when we need it!
a -= 2
ite.foreach(println) // Need it now--so we'll get 5,1,3,4
注意,在这种情况下,如果你调用一个带有迭代器的方法,ite 将在它被传递给函数时被设置。以前的版本——采用返回迭代器的函数——只会创建一次迭代器。
现在,您可能需要一个迭代器,它可以通过查看a 的当前值来处理已删除的条目。你也可以这样做,但是如果删除的结果返回None,而未删除的结果返回Some(x),则更容易实现:
var a = Set(1,2,3,4,5)
val ite = new Iterator[Option[Int]] {
private val it = a.iterator
def hasNext = it.hasNext
def next = {
val i = it.next
if (a(i)) Some(i) else None
}
}
a -= 2
ite.foreach(println) // Some(5),Some(1),None,Some(3),Some(4)
(否则,您必须缓存值,因为此迭代器中的 hasNext 需要在 a 的迭代器中调用 next,以便检查该元素是否仍然存在。)
无论如何,有很多选择,但你需要决定做哪一件明智的事情是你真正想做的。
【讨论】:
如果你绝对、肯定地坚持对任务的这种狭隘定义,那你就有问题了。
重新概念化问题的一种方法是迭代集合的元素,同时收集一个包含要删除的元素的新集合。然后在迭代循环完成后从输入集合中删除这些元素。
另一种方法是使用partition(...) 方法从输入创建两个新集合,一个谓词为真,一个谓词为假。
【讨论】:
您可以为迭代器存储已删除的元素并跳过这些。
class DeletableIterator[X](it : Iterator[X]) extends Iterator[X]{
val deleted = collection.mutable.Set[X]()
var nextElement : Option[X] = getNext
private def getNext : Option[X] = {
if(it.hasNext) {
val n = it.next
if(deleted contains n) getNext else Some(n)
} else None
}
def next = {
val r = nextElement.get
nextElement = getNext
r
}
def hasNext = nextElement.isDefined
def -(x : X) = {
deleted += x
this
}
}
implicit def iterator2DeltableIterator[X](i : Iterator[X]) = new DeletableIterator(i)
只要没有太多要删除的元素,这可能很有用。
var a = Set(1,2,3,4,5)
var ite = a.iterator
ite -= 2
ite -= 1
ite.toList
scala> ite.toList
res2: List[Int] = List(5, 3, 4)
【讨论】:
问题是你的Set是不可变的(Scala默认是不可变集合),但是因为a是一个变量而不是一个值,下面的调用a -= 2实际上是在执行
a = a - 2。所以它不会改变集合,而是将一个新集合分配给a。
相反,您可以使用可变集:var a = collection.mutable.Set(1,2,3,4,5)。
scala> var a = collection.mutable.Set(1,2,3,4,5)
a: scala.collection.mutable.Set[Int] = Set(1, 4, 2, 5, 3)
scala> val i = a.iterator
i: Iterator[Int] = non-empty iterator
scala> a -= 2
res0: scala.collection.mutable.Set[Int] = Set(1, 4, 5, 3)
scala> i.toList
res1: List[Int] = List(5, 3, 1, 4)
【讨论】:
问题是你必须使用一个可变的 Set 才能工作。否则,将使用以下表达式创建一个新 Set:a -= 2。并且先前生成的迭代器ite 引用了第一个而不是新的。请参阅 2 个 REPL 会话的下一个记录(具有不可变和可变 Set)
Welcome to Scala version 2.8.0.RC3 (Java HotSpot(TM) Client VM, Java 1.6.0_11).
Type in expressions to have them evaluated.
Type :help for more information.
scala> var a = Set(1,2,3,4,5)
a: scala.collection.immutable.Set[Int] = Set(4, 5, 1, 2, 3)
scala> var ite = a.iterator
ite: Iterator[Int] = non-empty iterator
scala> a -= 2
scala> a.toList
res1: List[Int] = List(5, 1, 3, 4)
scala> ite.toList
res2: List[Int] = List(5, 1, 2, 3, 4)
scala> ite.toList
res3: List[Int] = List()
----------------------------------
Welcome to Scala version 2.8.0.RC3 (Java HotSpot(TM) Client VM, Java 1.6.0_11).
Type in expressions to have them evaluated.
Type :help for more information.
scala> import scala.collection.mutable.Set
import scala.collection.mutable.Set
scala> var a = Set(1,2,3,4,5)
a: scala.collection.mutable.Set[Int] = Set(1, 4, 2, 5, 3)
scala> var ite = a.iterator
ite: Iterator[Int] = non-empty iterator
scala> a -= 2
res0: scala.collection.mutable.Set[Int] = Set(1, 4, 5, 3)
scala> ite.toList
res1: List[Int] = List(5, 3, 1, 4)
scala> ite.toList
res2: List[Int] = List()
【讨论】:
a -= 2 实际上是对变量a 的重新赋值。但是迭代器ite 不知道这个变化,因为创建了一个新的Set(在不可变的情况下)。我认为使用不可变集合是不可能实现这一点的。