假设我有以下代码:
BroadcastMessage以迭代器的形式获取学生组列表,只能遍历
致电sendMessage
时,它会向小组中的所有学生发送消息。
class BroadcastMessage(message:String,groups:List[Iterator[Student]]) {
def sendMessage:Unit = {
groups.foreach(group=>group.foreach(sendeMessage))
}
private def sendMessage(student:Student): Unit ={
EmailClient.sendMessage(student.email,message)
}
}
case class Student(id: String,email:String)
假设学生可以分成几组,我们不希望向他发送多封电子邮件。
可变解决方案是添加可变集并将学生的id添加到集合中,并且只有在集合中存在id时才发送消息。
class BroadcastMessage(message:String,groups:List[Iterator[Student]]) {
// evil mutable set
private var set:scala.collection.mutable.Set[String] = Set()
def sendMessage:Unit = {
groups.foreach(group=>group.foreach(sendeMessage))
}
private def sendMessage(student:Student): Unit ={
if (set.add(student.id)) {
EmailClient.sendMessage(student.email, message)
}
}
}
如何以不可变的方式实现它?
答案 0 :(得分:2)
嗯,我认为你在你的例子中做了两个不同的可变事物,而你真的只需要一个。
您需要private val set : mutable.Set[Student] = mutable.Set.empty[Student]
或private var set : Set[Student] = Set.empty[Student]
。也就是说,你需要改变集合本身,或者只需要引用到你的类所拥有的那个。我会亲自去找后者,最后得到的结论是:
case class Student(id: String,email:String)
class BroadcastMessage(message:String,groups:List[Iterator[Student]]) {
private var set : Set[Student] = Set.empty // <- no mutable Set, just a mutable reference to several immutable Sets
def sendMessage:Unit = {
groups.foreach(group=>group.foreach(sendMessage))
}
private def sendMessage(student:Student): Unit = {
if (!set(student)) {
set = set + student
EmailClient.sendMessage(student.email, message)
}
}
}
最后,您甚至可以一起摆脱sendMessage(student : Student)
方法:
case class Student(id: String,email:String)
class BroadcastMessage(message:String,groups:List[Iterator[Student]]) {
private var set : Set[Student] = Set.empty
def sendMessage:Unit = {
val students = (for{
group <- groups
student <- group
} yield student).toSet
val toBeNotified = (students -- set)
toBeNotified.foreach(student => EmailClient.sendMessage(student.email, message))
set = set ++ toBeNotified
}
}
我想这取决于风格......
答案 1 :(得分:1)
我设法做到了这一点。我想我失去了一些可读性,但它是可变的:
class BroadcastMessage(message: String, groups: List[Iterator[Student]]) {
def sendMessage(): Unit = {
groups.foldLeft[Set[String]](Set.empty)(sendMessage)
}
private def sendMessage(sent: Set[String], group: Iterator[Student]):Set[String] = {
group.foldLeft[Set[String]](sent)(sendMessage)
}
private def sendMessage(sent: Set[String], student: Student): Set[String] = {
if (!sent.contains(student.id)) {
EmailClient.sendMessage(student.email, message)
return sent + student.id
}
sent
}
}
答案 2 :(得分:0)
如果您没有内存限制,可以这样做:
def sendMessage:Unit = {
groups.flatten.distinct.foreach(sendMessage)
}
答案 3 :(得分:0)
你可以用单行代码来完成:
def sendMessage: Unit =
groups.reduce(_ ++ _).toStream.distinct.foreach(sendMessage)
用于学习目的的扩展版本:
val students: Iterator[Student] = groups.reduce(_ ++ _)
val sStudents: Stream[Student] = students.toStream
val dStudents: Stream[Student] = sStudents.distinct
def sendMessage: Unit = sStudents.foreach(sendMessage)
答案 4 :(得分:0)
看起来你正在寻找的是嵌套集合中的所有唯一Student
。
一种非常简单的方法是将集合展平并将其转换为Set
;这是Int
s:
scala> val groups = List(Iterator(1,2,3), Iterator(3,4,5))
groups: List[Iterator[Int]] = List(non-empty iterator, non-empty iterator)
scala> val unique: Set[Int] = groups.flatten.toSet
unique: Set[Int] = Set(5, 1, 2, 3, 4)
这里的问题是toSet
方法实际上复制了您的列表。为避免这种情况,您可以使用这个小技巧(您可以阅读有关collection.breakOut
和CanBuildFrom
here的更多信息):
scala> val unique: Set[Int] = groups.flatMap(identity)(collection.breakOut)
unique: Set[Int] = Set(5, 1, 2, 3, 4)
然而,这里可变性的来源是Iterator
的使用,无论如何都会消耗,有变异和破坏referential transparency。