Подтвердить что ты не робот

Как я могу ссылаться на переменную, присваивая ей значение, сохраняя неизменность?

Я возился в свой воскресный день и пытаюсь создать структуру "комнаты". В принципе, объект Room имеет несколько выходов, каждый из которых относится к другим Room s. Теперь, первое, что я пытаюсь создать, - это два Room, связанных друг с другом, предпочтительно в одном задании присваивания. Вот так:

case class Room(title: String, exits: Map[Direction.Direction, Room])

val firstRoom = Room("A room", Map(North -> Room("Another room", Map(South -> firstRoom))))

Эрго: номер один имеет выход North в комнату два, вторая комната имеет выход South обратно в комнату.

Однако, как вы можете себе представить, это не так: значение firstRoom val не определено при его создании, поэтому попытка ссылаться на него во время его назначения не будет работать.

Я уверен, что это верно для большинства, если не для всех языков программирования. Мой вопрос: как я могу решить это, не изменяя объект Room? Я могу просто создать несколько объектов Room и добавить к ним выходы потом, но это делает Room изменчивым, а в качестве личного упражнения я стараюсь избегать этого.

4b9b3361

Ответ 1

нерекурсивна

Я думаю, что ваш лучший вариант - сделать что-то вроде этого

object Rooms {
  case class Room(title: String) {
    def exits = exitMap(this)
  }
  val first:Room = Room("first")
  val second:Room = Room("second")

  private val exitMap = Map(first -> Map("S" -> second), second -> Map("N" -> first))
}

scala> Rooms.first.title
res: String = first

scala> Rooms.first.exits
res: scala.collection.immutable.Map[java.lang.String,Rooms.Room] = Map(S -> Room(second))

Это совершенно непреложно, и вы избегаете неприятных рекурсий.

Рекурсивный

Построение рекурсивной структуры требует гораздо большей осторожности, поскольку по умолчанию Scala не является ленивым. В частности, невозможно создать ленивый или вызываемый по имени параметр case class. Поэтому для этого нужно использовать специализированную структуру данных.

Одним из вариантов может быть использование Stream s:

case class LazyRoom(title: String, exits: Stream[LazyRoom])

object LazyRooms {
  lazy val nullRoom: LazyRoom = LazyRoom("nullRoom", Stream.empty)
  lazy val first: LazyRoom = LazyRoom("first", nullRoom #:: second #:: Stream.empty)
  lazy val second: LazyRoom = LazyRoom("second", nullRoom #:: first #:: Stream.empty)
}

scala> LazyRooms.first.exits(1).title
res> String: second

Чтобы быть на стороне сохранения, я префикс фиктивной комнаты перед каждым Stream, чтобы избежать преждевременного доступа. (Поток только ленив в хвосте, но не в его голове.) Специальная структура данных может избежать этого.

Очищенная версия

Мы можем сделать лучше с помощью вспомогательной функции call-by-name для выполнения грязной работы:

case class LazyRoom(title: String, exitMap: Stream[Map[String, LazyRoom]]) {
  def exits = exitMap(1) // skip the Streams empty head
}

def _exitMap(mappedItems: => Map[String, LazyRoom]) = {
  Map[String, LazyRoom]() #::
  mappedItems #::
  Stream.empty
}

object LazyRooms {
  lazy val first: LazyRoom = LazyRoom("first", _exitMap(Map("South" -> second)))
  lazy val second: LazyRoom = LazyRoom("second", _exitMap(Map("North" -> first)))
}

scala> LazyRooms.first.exits
res: Map[String,LazyRoom] = Map(South -> LazyRoom(second,Stream(Map(), ?)))

Ответ 2

Я полностью согласен с ответом Дебильского, но я не мог удержаться от замены метода exists ленивым val exists, предотвращая повторение поиска снова и снова.

object Rooms {
  case class Room(title: String) {
    lazy val exits = exitMap(this)
  }
  val first:Room = Room("first")
  val second:Room = Room("second")

  private val exitMap = Map(first -> Map("S" -> second), second -> Map("N" -> first))
}

Ответ 3

Альтернативное решение, к сожалению (я думаю) не работает с классами case:

class Room(val title: String, _exits : => Map[String, Room]) { lazy val exits = _exits }
val room1 : Room = new Room("A room", Map("N" -> room2))
val room2 : Room = new Room("Another room", Map("S" -> room1))

Другой вариант - использовать "ленивую" карту:

case class Room(val title : String, exits : Map[String, () => Room])
val room1 : Room = Room("A room", Map("N" -> (() => room2)))
val room2 : Room = Room("Another room", Map("S" -> (() => room1)))

Синтаксис был бы лучше, если бы вы сделали свою собственную ленивую реализацию карты, расширяя черту scala.collection.immutable.Map.