在scala中,如何创建一个包含引用的不可变对象列表(当实例事先不知道时)?

时间:2017-01-18 16:36:46

标签: scala

这个答案https://stackoverflow.com/a/41717310/280393解释了如何创建一个包含引用的不可变对象列表;但是,所提供的解决方案需要事先知道对象。 如何在按需创建对象时实现此目的?

  case class PersonA(id: Int, name: String, friends: Set[Int])
  val john = PersonA(0, "john", Set(1,2))
  val maria = PersonA(1, "maria", Set(0))
  val georges = PersonA(2, "georges", Set(1))
  val peopleA = Set(john, maria, georges)

  case class PersonB(id: Int, name: String, friends: Set[PersonB])
  // case class PersonB(id: Int, name: String, friends: () => Set[PersonB])

  def convert(peopleA: Set[PersonA]): Set[PersonB] = ???

  val peopleB = convert(peopleA)

  println(peopleB)
  println(peopleB.toList.map(_.friends.size))
  peopleB.toList.map {
    case PersonB(id, name, friends) => friends.size
  }.foreach(println)

那么,如果不修改case class PersonAval peopleA的实施,如何实施convert

假设两个PersonB实例相等,如果id相等, 一个解决方案是这样的:

class PersonB(val id: Int, val name: String) {
  var friends0: Set[PersonB] = _
  def setFriends(friends: Set[PersonB]) {
    require(friends0 == null)
    friends0 = friends
  }
  def friends: Set[PersonB] = {
    require(friends0 != null)
    friends0
  }

  override def equals(that: Any): Boolean = that match {
    case t: PersonB => t.id == id
    case _ => false
  }

  override def hashCode(): Int = id.hashCode

  override def toString = s"PersonB($id, $name, List(${friends.map(_.id).mkString(", ")}))"
}

object PersonB {
  def apply(id: Int, name: String) = new PersonB(id, name)
  def apply(id: Int, name: String, friends: Set[PersonB]): PersonB = {
    val p = new PersonB(id, name)
    p.setFriends(friends)
    p
  }

  def unapply(p: PersonB): Option[(Int, String, Set[PersonB])] =
    Some((p.id, p.name, p.friends))
}

def convert(peopleA: Set[PersonA]): Set[PersonB] = {
  val peopleB = peopleA.map(p => new PersonB(p.id, p.name))
  val peopleBMap = peopleB.map(p => (p.id, p)).toMap
  peopleA.foreach(p =>
    peopleBMap(p.id).setFriends(p.friends.map(peopleBMap))
  )
  peopleB
}

有更简单的方法吗?

Udate 基于@sjrd回答的解决方案:

class PersonB(val id: Int, val name: String, friends0: => Set[PersonB]) {
  lazy val friends: Set[PersonB] = friends0

  override def equals(that: Any): Boolean = that match {
    case t: PersonB => t.id == id
    case _ => false
  }

  override def hashCode(): Int = id.hashCode

  override def toString = s"PersonB($id, $name, List(${friends.map(_.id).mkString(", ")}))"
}

object PersonB {
  def apply(id: Int, name: String, friends: => Set[PersonB]): PersonB =
    new PersonB(id, name, friends)

  def unapply(p: PersonB): Option[(Int, String, Set[PersonB])] =
    Some((p.id, p.name, p.friends))
}

def convert(peopleA: Set[PersonA]): Set[PersonB] = {
  lazy val peopleB: Map[Int, PersonB] =
    (for (PersonA(id, name, friendsIDs) <- peopleA)
      yield (id, PersonB(id, name, friendsIDs.map(peopleB)))).toMap

  peopleB.values.toSet
}

2 个答案:

答案 0 :(得分:2)

如果要创建不可变数据,则需要使用DAG /有向非循环图来创建对象的创建顺序。

我认为你不能这样做,因为前两个记录只有一个周期:

val john = PersonB(0, "john", Set(maria, ...))
val maria = PersonB(1, "maria", Set(john))
约翰在约翰身上依赖玛丽亚和玛丽亚。 因此,在约翰之后创造玛丽亚和玛丽亚之后,必须创造约翰。

所以你可能不得不与你的不可变数据结构妥协,而不是使用case类,但可能是一次赋值。

答案 1 :(得分:0)

使用PersonB

的定义
case class PersonB(id: Int, name: String, friends: () => Set[PersonB])

你可以写:

def convert(peopleA: Set[PersonA]): Set[PersonB] = {
  lazy val peopleB: Map[Int, PersonB] =
    (for (PersonA(id, name, friendsIDs) <- peopleA)
      yield PersonB(id, name, () => friendsIDs.map(peopleB)).toMap
  peopleB.values.toSet
}

未经测试,但与此相关的内容应该有效。