2010-06-21 17 views
8

¿Cómo puede un valor de tipo:Cómo crear un valor de estructura de datos recursivo en (funcional) F #?

type Tree = 
    | Node of int * Tree list 

tener un valor que las referencias en sí genera de una manera funcional?

El valor resultante debe ser igual a x en el siguiente código Python, para una definición adecuada de árbol:

x = Tree() 
x.tlist = [x] 

Editar: Obviamente más explicación es necesaria. Estoy tratando de aprender F # y programación funcional, así que elegí implementar el cover tree que he programado anteriormente en otros idiomas. Lo relevante aquí es que los puntos de cada nivel son un subconjunto de los del siguiente nivel. La estructura conceptualmente va al nivel -infinito.

En idiomas imperativos, un nodo tiene una lista de hijos que se incluye a sí misma. Sé que esto puede hacerse imperativamente en F #. Y no, no crea un ciclo infinito dado el algoritmo del árbol de portada.

+0

¿Puede ampliar la pregunta, para aquellos de nosotros que no hablan mucho Python? La definición de tipo se ve bien, pero tenga en cuenta que F # no permite la construcción directa de un tipo de Unión discriminada (por ejemplo, Árbol) ... solo la construcción de los valores de unión; por ejemplo, Nodo, como en "dejar x = Nodo (0, [])" o "dejar y = Nodo (1, [x])", donde [] es la lista vacía. –

+0

@James: Quiere crear un nodo que sea su propio hijo. Me gusta 'let rec x = Node (something, [x])' (excepto que no funciona). – sepp2k

+1

@ sepp2k: Eso generalmente crearía un bucle infinito para cualquier código que recorriera la lista, aunque ...muy indeseable :-) He visto esto hecho en otros idiomas para marcar el final de una lista, pero idomatic F # usaría otro valor de DU, como "Ninguno", para ese propósito. –

Respuesta

9

La respuesta de Tomás sugiere dos formas posibles de crear estructuras de datos recursivas en F #. Una tercera posibilidad es aprovechar el hecho de que los campos de registro son compatibles con la recursión directa (cuando se utiliza en el mismo conjunto en el que se define el registro). Por ejemplo, el código siguiente funciona sin ningún problema:

type 'a lst = Nil | NonEmpty of 'a nelst 
and 'a nelst = { head : 'a; tail : 'a lst } 

let rec infList = NonEmpty { head = 1; tail = infList } 

El uso de este tipo de lista en lugar de la incorporada en uno, podemos hacer su trabajo código:

type Tree = Node of int * Tree lst 
let rec x = Node(1, NonEmpty { head = x; tail = Nil }) 
+0

Interesante. Conceptualmente, ¿sería la definición de 'a lst en algún sentido equivalente a una lista perezosa? –

+1

@Muhammad - No, ''a lst' no es flojo, así que es como la lista incorporada. El hecho de que pase por un registro le permite crear estructuras de datos autorreferenciales, pero no podría usar este tipo para hacer algo como crear una secuencia de todos los números pares como lo haría con una lista diferida. – kvb

+1

+1 Buen truco, ¡no sabía que esto fuera posible! –

7

No puede hacer esto directamente si la referencia recursiva no se retrasa (por ejemplo, envuelta en una función o en un valor diferido). Creo que la motivación es que no hay forma de crear el valor con referencias inmediatas "a la vez", por lo que sería incómodo desde el punto de vista teórico.

Sin embargo, F # admite valores recursivos; puede usarlos si la referencia recursiva se retrasa (el compilador F # generará luego un código que inicializa la estructura de datos y rellena las referencias recursivas). La forma más fácil es envolver el refernece dentro de un valor perezoso (función funcionaría también):

type Tree = 
    | Node of int * Lazy<Tree list> 

// Note you need 'let rec' here!   
let rec t = Node(0, lazy [t; t;]) 

Otra opción es escribir esto usando mutación. Entonces también necesita hacer que su estructura de datos sea mutable. Por ejemplo, puede almacenar ref<Tree> en lugar de Tree:

type Tree = 
    | Node of int * ref<Tree> list 

// empty node that is used only for initializataion 
let empty = Node(0, []) 
// create two references that will be mutated after creation  
let a, b = ref empty, ref empty 

// create a new node 
let t = Node(0, [a; b]) 
// replace empty node with recursive reference 
a := t; b := t 

Como se mencionó James, si no se nos permite hacer esto, usted puede tener algunas propiedades agradables como que cualquier programa que recorre la estructura de datos terminará (porque la estructura de datos es limitada y no puede ser recursiva). Por lo tanto, tendrá que ser un poco más cuidadoso con los valores recursivos :-)

+0

Buena respuesta. Gracias. Desde un punto de vista de la eficiencia, ¿es uno mejor que el otro? (De alguna manera siento que me arrepentiré;) –

+1

'Lazy' es estrictamente más complicado que' ref' (tiene que rastrear si ha sido evaluado o no), por lo que 'ref' debería ser más eficiente, pero sospecho que no hay real diferencia. –

Cuestiones relacionadas