如何使以下 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;这是 Ints:

的示例
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.breakOutCanBuildFrom here):

scala> val unique: Set[Int] = groups.flatMap(identity)(collection.breakOut)
unique: Set[Int] = Set(5, 1, 2, 3, 4)

然而,这里可变性的来源是 Iterator 的使用,它无论如何都会被消耗,那里发生变异和破坏 referential transparency