如何使以下scala不可变

时间:2017-03-31 08:58:59

标签: scala functional-programming immutability

假设我有以下代码:

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)
    }
  }

}

如何以不可变的方式实现它?

5 个答案:

答案 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.breakOutCanBuildFrom here的更多信息):

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

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