2012-01-22 4 views
21

Sto giocherellando intorno alla mia domenica pomeriggio e sto cercando di creare una sorta di "stanza". Fondamentalmente, un oggetto Room ha un numero di uscite, ciascuna delle quali si riferisce ad altri Room s. Ora, la prima cosa che sto cercando di creare è la connessione di due Room, preferibilmente in una singola istruzione di assegnazione. Come questo:Come faccio a fare riferimento a una variabile mentre assegna un valore ad essa, pur conservando l'immutabilità?

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

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

Ergo: in camera si ha un'uscita North a due camera, camera a due ha un'uscita South tornare a una camera.

Tuttavia, come potete immaginare, questo va storto: il valore firstRoom val non viene definito durante la sua creazione, quindi provare a fare riferimento ad esso durante il suo incarico non funzionerà.

Sono sicuro che questo è vero per la maggior parte, se non tutti i linguaggi di programmazione. La mia domanda: come risolvo questo senza rendendo il mio oggetto Room mutabile? Posso semplicemente creare alcuni oggetti Room e aggiungere successivamente le uscite, ma ciò rende il Room s mutabile e, come esercizio personale, cerco di evitarlo.

risposta

17

non ricorsiva

penso che la soluzione migliore è quella di fare qualcosa di simile

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

E 'perfettamente immutabile e si evita ricorsioni brutte.

ricorsivo

Costruire una struttura ricorsiva prende cura molto di più come Scala non è pigro per impostazione predefinita. In particolare, non è possibile creare un parametro lazy o call-by-name case class. Quindi, dovremo ricorrere a una struttura dati specializzata per questo.

Una possibilità potrebbe essere quella di usare 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 

Per essere sul lato risparmiare, ho prefissati una camera fittizio prima di ogni Stream per evitare l'accesso prematuro. (Un flusso è solo pigro nella sua coda ma non nella sua testa.) Una struttura dati dedicata potrebbe essere in grado di evitarlo.

ripulito versione

Possiamo fare di meglio con una funzione di supporto chiamata per nome per fare il lavoro sporco:

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(), ?))) 
+0

Risposta epica, ti meriti tutti i voti che puoi ottenere. Non solo fornisci una risposta semplice, ma anche una più profonda su una funzione linguistica che non avevo ancora avuto la possibilità di utilizzare o di trovare uno scopo. Mi sono imbattuto in flussi una o due volte durante la ricerca di questa risposta, ma non avevo idea di come avrebbero risolto questo problema. Molto obbligato. – fwielstra

0

Una soluzione alternativa, che purtroppo (credo) doesn 'lavoro t con classi 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)) 

Un'altra opzione sarebbe quella di utilizzare una mappa "pigro":

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

La sintassi sarebbe migliore se si rendesse la propria implementazione di mappa pigra estendendo il tratto scala.collection.immutable.Map.

2

Sono totalmente d'accordo con la risposta di Debilski, ma non ho potuto resistere alla sostituzione del metodo exists con una lazy val exists, impedendo che la ricerca si ripetesse all'infinito.

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