2010-04-06 20 views
12

Estoy construyendo una aplicación completa a partir de objetos inmutables para que el multi-threading y deshacer sea más fácil de implementar. Estoy usando el Google Collections Library que proporciona versiones inmutables de Mapa, Lista y Conjunto.¿Cómo manipulo un árbol de objetos inmutables?

Mi modelo de aplicación se parece a un árbol:

  • Escena es un objeto de nivel superior que contiene una referencia a un nodo raíz.
  • Cada nodo puede contener nodos y puertos secundarios.

Un gráfico de objetos podría tener este aspecto:

Scene 
| 
+-- Node 
     | 
     +-- Node 
      | 
      +- Port 
     +-- Node 
      | 
      +- Port 
      +- Port 

Si todos estos objetos son inmutables, controlado por un objeto de nivel superior SceneController:

  • ¿Cuál es la mejor manera de construir esta jerarquía?
  • ¿Cómo reemplazaría un objeto que es arbitrariamente profundo en el árbol de objetos?
  • ¿Hay alguna manera de admitir enlaces de retroceso, p. un nodo que tiene un atributo "principal"?

Y más en general:

  • Tener cualquier patrón surgió para hacer frente a este tipo de datos?
  • ¿Hay literatura (académica) disponible sobre el tema?
  • ¿Es esta una buena idea?
+0

¿Qué has probado ya? ¿Por qué una solución sencilla para cada uno de los 3 problemas enumerados no es lo suficientemente buena para usted? – Roman

+0

Tengo mi propia implementación para construir/reemplazar, pero me encantaría escuchar sobre los patrones que otras personas han encontrado. – Frederik

+0

Puede que le resulte útil obtener una copia de Estructuras de datos puramente funcionales de Chris Okasaki. –

Respuesta

11

Aquí hay dos conceptos de interés. Primero, estructuras de datos persistentes. Si todos los elementos del árbol son inmutables, uno puede derivar un nuevo árbol del árbol original reemplazando algunas partes, pero haciendo referencia a las partes más antiguas, ahorrando así tiempo y memoria.

Por ejemplo, si añadiera un tercer puerto al nodo que ya tiene dos puertos, tendría que crear una nueva escena, un nuevo descendiente del nodo de escena y el nodo que está cambiando. El otro Nodo y todos los Puertos no necesitan ser creados de nuevo, solo se refieren a ellos en la nueva Escena/Nodos.

El otro concepto es el de una cremallera . Una cremallera es una forma de "navegar" a través de una estructura de datos persistente para optimizar los cambios locales.Por ejemplo, si agrega cuatro Puertos nuevos en lugar de uno solo, pero agregó cada Puerto de uno en uno, tendrá que crear cuatro Escenas nuevas y ocho Nodos nuevos. Con una cremallera, pospones esas creaciones hasta que termines, ahorrando en esos objetos intermedios.

La mejor explicación que he leído sobre la cremallera es here.

Ahora, el uso de una cremallera para navegar por una estructura de datos elimina la necesidad de tener enlaces de retroceso. Usted puede tener enlaces de retroceso en una estructura inmutable, mediante el uso inteligente de constructores recursivos. Sin embargo, dicha estructura de datos no sería persistente. Las estructuras de datos inmutables no persistentes tienen un rendimiento de modificación pésimo, porque necesita copiar la información completa cada vez.

En cuanto a literatura académica, recomiendo estructuras de datos puramente funcionales, por Okasaki (dissertation PDF, fully fledged book).

+2

+1 para ambos mencionando Zippers y Okasaki que, literalmente, escribieron el libro sobre este tema. Otro concepto interesante es la estructura de datos transitoria * de Clojure 1.1. (Básicamente, una estructura de datos temporalmente no persistente). De hecho, Clojure en general es interesante: si Okasaki escribió el libro sobre las estructuras de datos funcionales, Rich Hickey escribió la biblioteca. Y, por cierto: las estructuras de datos de Clojure están * específicamente * escritas de tal manera que * pueden * usarse como una biblioteca de Java. Son completamente independientes del lenguaje Clojure y la biblioteca estándar de Clojure. –

+0

El enlace a la publicación de la cremallera está roto, aquí hay uno en funcionamiento http://scienceblogs.com/goodmath/2010/01/13/zippers-making-functional-upda/ – Sergio

+0

@Sergio Gracias, corregido. –

3

Si el árbol es inmutable, a continuación, si desea cambiarlo en cualquier caso se tienen que producir un nuevo árbol.

Esto suena mal, pero no lo es si todos sus nodos también son inmutables. Como no necesita hacer copias de objetos inmutables, su nuevo árbol se referirá principalmente al árbol anterior, excepto por los cambios que realizó.

Tendrá que diseñar su árbol de tal manera que cada árbol inmutable se refiera a otros árboles inmutables. De esta forma, tampoco será necesario reproducir todo el árbol inmutable.

Pero si vas a la ruta de árbol inmutable, entonces no puedes tener enlaces de regreso. De lo contrario, no puede reutilizar árboles secundarios.

+0

Descubrí que el modelo se parece mucho al sistema de control de versiones de Git, donde el cambio de un archivo hace que el archivo, y por lo tanto el árbol y todos los árboles anteriores, cambien. Para los enlaces de la parte posterior, ¿no habría un enfoque de "alias" o "especificador de ruta" que se pueda resolver para una determinada versión de un árbol? – Frederik

+0

¿Qué quiere decir con enlaces de retroceso? Porque si un nodo se vincula con el padre y el padre cambia, tendrá que regenerar todos los nodos hijo y nieto, etc. Eso es mucho trabajo para un cambio. – Pyrolistical

+0

Bueno, un método getParent() sería un enlace de retroceso al padre del Nodo. Si un Nodo tuviera un atributo principal, no podría volver a utilizar el Nodo original. Me preguntaba si había una forma más inteligente de hacer esto, equivalente a los "enlaces simbólicos" de Unix, por ejemplo. – Frederik

Cuestiones relacionadas