如何使以下 Scala 不可变
how to make the following scala immutable
假设我有以下代码:
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)
}
}
}
如何以不可变的方式实现它?
如果你没有内存限制,你可以这样做:
def sendMessage:Unit = {
groups.flatten.distinct.foreach(sendMessage)
}
好吧,我认为你在你的例子中做了两个不同的可变事物,而你实际上只需要一个。
您需要 private val set : mutable.Set[Student] = mutable.Set.empty[Student]
或 private var set : Set[Student] = Set.empty[Student]
。也就是说,您需要 改变集合本身 或只是 对它的引用 您的 class 持有。我个人会选择后者,结果是这样的:
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
}
}
我想这取决于风格...
我设法做到了。我想我失去了一些可读性但它是可变的:
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
}
}
你可以用一条线来做:
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)
看起来您要查找的内容在嵌套集合中都是唯一的 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。
假设我有以下代码:
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)
}
}
}
如何以不可变的方式实现它?
如果你没有内存限制,你可以这样做:
def sendMessage:Unit = {
groups.flatten.distinct.foreach(sendMessage)
}
好吧,我认为你在你的例子中做了两个不同的可变事物,而你实际上只需要一个。
您需要 private val set : mutable.Set[Student] = mutable.Set.empty[Student]
或 private var set : Set[Student] = Set.empty[Student]
。也就是说,您需要 改变集合本身 或只是 对它的引用 您的 class 持有。我个人会选择后者,结果是这样的:
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
}
}
我想这取决于风格...
我设法做到了。我想我失去了一些可读性但它是可变的:
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
}
}
你可以用一条线来做:
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)
看起来您要查找的内容在嵌套集合中都是唯一的 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。