uso manejar listas estructura ejercicio definir datos como binarios binario arboles arbol algoritmo haskell monads reader-monad

manejar - Haskell: profundidad para cada nodo en árbol binario utilizando la mónada Reader



ejercicio arboles binarios (2)

No es necesario entrar y salir del Reader la manera que lo hace aquí usando runReader ; en cambio, puedes reescribirlo como

renumberR :: Tree a -> Reader Int (Tree Int) renumberR (Node _ l r) = do x <- ask l'' <- local (+1) (renumberR l) r'' <- local (+1) (renumberR r) return (Node x l'' r'') renumberR Empty = return Empty

Sin embargo, puedes escribirlo aún mejor usando solo la interfaz de Reader :

renumberR (Node _ l r) = Node <$> ask <*> local (+1) (renumberR l) <*> local (+1) (renumberR r) renumberR Empty = pure Empty

Tenga en cuenta que he cambiado el nombre de su función para renumberR para enfatizar el hecho de que se ejecuta en Reader , pero no necesariamente utilizando su interfaz monádica.

Escribí el siguiente código. Está funcionando y usando la mónada Reader .

¿Podría darme algunas pistas sobre el estilo del código en Haskell? Principalmente, me refiero a mónadas: soy novato.

import Control.Monad.Reader data Tree a = Node a (Tree a) (Tree a) | Empty renumberM :: Tree a -> Reader Int (Tree Int) renumberM (Node _ l r) = ask >>= (/x -> return (Node x (runReader (local (+1) (renumberM l)) x) (runReader (local (+1) (renumberM r)) x))) renumberM Empty = return Empty renumber'''' :: Tree a -> Tree Int renumber'''' t = runReader (renumberM t) 0


Quiero mostrarte que tu idea es una instancia de un concepto más general: compresión . Aquí hay una versión de su programa que emplea un estilo más simple y más funcional.

Funcionantes Aplicativos

Aquí está la definición de Applicative :

class Functor f => Applicative f where pure :: a -> f a (<*>) :: f (a -> b) -> f a -> f b

Se podría decir que un tipo fx es una estructura f contiene algunos valores x . La función <*> toma una estructura de funciones ( f (a -> b) ) y la aplica a una estructura de argumentos ( fa ) para producir una estructura de resultados ( fb ).

Aplicadores Zippy

Una forma de hacer de Tree un funtor aplicativo es haciendo <*> atravesar los dos árboles en el paso de bloqueo, comprimiéndolos juntos como lo hace zip con las listas. Cada vez que encuentre un Node en el árbol de funciones y un Node en el árbol de argumentos, puede sacar la función y aplicarla al argumento. Tienes que dejar de atravesar cuando llegues al final de cualquiera de los árboles.

instance Applicative Tree where pure x = let t = Node x t t in t Empty <*> _ = Empty _ <*> Empty = Empty (Node f lf rf) <*> (Node x lx rx) = Node (f x) (lf <*> lx) (rf <*> rx) instance Functor Tree where fmap f x = pure f <*> x -- as usual

pure x genera un árbol infinito de x s. Esto funciona bien porque Haskell es un lenguaje perezoso.

+-----x-----+ | | +--x--+ +--x--+ | | | | +-x-+ +-x-+ +-x-+ +-x-+ | | | | | | | | etc

Entonces la forma del árbol t <*> pure x es la misma que la forma de t : solo dejas de atravesar cuando encuentras un Empty , y no hay ninguno en pure x . (Lo mismo se aplica a pure x <*> t )

Esta es una forma común de hacer que una estructura de datos sea una instancia de Applicative . Por ejemplo, la biblioteca estándar incluye ZipList , cuya instancia Applicative es muy similar a la de nuestro árbol:

newtype ZipList a = ZipList { getZipList :: [a] } instance Applicative ZipList where pure x = ZipList (repeat x) ZipList fs <*> ZipList xs = ZipList (zipWith ($) fs xs)

Una vez más, pure genera un ZipList infinito y <*> consume sus argumentos en lock-step.

El zippy prototipo Aplicable, si lo desea, es el "lector" Aplicativo (->) r , que combina funciones al aplicarlas todas a un argumento fijo y recoger los resultados. Por lo tanto, todos los funtores Representable admiten (al menos) una instancia Applicable rápida.

Utilizando alguna maquinaria Aplicable , podemos generalizar el archivo zip del Prelude a cualquier funcionador aplicativo (aunque solo se comportará exactamente como zip cuando el Applicative sea ​​de naturaleza zippy; por ejemplo, con la instancia Applicative regular para [] zipA le dará el Cartesiano producto de sus argumentos).

zipA :: Applicative f => f a -> f b -> f (a, b) zipA = liftA2 (,)

Etiquetado como Zipping

El plan es comprimir el árbol de entrada junto con un árbol infinito que contenga la profundidad de cada nivel. El resultado será un árbol con la misma forma que el árbol de entrada (porque el árbol de profundidades es infinito), pero cada nodo se etiquetará con su profundidad.

depths :: Tree Integer depths = go 0 where go n = let t = go (n+1) in Node n t t

Así es como se ve la depths :

+-----0-----+ | | +--1--+ +--1--+ | | | | +-2-+ +-2-+ +-2-+ +-2-+ | | | | | | | | etc

Ahora que hemos configurado las estructuras que necesitamos, etiquetar un árbol es fácil.

labelDepths :: Tree a -> Tree (Integer, a) labelDepths = zipA depths

Volver a etiquetar un árbol tirando las etiquetas originales, como lo especificó originalmente, también es fácil .

relabelDepths :: Tree a -> Tree Integer relabelDepths t = t *> depths

Una prueba rápida:

ghci> let myT = Node ''x'' (Node ''y'' (Node ''z'' Empty Empty) (Node ''a'' Empty Empty)) (Node ''b'' Empty Empty) ghci> labelDepths myT Node (0,''x'') (Node (1,''y'') (Node (2,''z'') Empty Empty) (Node (2,''a'') Empty Empty)) (Node (1,''b'') Empty Empty) +--''x''-+ +--(0,''x'')-+ | | labelDepths | | +-''y''-+ ''b'' ~~> +-(1,''y'')-+ (1,''b'') | | | | ''z'' ''a'' (2,''z'') (2,''a'')

Puede idear diferentes esquemas de etiquetado variando el árbol que recorre. Aquí hay una que te dice el camino que tomaste para llegar a un nodo:

data Step = L | R type Path = [Step] paths :: Tree Path paths = go [] where go path = Node path (go (path ++ [L])) (go (path ++ [R])) +--------[ ]--------+ | | +---[L]---+ +---[R]---+ | | | | +-[L,L]-+ +-[L,R]-+ +-[R,L]-+ +-[R,R]-+ | | | | | | | | etc

(El anidamiento ineficiente de llamadas a ++ arriba puede mitigarse usando listas de diferencias ).

labelPath :: Tree a -> Tree (Path, a) labelPath = zipA paths

A medida que continúe aprendiendo Haskell, obtendrá una mejor detección cuando un programa sea un ejemplo de un concepto más profundo. La configuración de estructuras generales, como hice con la instancia Applicative anterior, rápidamente paga dividendos en la reutilización del código.