Evaluación Perezosa
Evaluación Perezosa
Evaluación Perezosa
EVALUACIN PEREZOSA
Comencemos la evaluacin de la expresin cuadrado (3 + 4).
La secuencia de reduccin es:
Cuadrado (3+4)
= {definicin de +}
Cuadrado 7
= {definicin de cuadrado}
7 x7
= {definicin de c}
49
Otra opcin
Cuadrado (3 + 4)
= {definicin de cuadrado}
(3 + 4) x (3 + 4)
= {definicin de +}
7 x (3 + 4)
= {definicin de +}
7x7
= {definicin de x}
49
Estas ilustran las estrategias de reduccin, llamadas respectivamente ms interna
y ms externa.
Otro ejemplo:
Fst (cuadrado 4, cuadrado 2)
= {definicin de cuadrado}
Fst (4 x 4, cuadrado 2)
= {definicin de x}
Fst (16, cuadrado 2)
= {definicin de cuadrado}
Fst (16, 2 x 2)
= {definicin de x}
Fst (16,4)
= {definicin de fst}
16
La estrategia de reduccin ms externa para la misma expresin produce
=0 fib 1 =1
El tiempo para evaluar fib n mediante estas ecuaciones viene dado por T(fib)(ri),
donde:
T(fib)(0)
= 0(1)
T(fib)(l) = 0(1)
T(fib)(n + 2) = T(fib)(ri) + T{fib)(n + 1) + 0(1)
La funcin T(fib) satisface por tanto ecuaciones muy similares a las de fib. Es fcil
comprobar por induccin que T(fib)(n) = (fib n), por tanto el tiempo para calcular
fib es proporcional al tamao del resultado. Dado que fib n = 0(3>n), donde $ es la
razn urea $ = (1 + >/5)/2, el tiempo es exponencial en n.
Consideremos ahora la funcin fibDos definida mediante
fibDos n = (fib n, fib (n+ 1))
Claramente, fib n = fst (fibDos n). La sntesis de un programa recursivo para
fibDos proporciona:
fibDos O
= (0,1)
LLENANDO PRRAFOS
Los ejemplos anteriores tupiaban dos funciones, pero se puede utilizar la misma
idea con buenos resultados tupiando una lista de funciones. En esta versin, la
tcnica de tuplamiento recibe usualmente el nombre de tabulacin. Se nos da una
lista ws de palabras y queremos calcular una lista de lneas wss tal que ws =
concat wss, con la restriccin adicional de que cada lnea de wss debera tener
como mximo una longitud dada y adems wss debe minimizar cierta nocin de
desperdicio. Para simplificar, supondremos que se determina el desperdicio
mediante una funcin numrica dada desp que toma el valor oo si el prrafo
contiene una lnea que no cabe en la anchura dada.
La definicin de llenar toma la forma:
llenar :: [Palabra] [Linea] llenar = mejor llenados
mejor :: [[Linea]] -~[Linea] mejor = foldll mejor Que
where mejorQue uss vss = if desp uss < desp vss then uss else vss
:: [Palabra] [[Linea]]
llenados []
= [[ ]]
llenados (w: ws) = [us : vss \ (us,vs) descomp (w: ws), vs llenados vs]
Se especifica la funcin descomp mediante la condicin de que si xs es una lista
no vaca, entonces descomp xs devuelve la lista de todos los pares (us, vs) tales
que us ++ vs = xs y us es no vaca. Se puede definir la funcin descomp de varias
maneras; una de ellas es la siguiente:
descomp
descomp (w: ws) = zip (map (w.) (inits ws)) (tails ws)
Aqu, inits ws devuelve la lista de los segmentos iniciales de ws en orden creciente
de longitud, y tails ws devuelve los segmentos finales en orden decreciente de
longitud. Por tanto, la funcin descomp devuelve la lista de todas las formas
posibles de partir la entrada de manera que el primer componente de cada par sea
una lista no vaca.
Para mejorar el programa, necesitamos la suposicin adicional
mejor -map {us :) = (us:) - mejor
Dicho con palabras: fijada una primera lnea, se obtiene el mejor prrafo tomando
el mejor prrafo para las siguientes lneas. Esta condicin es una aplicacin de lo
que en los textos de diseo de algoritmos se conoce como principio de
optimalidad.
Dada la suposicin anterior, es fcil comprobar que llenar se puede definir
mediante
llenar [ ]
=[]
llenar (w: ws) = mejor [LZS : llenar vs \ (us, vs) -descomp (w: ws)]
Suponiendo que tanto mejor como descomp emplean tiempo lineal, tenemos
T(llenar)(0) = 0(1)
T(llenar)(n +1) = 0(n) + Xk=o T(llenar)(k)
La solucin de esta relacin de recurrencia es T(llenar)(n) = 0(2"); por tanto, llenar
necesita todava tiempo exponencial. La razn es que, para toda secuencia final
de ws, se recalcula llenar vs. La situacin es muy similar a la de la funcin de
Fibonacci, excepto que en vez de tener dos trminos recursivos, tenemos una lista
de ellos. La solucin es similar: definimos llenarColas mediante:
llenarColas = map llenar tails
Tenemos que llenar = head llenarColas; por tanto, se puede calcular llenar
calculando llenarColas. Slo resta dar un programa recursivo para llenarColas. El
caso base:
llenarColas [ ] = [[ ]]
Es fcil, y para el caso recursivo argumentamos primero que llenar
(w: ws) = {definicin de llenar}
=
{definicin de descomp}
mejor [us: llenar vs \ (us, vs) zip (map (w:) (inits ws)) (tails ws)]
=
{conjetura}
mejor [(w : us) : vss \ (us, vss) zip (inits ws) (map llenar (tails ws))]
=
{definicin de llenarColas}
mejor [(w: us) : vss \ (us, vss) *-zip (inits ws) (llenarColas ws)]
La demostracin de la conjetura empleada se deja como ejercicio. Ahora
podemos deducir
llenarColas (w: ws)
{definicin de llenarColas, ms algunas simplificaciones de rutina} llenar (w: ws) :
llenarColas ws =
{por el clculo anterior de llenar, definiendo fts = llenarColas
ws}
mejor [(w: us) : vss \ (us, vss) zip (inits ws) fts)] : fts.
LA MEDIA ARITMTICA.
El ltimo ejemplo para ilustrar la tcnica de tuplamiento consigue una mejora de
eficiencia ms modesta. Se define la funcin media para listas no vacas de
nmeros mediante:
media :: [Float] -~Float media xs = (sum xs)/(length xs)
La evaluacin de media necesita dos recorridos del argumento, pero mediante
tupia- miento podemos conseguir que se haga slo uno. Definimos sumaLong
mediante
sumaLong xs = (sum xs, length xs)
= (x, 1)
sumaLong (x: y: xs) = (x+ z, n + 1), where (z, n) = sumaLong (y: xs)
CONTROLANDO EL ESPACIO.
Consideremos la reduccin del trmino sum [1..1000], donde sum = foldl (+) 0:
sum [1..1000]
= foldl (+) 0 [1..1000]
= foldl (+) (0 + 1) [2..1000]
= foldl (+) ((0 + 1) + 2) [3..1000]
= foldl (+) (...((0 + 1) + 2) + ...+ 1000) []
= (...((0 + 1) + 2) + ...+ 1000)
= 500500
El punto a resaltar es que, al calcular sum [1..] mediante reduccin ms externa,
la expresin crece hasta un tamao proporcional a n. Por otra parte, si utilizamos
una mezcla juiciosa de pasos de reduccin ms externa y ms interna, entonces
podemos obtener la siguiente secuencia de reduccin:
sum [1..1000]
= foldl (+) 0 [1..1000]
= foldl (+) (0 + 1) [2..1000]
= foldl (+) 1 [2..1000]
= foldl (+) (1 + 2) [3..1000]
= foldl (+) 3 [3..1000]
= foldl (+) 500500 []
= 500500
Resumiendo, al reducir sum [l..n] a forma normal mediante reduccin ms externa
pura necesitamos espacio en D(n), mientras que una combinacin de reduccin
ms externa y ms interna requiere solamente espacio en 0(1).
FORMA NORMAL DBIL Y LA FUNCIN STRICT.
((8 X 5) + 1) + 1
(40+l) + l
41 + 1
42
=a
sfoldl () a (x: xs) = strict {sfoldl ()) {a e x) xs Siendo sum = sfoldl (+) 0
tenemos ahora:
sum [1..1000]
= sfoldl (+) 0 [1..1000]
= strict (sfoldl (+)) (0 + 1) [2..1000]
CONCLUSION:
La evaluacin perezosa, nos da a entender de que no se evala nada hasta tanto
deba ser avaluado, esto permite que uno puede, por ejemplo, definir una lista
infinita de primos sin caer en una recursin infinita. Slo los elementos de esta
lista que sean realmente usados sern computados. La evaluacin perezosa hace
esta operacin de manera ms limpia.