2010-05-15 14 views
9

Dire che ho il seguente tipo albero di Haskell, dove "Stato" è un semplice involucro:Come generare un albero in modo funzionale. (Con Haskell)

data Tree a = Branch (State a) [Tree a] 
      | Leaf (State a) 
      deriving (Eq, Show) 

Ho anche una funzione di "espandere :: albero a -> albero un" che prende un nodo foglia, e lo espande in un ramo, o prende un ramo e lo restituisce inalterato. Questo tipo di albero rappresenta un albero di ricerca N-ario.

La ricerca della profondità prima è uno spreco, poiché lo spazio di ricerca è ovviamente infinito, poiché posso facilmente continuare ad espandere lo spazio di ricerca con l'uso di espandere su tutti i nodi foglia dell'albero e le possibilità di perdere accidentalmente lo stato-obiettivo è enorme ... quindi l'unica soluzione è una ricerca di ampiezza, implementata in modo piuttosto decente su here, che troverà la soluzione se è lì.

Quello che voglio per generare, però, è l'albero attraversato fino al trovare la soluzione. Questo è un problema perché so solo come fare questo depth-first, che potrebbe essere fatto semplicemente chiamando la funzione "expand" ancora e ancora sul primo nodo figlio ... finché non viene trovato uno stato-obiettivo. (Questo in realtà non genererebbe niente di diverso da una lista davvero scomoda.)

Qualcuno potrebbe darmi qualche suggerimento su come fare questo (o un intero algoritmo), o un verdetto sul fatto se sia possibile o meno con una complessità decente ? (O qualche fonte su questo, perché ho trovato piuttosto pochi.)

+0

Come parte, potresti voler usare qualcosa di diverso da "Stato" lì, poiché quel nome è usato nelle librerie standard per la monade di Stato, che è suscettibile di confondere le persone. –

+0

Mi sono reso conto che proprio ora mentre stavo usando la monade di stato per implementare il mio algoritmo, sulla base del consiglio dato qui. – wen

risposta

9

Hai guardato Chrisdi Chris Okasaki? Il modulo Data.Tree include un generatore di alberi monadici denominato unfoldTreeM_BF che utilizza un algoritmo adattato da tale documento.

Ecco un esempio che credo corrisponda a quello che stai facendo:

Supponiamo che voglio cercare un albero binario infinito di stringhe in cui tutti i bambini di sinistra sono la stringa genitore più "a", e il diritto i bambini sono i genitori più "bb".Potrei usare unfoldTreeM_BF per cercare l'albero in ampiezza e restituire l'albero cercato fino alla soluzione:

import Control.Monad.State 
import Data.Tree 

children :: String -> [String] 
children x = [x ++ "a", x ++ "bb"] 

expand query x = do 
    found <- get 
    if found 
    then return (x, []) 
    else do 
     let (before, after) = break (==query) $ children x 
     if null after 
     then return (x, before) 
     else do 
      put True 
      return (x, before ++ [head after]) 

searchBF query = (evalState $ unfoldTreeM_BF (expand query) []) False 

printSearchBF = drawTree . searchBF 

Questo non è molto bella, ma funziona. Se cerco "AABB" ottengo esattamente quello che voglio:

| 
+- a 
| | 
| +- aa 
| | | 
| | +- aaa 
| | | 
| | `- aabb 
| | 
| `- abb 
| 
`- bb 
    | 
    +- bba 
    | 
    `- bbbb 

Se questo è il genere di cose che stai descrivendo, non dovrebbe essere difficile adattarsi per il vostro tipo di albero.

UPDATE: Ecco una versione gratuita-do di expand, nel caso in cui siete in questo genere di cose:.

expand q x = liftM ((,) x) $ get >>= expandChildren 
    where 
    checkChildren (before, []) = return before 
    checkChildren (before, t:_) = put True >> return (before ++ [t]) 

    expandChildren True = return [] 
    expandChildren _  = checkChildren $ break (==q) $ children x 

(Grazie a camccann per me incitamento lontano da vecchie abitudini di struttura di controllo Spero che questo la versione è più accettabile.)

+1

Caro Dio, quel codice, io ... io ... ma ... cosa stai * facendo * a quella monade di Stato povera? Sei un mostro! –

+0

Sì, è brutto, ma era fuori di testa. Come lo faresti? Devo ammettere che sono un principiante, ma non vedo un modo per dire unfoldTreeM_BF per fermare l'espansione dei bambini senza Stato. –

+0

Ok, ho risolto la cosa stupida che stavo facendo con la query in State, ma penso ancora che ne ho bisogno per sapere quando fermarmi. Non è esattamente questo il motivo per cui il costruttore di alberi è monadico in primo luogo? –

5

Sono curioso di sapere perché è necessaria la funzione expand - perché non semplicemente costruire l'intero albero in modo ricorsivo ed eseguire qualsiasi ricerca tu voglia?

Se si utilizza expand per tenere traccia dei nodi esaminati dalla ricerca, la creazione di un elenco come si va sembra più semplice o anche una seconda struttura ad albero.

Ecco un rapido esempio che restituisce solo il primo risultato che trova, con la spuria Leaf costruttore rimosso:

data State a = State { getState :: a } deriving (Eq, Show) 

data Tree a = Branch { 
    state :: State a, 
    children :: [Tree a] 
    } deriving (Eq, Show) 

breadth ts = map (getState . state) ts ++ breadth (concatMap children ts) 
search f t = head $ filter f (breadth [t]) 

mkTree n = Branch (State n) (map mkTree [n, 2*n .. n*n]) 

testTree = mkTree 2 

provarlo in GHCi:

> search (== 24) testTree 
24 

Per contrasto, ecco un ingenuo ricerca approfondita:

depth (Branch (State x) ts) = x : (concatMap depth ts) 
dSearch f t = head $ filter f (depth t) 

... che naturalmente non riescono s per terminare durante la ricerca con (== 24), perché i rami più a sinistra sono una serie infinita di 2 secondi.

+4

Solo per sillabare: il "trucco" per eseguire in modo funzionale la ricerca in ampiezza è iterare su un elenco di alberi, non su un singolo albero. Potrebbe essere utile aggiungere annotazioni di tipo alle funzioni di livello superiore e cercare qui. – MtnViewMark

+0

Capisco come funziona questa soluzione orientata al livello per l'attraversamento di BF, ma ciò che Dennetik desidera è l'albero esaminato fino al punto che la soluzione viene trovata. Questo sembra approssimativamente equivalente alla numerazione BF, e sono a mia conoscenza che gli approcci orientati al livello non sono facili da estendere alla numerazione, mentre l'approccio basato sulla coda di Okasaki è. Ecco perché ho usato 'unfoldTreeM_BF' nella mia risposta. Mi manca qualcosa? Puoi approfondire come recuperare l'albero esaminato con il tuo approccio? –

+0

Stavo usando la funzione "expand" perché ho lavorato con Java per troppo tempo e ho dimenticato di poter contare su una valutazione lazy per lavorare su alberi infiniti. Grazie per avermelo ricordato - poiché questo è quello che raccolgo il tuo codice? (O sono semplicemente sciocco.) – wen

Problemi correlati