2010-06-21 12 views
8

Come può un valore di tipo:Come creare un valore di struttura dati ricorsivo in (funzionale) F #?

type Tree = 
    | Node of int * Tree list 

avere un valore che fa riferimento sé generato in modo funzionale?

Il valore risultante dovrebbe essere uguale a x nel seguente codice Python per un adeguato definizione di albero:

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

Edit: Ovviamente più spiegazione è necessaria. Sto cercando di imparare F # e la programmazione funzionale, quindi ho scelto di implementare lo cover tree che ho programmato prima in altre lingue. La cosa rilevante qui è che i punti di ogni livello sono un sottoinsieme di quelli del livello sottostante. La struttura concettualmente va al livello -infinità.

In lingue imperative un nodo ha una lista di bambini che include se stessa. So che questo può essere fatto imperativamente in F #. E no, non crea un ciclo infinito dato l'algoritmo dell'albero della copertura.

+0

Potete approfondire la questione, per quelli di noi che non parlano molto Python? La definizione del tuo tipo sembra soddisfacente, ma nota che F # non consente la costruzione diretta di un tipo di Unione discriminata (ad es., Albero) ... solo la costruzione dei valori di unione; ad es., Nodo, come in "let x = Node (0, [])" o "let y = Node (1, [x])", dove [] è la lista vuota. –

+0

@James: Vuole creare un nodo che sia figlio suo. Come 'let rec x = Node (something, [x])' (eccetto che non funziona). – sepp2k

+1

@ sepp2k: Generalmente questo crea un ciclo infinito per qualsiasi codice che passi l'elenco, anche se ...altamente indesiderabile :-) Ho visto questo fatto in altre lingue per segnare la fine di una lista, ma F # idomatic userebbe un altro valore DU, come "Nessuno", per quello scopo. –

risposta

9

La risposta di Tomas suggerisce due possibili modi per creare strutture dati ricorsive in F #. Una terza possibilità consiste nel trarre vantaggio dal fatto che i campi dei record supportano la ricorsione diretta (se utilizzata nello stesso assembly in cui è definito il record). Per esempio, il seguente codice funziona senza alcun 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 } 

Usando questo tipo di elenco invece del built-in uno, siamo in grado di rendere il vostro lavoro Codice:

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

Interessante. Concettualmente, la definizione di 'a lst sarebbe in un certo senso equivalente a una lista pigra? –

+1

@Muhammad - No, ''a lst' non è pigro, quindi è proprio come la lista built-in. Il fatto che passi attraverso un record consente di creare strutture di dati autoreferenziali, ma non è possibile utilizzare questo tipo per fare qualcosa come la creazione di un flusso di tutti i numeri pari come si potrebbe con un elenco pigro. – kvb

+1

+1 Bel trucco, non sapevo che fosse possibile! –

7

Non è possibile eseguire questa operazione direttamente se il riferimento ricorsivo non viene ritardato (ad esempio, avvolto in una funzione o un valore lazy). Penso che la motivazione sia che non c'è modo di creare il valore con riferimenti immediati "alla volta", quindi questo sarebbe imbarazzante dal punto di vista teorico.

Tuttavia, F # supporta i valori ricorsivi - è possibile utilizzare quelli se il riferimento ricorsivo è ritardato (il compilatore F # genererà quindi del codice che inizializza la struttura dati e riempie i riferimenti ricorsivi). Il modo più semplice è quello di avvolgere la refernece all'interno un valore pigro (funzione avrebbe funzionato troppo):

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

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

Un'altra opzione è quella di scrivere questo utilizzando mutazione. Quindi è anche necessario rendere mutevole la struttura dei dati. Ad esempio è possibile memorizzare ref<Tree> invece di 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 

Come James detto, se non ti è permesso di fare questo, si può avere alcune proprietà piacevoli come ad esempio che qualsiasi programma che cammina la struttura dei dati terminerà (perché il data-structrue è limitato e non può essere ricorsivo). Quindi, dovrai essere un po 'più attento con i valori ricorsivi :-)

+0

Bella risposta. Grazie. Da un punto di vista dell'efficienza, è uno migliore dell'altro? (In qualche modo sento che me ne pentirò;) –

+1

'Lazy' è strettamente più complicato di' ref' (deve tracciare se è stato valutato o meno), quindi 'ref' dovrebbe essere più efficiente, ma sospetto che non ci sia differenza. –

Problemi correlati