Você está na página 1de 14

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II

Una vez evitado el molesto problema del apartado eliminatorio (que luego resultaba no serlo) tipo test y como
he conseguido aprobar la asignatura después de muchos pesares, quiero transmitir al resto de los alumnos que
han sufrido como yo esta asignatura unas pinceladas que los ayude en el mal trago del examen.

Este texto se encuentra dirigido a aquellos que ya se han estrellado contra la asignatura. Los novatos pueden
usarlo también, por supuesto.

Vamos a suponer que el examen consiste básicamente en el desarrollo, como en la práctica, de un supuesto. Y
que en dicho supuesto hay un vector.

RECURSIVIDAD

Especificación de la función:
Consiste en una terna QSR en la que:
Q es la precondición. Lo que deben cumplir los datos de entrada.
S es la definición (exclusivamente) de la función. Debe tener variables de entrada y de salida.
R es la postcondición. Las condiciones que debe cumplir la (las) variables de salida.

Q es Q(x) siendo x las variables de entrada.


R es R(x,y) siendo x las variables de entrada e “y” la variable de salida.
S puede ser:
Función: (fun) si devuelve un valor.
Acción: (accion) si existe alguna variable que sea de entrada Y salida.

No pide nada más. Pero empiezan tus problemas:


R debe ser exacto. Si te equivocas, por la regla del dominó, no vas a tener nada bien. Así que míralo bien y
compruébalo dos veces antes de seguir.
En primer lugar comprueba que el tipo de datos coincide con el especificado en la salida. Si es binario
(CIERTO, FALSO), tienes que establecer unos criterios lógicos (algo es igual o distinto a otra cosa, Y/O etc.).
Si es natural comprueba que haces cálculos en el ámbito de los naturales (div en lugar de /). También procura
no mezclar enteros con naturales, y por supuesto, cuidado con mezclar ambos con los reales.
Pero, como suponemos que hay un vector que recorrer, no es tan difícil:
a) Encontrar algo. Responder cierto si...
R(x,y)≡{b=(∃ α∈ [1..n]· α cumple la condición)} b es binario.
b) Demostrar que se cumple algo. Responder cierto si...
R(x,y)≡{b=(∀ α∈ [1..n]· α cumple la condición} b es binario.
c) Contar los elementos que cumplen la condición.
R(x,y)≡{s=Ν α (desde 1 hasta n) · α cumple la condición} s es natural.
d) Sumar elementos del vector.
R(x,y)≡{s=Σ α (desde 1 hasta n) } s depende del tipo de los datos del vector.
e) Multiplicar elementos del vector.
R(x,y)≡{s=Π α (desde 1 hasta n) } s depende del tipo de los datos del vector.

Q debe ser lo menos restrictivo posible. Y ahí entran las opiniones. Si el equipo docente de la asignatura decide
que has sido demasiado restrictivo, mal comienzas:
Si un número es natural, poner que es mayor o igual que cero es una tontería. Mejor no ponerlo.
Si un número es natural, (CARDINAL) o entero (INTEGER), poner que tiene unos límites (MAXINT,
MAXCARD) es demasiado apegado a la práctica y aquí somos muy teóricos.

Prueba con “verdadero”. Y piensa en qué trampa vas a caer. ¿Realmente hay alguna condición que deben
cumplir los parámetros de entrada?.

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II Página 1 de 14


Primera inmersión o inmersión no final:

La especificación no vale para nada, y nadie le pide que haga nada, porque no es posible. Así que vamos a
“sumergirla” (inmersión) para que aprenda a bucear. Es decir, vamos a convertirla en recursiva.

Para eso necesitamos una variable que no está.

Para ello necesitamos reforzar la poscondición (R) debilitándola. Oficialmente y para todos los efectos la
estamos debilitando. Que el libro [Peña97] parezca contradecirse a sí mismo es sólo una circunstancia en la que
no vamos a entrar (*). DEBILITAR.

Y debilitar significa coger ese (“existe, para todo, conteo, suma o producto” desde el primero hasta el último) y
convertirlo en otro desde el primero hasta una variable i.
¿Que usaste ya i precisamente en eso?. ¡Qué falta de previsión!. Vale. Usaremos c.
Por tanto:
R’≡(R debilitada) ∧ (c=n)
Aunque R’ ,R y Rdebilitada son incomparables por lógica (según [Peña97]**), la lógica aquí vale poco. A
todos los efectos R’ y R son equivalentes.
¿Qué pasa con S?. Debemos reescribir S incluyendo la variable c.
¿Qué pasa con Q?. Tenemos que reforzarla (esta vez sí) incluyendo una limitación a c.
Pongamos que Q’ ≡ Q ∧ (c<=n).
NO es la única solución. Pero al equipo docente de la asignatura le gusta esta. Tanto que muy pocos se atreven
a hacerlo de otra manera. Por si acaso, vamos a hacerlo así.

¿Qué tipo le has dado a c?. ¿Natural?. Es natural que lo hagas. Pero no te confíes. En su momento puede que
tengas que romperlo todo y empezar de nuevo.

Análisis por casos:

En el análisis por casos tienes que hacer tres cosas.

La primera es decidir qué es Bt. En castellano el caso trivial.

En las prácticas, el rango de la matriz va desde 1 hasta n [1..n].


El caso trivial es cero (0).
En los exámenes el rango de la matriz suele ir desde 0 hasta n.
Así que el caso trivial es menor que cero.
Lo que programando, si has definido c como natural te provoca un bonito código de error.
Mejor define c como entero en lugar de natural.

Pero cuidado con hacerlo al revés.


Si has definido c<=n y pones como caso trivial c>n acabas de estrellarte contra la ruda realidad. Suspenso. Y
eso que, te lo adelanto ya, no vas a detectar el error con la “famosa” verificación formal de programas
recursivos.
¿Que porqué?. Porque aunque nadie te lo haya dicho, Bt y Q’ tienen que tener algo en común. Bt ∧ Q’ ≠ falso.
Obligatoriamente. Cuando lleguemos a Bt aún debemos estar en Q’.
Una vez que has decidido qué es Bt puedes ampliarlo a tu conveniencia. Con tal de que el Bt original esté en el
rango que has creado para él. Si es cero, c=0 no hay ningún inconveniente en poner c<1.
Por supuesto el caso trivial conduce al elemento neutro de la operación. 0 en conteo y suma, 1 en productos y
potencias, falso (elemento neutro de la conjunción) si en R tenemos un ∀ o cierto (elemento neutro de la
disyunción) si tenemos en R un ∃. Esto es triv(x).

Lo segundo es decidir qué se hace en Bnt. Caso no trivial.


Se supone que se examina el caso concreto. El momento c. Y se llama recursivamente a la función variando c
por c-1 (lo habitual), o c+1 (invertido).

Y lo tercero es decidir cual es la llamada correcta a la función. ¿Cual es el valor Cini?.

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II Página 2 de 14


“Evidentemente” es el otro extremo del vector. Si Bt es cero, Cini es n.

¿Está claro?. Repito:


“c” recorre el vector hasta salirse. Y se tiene que salir, así que en Q tienes que dejarle espacio.
Si el vector va desde 1 hasta n se sale en 0 o en n+1.
Si el vector va desde 0 hasta n se sale en -1 o en n+1.
COMPRUEBA Q. Y CAMBIA LO QUE SEA PRECISO.

Así pues tenemos:


Q’(x)≡Q(x) ∧ (c<=n)
S’≡ fun iLoQueSea (x variables de entradas, c entero (o natural)): dev resultado.
Caso c<extremo inferior → elemento neutro de la operación
c>=extremo inferior → “análisis para el momento c” (OP) iLoQueSea (x,c-1)
R’(x,y) ≡ Rdebilitada ∧ (c=n)
(El extremo inferior es el rango más pequeño del vector. Si va “desde 1 hasta n” el extremo inferior es 1. Por
tanto Bt es c<1)

Si en R tenemos un ∀ la operación OP es Y (∧). Si tenemos un ∃ la operación es O (∨).


En el resto de los casos, habitualmente suma, producto.

Verificación formal de la inmersión no final.

Francamente, y ahora que no nos oye el equipo docente de la asignatura: esto tiene más agujeros que un queso
Emmental (que es el de los agujeros. El gruyére es como el queso de barra, con unos agujeritos muy pequeños).

En primer lugar unas consideraciones sobre ⇒.


Significa que el segundo predicado es más débil que el primero. Es decir, que el primero es más restrictivo que
el segundo (más fuerte).
a∧b⇒a. Por supuesto, a∧b⇒b. (1)
a⇒a∨b. Por supuesto, b⇒a∨b (2)
Falso ⇒ cualquiercosa. Pero olvídalo. Si te sale falso vas bastante mal.
Cualquiercosa ⇒ cierto. Y esto sí lo vamos a usar. (3)

1.- Completitud de la alternativa.


Q(x) ⇒Bt ∨ Bnt.
En Q(x) por (1) tomamos sólo la parte de c. Si te sale algo así como (c<n) ⇒ (c>n) ∨ (c<=n ), es decir, (c<n)
⇒cierto, es verdad. Y estás suspenso.
¿Te tengo que decir porqué?. Porque (c<n) y (c>n) no tienen un Bt común. Porque cuando llamas a c>n no se
cumple la precondición.
Sin embargo (c<=n) ⇒ (c<1)∨ (c>=1), es otra vez (c<=n)⇒cierto y apruebas.

2.- Satisfacción de la precondición para la llamada interna.

Hay que demostrar que Q(x) v Bnt(x) ⇒ Q(s(x))


En este caso hay que tener en cuenta que no sirven ni ciertos ni falsos.
Q(x) Y Bnt(x) es, más o menos, Q(x).
¿Qué es Q(s(x))?. Pues es sólo Q(x) cuando sustituimos c por el valor de la siguiente llamada recursiva.

Los casos posibles están contados. Más o menos cuatro.


Supongamos que c varía entre n y 1. Hemos definido Q’(x) como Q(x)v (c<=n) [y mayor o igual que cero, pero
eso no lo ponemos si c es natural]. Entonces:
Q(x) v Bnt(x) ⇒ Q(s(x))
c<=n v c>1 ⇒ c-1<=n
c<=n v c>1 ⇒ c-1<=n.
Y podemos dejarlo ahí. Porque si c<=n entonces c-1<=n-1 lo que es más restrictivo que c-1<=n

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II Página 3 de 14


En el caso alternativo, cuando c varía entre 1 y n entonces
Q(x) v Bnt(x) ⇒ Q(s(x))
c<=n+1 v c<=n ⇒ c+1<=n+1
c<=n ⇒ c<=n
Y basta.

3.- Base de la inducción.


Hay que intentar demostrar que Q(x) v Bt(x) ⇒ R(x, triv(x))
Lo cual es evidente, si lo hemos hecho bien.
Q(x) v Bt(x) es Bt(x) sin más o menos. Si nos da falso estamos en problemas, ya te lo he dicho.
R(x, triv(x)) es cierto. Así que Bt(x) ⇒cierto. Lo que es indiscutible.
Pero como los profesores nos van a calificar por lo que hagamos, vamos a hacerles creer que sabemos lo que
estamos haciendo.
R(x, triv(x)) es el resultado de R cuando ponemos en x el valor de Bt y en el resultado el resultado trivial. Es
decir, si para Bt(x) devuelve 0 entonces:
R(x, triv(x))/0=(3”en un rango imposible” de algo). (Desde 1 hasta 0 habitualmente. O desde n+1 hasta n). Si
hemos cuidado en poner en triv(x) el elemento neutro de la operación será lo que salga (es lo que sale) cuando
se hace un recorrido en un rango imposible.
Como 0=0 es cierto, pues ya está. O 1=1 o falso=falso. Que son las alternativas habituales.
Sólo recuerda que el elemento neutro de la disyunción es ‘falso’, pero el de la conjunción es ‘verdadero’.

Es decir: R(x,triv(x)) es el resultado booleano de una igualdad. Y la igualdad debe ser cierta.
Si no lo es, echa mano del tipex. O tacha, que hay que arreglar poco.

4.- Paso de inducción.


Aquí tendrás problemas hasta que comprendas que este método es el método de inducción. Lo de noetheriana
está solo para despistar.
Si es válido para cero, para uno, (los dos neutros, así nos curamos en salud),
y suponemos que es válido para n. ¿Es válido para n+1?.
Vamos a hacer cuentas y veremos lo que sale.
Q(x) v Bnt(x) v R(s(x), y’) ⇒ R(x,c(y’,x))
Puedes ignorar Q(x) Y Bnt(x). Están, pero ignóralos.
R(s(x), y’) es lo que hace.
R(x, c(y’,x)) es lo que debería de hacer.
Y si las dos cosas no coinciden, entonces, a borrar.
Un par de ejemplos:
a) Supongamos un algoritmo que suma los elementos de un vector.
Q(x)/{c<=n}
fun iSuma (x:vector [1..n]de reales):dev s real;
caso c=0 6 0
c>0 6 x[c]+iSuma (x,c-1)
fcaso
ffun
R(x,y)/{s=3(desde i=1 hasta c) x[i]} v {c=n}

Habíamos quedado que R(s(x),y) es lo que hace:


R(s(x),y)/x[c]+(3(desde i=1 hasta c-1) x[i] )
(hemos supuesto hasta c-1 y evaluamos c)
Y también habíamos quedado en que R(x,c(y’,x)) es lo que debería hacer:
R(x,c(y’,x))/{s=3(desde i=1 hasta c) x[i]}
¿Lo ves?. Espero que sí.

b) Supongamos un algoritmo que busca un determinado valor en un vector.


Q(x)/{r>0 v c<=n}
fun iBusca (x:vector [1..n]de naturales):dev b boolean;
caso c=0 6 falso
c>0 6 b=(x[c]=r) w iBusca (x, c-1)

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II Página 4 de 14


fcaso
ffun
R(x,y)/{b=(›"0 [1..c] A x[i]=r)} v {c=n}

Lo que hace:
R(s(x),y)/ b=(x[c]=r) w {b=(∃"0 [1..c-1] A x[i]=r)}
Lo que debería hacer:
R(x,c(y’,x))/{b=(›"0 [1..c] A x[i]=r)}
Y si no lo ves recuerda que el símbolo › se puede sustituir por una concatenación de disyunciones (w) y el
símbolo œ por una concatenación de conjunciones (v).

Ya hemos pasado lo difícil. Continuamos:

4.- Elección de una estructura de preorden bien fundado.


Formalmente debes encontrar t: Dt1 6 Z tal que Q(x) ⇒ t(x)>=0
Lo que no quiere decir nada porque no hay nada que demostrar.
Si a estas alturas no sabes lo que es t, vas un poquitín despistado.
Si c recorre desde n hasta 1 (alternativamente 0) entonces t=c
Si c recorre desde 1 hasta n entonces t=n-c
Lo escribes bonito y ya está.

y 5.- Demostración del decrecimiento de los datos.

NO es decrecimiento de los datos. Es decrecimiento de la t que acabamos de definir en cada llamada recursiva.
Te piden demostrar que Q(x) v Bnt (x) ⇒ t(s(x))<t(x)
Ignora la primera parte. Ya la hemos usado dos veces, así que simplemente copiala.
El resultado correcto es Q(x) v Bnt (x) ⇒ cierto.
Y para que sea cierto, t del sucesor de x tiene que ser menor que t de x.
Si t=c entonces c-1<c lo que es cierto.
Si t=n-c entonces (n-c)-1<n-c lo que también es cierto.

Y se acabó.

Esto lo coge Charles Dogdson (alias Lewis Carroll) y se parte de risa. ¡Feliz no cumpleaños!.

Comenzamos de nuevo:

Tenemos una función inútil. Es la siguiente:


Q(x)
fun LoQueSea (x:variables de entrada):dev y
R(x,y)

Vamos a sumergir esta función en el proceloso mar de la recursividad:

1.- Buscamos una variable c. Esta variable va a recorrer el vector desde el extremo superior (n) hasta el extremo
inferior (1 ó 0).
Definimos automáticamente t = c. Ya tenemos el preorden bien fundado.
2.- Le hacemos un sitio a c dentro de la función inútil. Para ello...
a) “Debilitamos” R. R≡Rdébil v (c=n). En Rdébil sustituimos cualquier referencia a n por c.
b) Introducimos c dentro de las variables de entrada de la función.
c) Reforzamos Q incluyendo una referencia a c. Pero ojo, dale suficiente espacio: cuando se salga del
vector, y se tiene que salir, aún debe cumplir Q’

Ya tenemos la función recursiva. Para el equipo docente “inmersora no final”.

Q’(x)=Q(x) v (c<=n)
fun iLoQueSea (x:variables de entrada, c:entero):dev y

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II Página 5 de 14


R’(x,y)=Rdebil(x,y) v (c=n)

3.- Llamada inicial. La tenemos encima: c=n. Pero si lo recorres de abajo a arriba en ambos casos debes poner
c=1 (ó c=0 dependiendo del rango del vector).
4.- Análisis por casos:
a) El caso Bt es cuando c se sale del vector [c<1, c<0 ó c>n]. Dependiendo que el vector esté en [0..n] ó
[1..n]. Y si lo recorremos hacia arriba o hacia abajo.
b) El caso Bnt es cuando c está en el vector. Todo lo demás. Es decir: [c>=1; c>=0; c<=n]
a2) El caso Bt conduce a triv(x). Que es el elemento neutro de la operación que vamos a poner debajo,
dentro de un momento.
b2) El caso Bnt conduce al examen del momento “c” OP (operado con) una llamada recursiva en la que c
pasa a c-1 ó c+1.
Podríamos decir que es (Rdébil para c) OP (Rdébil desde 1 hasta c-1) [llamada recursiva].

Y ahora demostramos que es correcto.

¿Alguien te ha informado ya que a los programadores se los paga o clasifica por los miles de líneas de código al
mes que pueden hacer?.

Problemas:
-Tu R(x,y) es un desastre. Ni se acerca a lo que te están pidiendo. Mala suerte.
-Q(x) es demasiado optimista o pesimista. Es decir más débil o más fuerte de la cuenta, en opinión del equipo
docente de la asignatura. Mala suerte.
-Te has empeñado en que 0 es el elemento neutro del producto. O, lo que es más habitual, que cierto es el
elemento neutro de la disyunción (∃).
-Por un olvido estúpido la llamada recursiva va a c. Lo que hace que el ordenador entre en un ciclo infinito y
tengas que apagarlo a lo bruto.
-Por enésima vez: ‘falso⇒CualquierCosa’ es formalmente correcto. Pero no sirve.

Inmersión final:

Según uno de los dos posibles libros de texto de la asignatura, [Peña 97], la inmersión final crea código más
compacto, eficiente y menos comprensible.

Si has tenido que hacer X prácticas obligatorias, con X >=2 sabes que eso no es cierto. No es más eficiente.
Menos comprensible, sí, por supuesto.

Entonces, ¿para qué la inmersión final?. Incumpliendo las reglas que me he impuesto para hacer esta guía, te
voy a contar una historia que puede que te aclare este concepto. Y voy a hacerlo porque creo que nadie se ha
molestado en explicártelo. Por supuesto no va a servir para que apruebes, así que puedes saltártelo
tranquilamente.

Supongamos que te encuentras en lo alto de una escalera. No sabes los pisos que hay hasta la salida. Tampoco
sabes si los pisos tienen todos la misma altura o no, si la escalera se subdivide en varias, ni si hay varias salidas.
En resumen, sabes poco. Pero quieres poder salir del edificio en caso de necesidad, a oscuras, medio dormido.
Así que coges un montón de miguitas de pan y dejas caer una en el suelo. Después bajas un escalón (PUSH)
llamándote recursivamente a tí mismo. Es decir: (miguita, escalón), (miguita, escalón), etc.
Cuando se acaba la escalera y/o vislumbras la salida te dices a tí mismo: “Vale”. Esto es triv(x).
Te das media vuelta y comienzas a subir los escalones (POP) recogiendo las miguitas que has dejado caer.
Cuando estás de vuelta en el origen, tienes un montón de miguitas que te dicen el número de escalones que
tienes que descender. Además puedes haber recopilado otra mucha información: número de descansillos,
escaleras alternativas, puertas, etc.

Después de hacer esto un par de cientos de veces te dices a tí mismo: ¿Y porqué no llevo conmigo una bolsa
donde guardar las miguitas?. Así cuando termine, puedo coger el ascensor.

¿Coger el ascensor?.

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II Página 6 de 14


Sí. En informática después de un número indeterminado de PUSH (llamadas a subrutinas) no tiene porque
haber igual número de POP (retornos). Un único POP puede devolverte al punto de origen, a la llamada
original.
Siempre que no hayas tocado la pila de datos. Es decir, siempre que no te hayas dejado miguitas por ahí.

Una función recursiva final no se deja cálculos para después. Se los lleva dentro de la propia función, dentro de
la llamada recursiva. Así que tiene su bolsa, real o virtual en la que guardar las miguitas.

Esto exige un cambio de mentalidad: Estás en lo alto de la escalera y te dices a tí mismo: “De acuerdo.
Supongamos que estoy en la salida. Me digo a mí mismo ‘Vale’ y cada vez que baje un escalón pienso que lo
estoy subiendo y recogiendo la miguita, que voy a guardar en esta bolsa”. Y además tienes que estar seguro que
el número de miguitas no va a cambiar por hacerlo así. Porque estás metiendo en la bolsa en primer lugar la
última miga y eso puede ser importante.

¿Entonces es más eficiente?. NO. Y no lo es porque el conserje del edificio, el señor Compilador es muy serio y
tiene responsabilidades ante el resto de los propietarios y, sobre todo, ante el presidente de la comunidad, Don
Sistema Operativo. Así que cuando te ve haciendo PUSH con tu montón de miguitas se fía de tí lo justo y
necesario, (es decir, nada) y te obliga a acompañarle escaleras arriba comprobando que no te has dejado
ninguna miguita olvidada en la pila de datos. Que luego el ordenador se cuelga y nadie sabe porqué.

En resumen: la posible mejor eficiencia de una función inmersora final depende del compilador.

¿Lo entiendes?. ¿Seguro?.

Según [Peña97] en su página 62 el término inglés para “recursiva final” es ‘tail recursion’, es decir,
recursividad de cola. Y las ‘funciones recursivas no finales” son en inglés ‘nontail recursive function’. Es decir,
sin cola.
VALE.

Volvamos al principio de este apartado:


Inmersión final:

Vamos a crear una función en la que las operaciones que hasta ahora se hacían antes de la llamada recursiva, se
hagan en la propia llamada recursiva.
Una función inmersora final o recursiva final tiene una variable (una nueva variable) en la que se van
acumulando los cálculos.
Y el proceso de transformar una función recursiva final en otra final es automático y se llama “Desplegado y
Plegado”. Que es lo que te van a poner en el examen.
1.- Hacer el árbol sintáctico de la operación. Queda muy bonito así que hazlo. Si lo haces a lápiz es más fácil
arreglarlo.
2.- Defines ‘g’ como lo que ocurre en Bnt(x). Suele ser algo así: w OP iLoQueSea (x,c)
Por tanto: iiLoQueSea (x,c,w):=w OP iLoQueSea(x,c)
Esto empieza a parecerse a eso de s:=s+t.
3.-En lugar de iLoQueSea(x,c) pones su análisis por casos. Y usas la propiedad distributiva.
No te olvides, en algún momento, de poner que has usado la propiedad asociativa. Lo esperan de tí, no los
defraudes.

4.- Como tu objetivo final es convertir el caso no trivial en una llamada a iiLoQueSea, busca la ocasión.

Notas:
No olvides que en el caso trivial nos llevamos la bolsita de migas. Eso es w. Si has utilizado correctamente la
propiedad distributiva, no hay problema.
La llamada inicial es triv(x). iiLoQueSea (x,c,triv(x))=iLoQueSea (x,c)

Así que:
Q’‘(x)
fun iiLoQueSea (x:variables de entrada, c: entero, w:datos):dev s datos.
caso Bt(x) → w

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II Página 7 de 14


Bnt(x) → iiLoqueSea (x,c-1, (w OP Rdébil para c))
ffun
R(x,y)

En el que, para bordarlo:


R(x,y) es el original. No R’ sino el primero.
Q’‘(x) es Q’(x) ∧ (w=R *hasta ahora*).
*Hasta ahora* en descendente es desde c+1 hasta n.
*Hasta ahora* en ascendente es desde 1 hasta c-1.

Fin de la recursividad.

Ejercicio para meditar: ¿Porqué no se verifican las funciones recursivas finales?. Parece un poco tonto hacer
una cosa, comprobarla, modificarla y no comprobar la modificación.
O como se dice en Informática: “Si funciona, no lo toques”.
Así que, ¿porqué no se verifica después de obtener la función recursiva final?.
[Peña 97] da un argumento: “No tiene interés optimizar un programa incorrecto”.
Bien. ¿Qué opinas?.

ITERACIÓN

Es posible que tengas la idea de que, si en lugar de desarrollar un supuesto recursivo en el examen fuera
iterativo tendrías más probabilidades de aprobar. Que es más fácil.
Yo también tuve esa idea en su momento.
No es cierta. Las probabilidades de suspender son las mismas en ambos casos.
Lo que sí es cierto es que puedes caer en los mismos errores. Así que manténte alerta.

Verificación formal de programas iterativos.

Una cosa buena que tienen los programas iterativos es que el proceso de verificación formal es idéntico al
proceso de creación del programa. Es decir, la verificación formal puede utilizarse para crear un programa
correcto.

El esquema de un programa iterativo es el siguiente:

Q(X)
fun LoQueSea-it (x variables de entrada):dev s
inicio
(P)Mientras B hacer
restablecer
avanzar
fmientras
dev s
ffun
R(x,y)

Suponemos, como ya hemos hecho con el caso recursivo que tenemos que recorrer un vector.

El proceso de verificación formal de programas recursivos consta de 6 pasos, numerados, según [Peña97] del 1
al 6. El mismo autor establece 6 pasos para la verificación formal de programas iterativos, numerados del 0 al
5.
¿Puedes encontrar algún motivo por el que sea así?. Alguno habrá, así que procura recordarlo: En iterativo los
pasos de verificación van del 0 al 5.

0.- Encontrar P, invariante y t función limitadora.

Este paso 0 es, en su segunda parte, igual al paso 5 del proceso recursivo. Respecto a la primera parte, también
tuvimos que hacer algo así cuando generamos la función recursiva no final. Y, realmente, también en ese

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II Página 8 de 14


momento se definía t.

Tomemos R. Vamos a debilitarlo sustituyendo n por c, de tal manera que:


R≡ Rdébil ∧ (c=n)
Uno es el invariante. El otro ¬B. Nos estamos adelantando, pero no importa.
Rdébil es el invariante. La función limitadora t es igual a (n-c).

1.- P ∧ ¬B ⇒ R

Cae por su propio peso. Siempre que consideremos que el R original y el R descompuesto en una conjunción
son equivalentes, aunque sean incomparables. Como ya lo había dicho para el caso recursivo, no insistiré en el
tema. Escribes lo mismo en los dos lados de la ecuación lógica y ya está.
Dado que ¬B es (c=n) entonces B ≡(c ≠ n)

2.- {Q}Inicio{P}

¿Esto qué es?. ¿Es una ecuación o inecuación lógica?. ¿Hay alguna manera de saber qué está en qué lado de la
fórmula, igualdad, o lo que sea?.

Para eso está esta guía, ¿no?. Para aclarártelo.

Lo que ocurre es que te han cambiado la terminología. El primer capítulo tenía su terminología propia, el
segundo capítulo se dedicaba a explicar la terminología del tercer capítulo, pero al llegar al cuarto era hora de
cambiar. Que en esta asignatura la terminología tiene una fecha de caducidad muy inferior a la del pescado
fresco. En dos capítulos huele mal y hay que tirarla.

Después de una meditación profunda llegamos a que {Q}Inicio{P} puede ser interpretado como Q≡ P(inicio).
Y en unos momentos sustituiremos ≡ por ⇒ .
Vamos a ser más concretos.
Necesitamos dos variables. Una ya la conocemos, c, y vamos a usarla en el control del bucle. Y la otra es una
variable que al final será s. No podemos usar s porque no está definida (en Módula2, por ejemplo se define el
tipo pero no el nombre de la variable que devuelve la función. Es la función llamante la que tiene un nombre
(propio) para esa variable).
Para mantener la terminología dentro de un orden será w, nuestra querida bolsita de migas de pan. Así pues:
inicio:
var
c:entero;
w:mismo tipo que s;
fvar;
c:=extremo inferior del rango del vector-1 (cero si el rango es [1..n]; -1 si el rango es [0..n])
w:=triv(x)
finicio

Te recuerdo que ¬B es (c=n) por decisión unilateral. Es decir que recorremos el vector desde el extremo
inferior al superior. Puedes hacerlo al revés, si quieres. Al equipo docente le gusta así, lo que es un argumento
importante.
¿Entonces porqué definimos c fuera de rango?.
Porque esto, en el fondo, es equivalente a lo que poníamos en el punto 3 de la verificación de programas
recursivos: base de inducción. P tiene que dar cierto. Y para eso, como hacíamos allí,
[0=Σ ”desde 1 hasta 0” de algo]. Sustituimos 0(w=triv(x)) por 1, cierto o falso y Σ por Π ,∀ ó ∃ .

En el fondo no necesitamos Q. Porque P(inicio) tiene que ser cierto.

Si estamos en paralelismos entre recursividad e iteración, y este punto es equivalente a la base de inducción.
Entonces, ¿qué pasa con el anterior?. ¿No debería ser ese el equivalente?.
También. Ten en cuenta que {P} es un predicado intermedio que vale de postcondición de la primera parte
(inicio) y precondición de la segunda (restablecer, avanzar). Así que el punto anterior verifica “3" en la segunda

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II Página 9 de 14


parte y este en la primera.

3.- {P ∧ B} S {P}

Ya que estamos buscando paralelismos que nos orienten, volvemos al caso recursivo. Esto es, terminologías
aparte, igual que el punto 2 de la verificación recursiva. Allí decíamos que:
Q(x) ∧ Bnt ⇒ Q(s(x)).
Como acabamos de decir, {P} es precondición del bucle. Luego:
{P ∧ B} S {P} ≡ P ∧ B ⇒ P(S). Que es lo mismo que en recursión.
Lo cual nos da una idea bastante clara de lo que tenemos que hacer.

Demostrar: Debemos calcular P(S). Dado que P es un invariante, es decir, una fórmula que no varía a lo largo
del bucle, si todo es correcto entonces P ⇒ P.

Construir: Para construir bien un algoritmo iterativo de entrada, necesitamos definir las dos subfunciones
restablecer y avanzar.

Avanzar, la última es fácil. Tenemos que asegurarnos que la función t recorre su camino hasta el final. Y como
estamos hablando de vectores, y como lo estamos recorriendo desde el extremo inferior (1) al superior (n) y t=
(n-c) entonces

avanzar:
c:=c+1
favanzar.

Avanzar habitualmente destruye el invariante. Pero como es “invariante” la función restablecer debe arreglarlo.
En este momento, en el libro de Peña aparece el error más grande. El predicado T.
T ≡ P ∧B
Dentro de poco va a volver a aparecer T con otro sentido. Teniendo tantas letras a su alcance, ¿porqué
repetirla?. ¿Sólo para complicarnos la vida un poco más a los pobrecitos que queremos aprobarla?.

Dado que B es c≠ n tenemos:


[Rdébil hasta c] ∧ [c≠ n] ⇒ P(S).
En S sólo hemos definido avanzar, que hace que c aumente en una unidad. Luego,
[Rdébil hasta c] ∧ [c≠ n] ⇒ [Rdébil hasta c+1]
Con lo cual parece claro la función de restablecer:
restablecer
Rdébil para c+1.
frestablecer

NOTA IMPORTANTE:
Cualquiera acostumbrado a programar escribiría:

“mientras B hacer
c:=c+1;
Rdébil para c;
fmientras.”

Lo cual parece correcto, bonito y fácilmente comprensible. Además es más lógico: avanzar destruye primero la
invariancia y después restablecer cumple con su función.
¿Quieres aprobar?.

Entonces escribe:

“mientras B hacer
R débil para c+1
c:=c+1

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II Página 10 de 14


fmientras.”

Sin discusiones. Simplemente hazlo.

4.- P ∧ B ⇒ t>=0

A t no se le pide que sea un preorden bien fundado. Se le pide que sea natural. Y los naturales, con la relación <
es un orden estricto. Que es más estricto, más fuerte que los p.b.f.
{P ∧ B} es, para estos efectos, {B}
Es decir, en nuestro supuesto:
(c ≠n) ⇒ (n-c)>=0
Lo cual es cierto si consideramos que c<=n. Que es como lo hemos definido. En Inicio.

5.- {P ∧ B ∧ (t=T)}S{t<T}

Ahora aparece T. Por segunda vez.


Según la terminología del capítulo 2 usamos letras mayúsculas, no sólo en predicados, sino cuando necesitamos
recordar el valor previo de una variable. Es decir, T es el valor de t antes de S y t es el valor de t después de
pasar por S.
Traduciéndolo a algo un poco más comprensible:
P ∧ B ∧ t(x) ⇒ t(S(x))<t(x). Es decir, el punto 6 de demostración de algoritmos recursivos.
Y como en aquel caso, está claro que P ∧ B no pintan nada ahí. Simplemente la segunda parte de la inecuación
debe ser cierta.
Como regla general, si avanzar es c:=c+1 y t=n-c entonces
n-(c+1) < n-c
(n-c) -1 < n-c.
Lo cual es cierto. Hay una propiedad de la resta que dice que si sumamos un número al sustrayendo, restamos
ese mismo número al resultado.

Parece ser que hemos terminado. Y que el procedimiento era muy similar al de los programas recursivos.
¿Donde está entonces el núcleo de la inducción noetheriana, el paso de inducción?.
En el punto 3. No te lo dije para no ponerte nervioso.

Esta es la equivalencia entre recursividad e iteración:

Recursividad Iteración Comentarios


1 ---- Innecesario. B∨¬ B≡cierto, por definición.
2 3 P actúa como Q
3 1,2 P actúa como Q en 1 y como R en 2
4 3 P actúa simultáneamente como Q y R.
5 0,4 P actúa como Q
6 5 P actúa como Q

De lo cual se deduce que es fácil convertir programas recursivos en iterativos, y viceversa.


Lo cual forma parte de nuestro siguiente capítulo.

TRANSFORMACIÓN RECURSIVO-ITERATIVO.

Como sabrás la transformación de inmersión no final a iterativo da un doble bucle, mientras la transformación
de una función inmersora final a iterativo da un sólo bucle. Y si quieres saber porqué deberías leerte ese cuento
sobre escaleras y miguitas que te dejaste atrás.

La transformación es rápida y fácil. Veamos un esquema en el que pasamos de iterativo a recursivo

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II Página 11 de 14


rápidamente.

Q(x)
fun LoQueSea (x, variables de entrada):dev s
inicioA:
var
c: entero (o natural);
w:mismo tipo que s;
inicioB:
c:=extremo inferior -1
w:=triv(x);

mientras c≠ n hacer
restablecer:
w:=w OP Rdébil para c+1;
avanzar:
c:=c+1;
fmientras
dev w;
ffun;
R(x,y) ≡ Rdébil(desde 1 hasta c) ∧ (c=n)

En el que B= (c≠ n) y P=Rdébil (desde 1 hasta c)

Su equivalente recursivo final será:

Q’‘(x) ≡ Q(x) ∧ (c<=n) ∧ (w=R débil desde 1 hasta c-1)


fun iiLoQueSea (x, variables de entrada, inicioA): dev s
caso ¬B → dev w
B → iiLoQueSea (x,avanzar, restablecer);
fcaso
ffun
R(x)
Y su llamada inicial es iiLoQueSea (x,inicioB)

ESTUDIO DEL COSTE

Si estamos repasando un vector para averiguar algo, las probabilidades de que el coste esté en el orden de Θ(n)
son muy altas.

El libro de [Peña97] no es mal libro en este sentido. Conviene memorizar las recurrencias por resta y por
división. Las soluciones, por supuesto.

Hay unos factores que hay que tener en cuenta: son k, b, a.


k es el exponente del coste de Bt(x). Habitualmente 0 (coste unitario) salvo que haya algún bucle interno. En
ese caso k puede ser 1, 2 o más.
b es el número que restamos a t para cada llamada recursiva. Normalmente 1
a es el número de llamadas recursivas en Bnt.

Si a es mayor que 1 entonces el problema es NP y su coste exponencial: a(n div b). Habitualmente 2n . Es decir,
a=2, b=1. En este caso k es irrelevante.

Si a es 1 entonces el problema es P y su coste polinómico. Es decir nk+1. Como generalmente el coste de Bt es


constante (k=0) entonces T(n)=n. Salvo que haya uno o varios bucles internos.
Y acaba de aparecer nuestro tercer T. Y el primero que aparece en el libro. ¡Qué manía de repetir la misma
letra!.

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II Página 12 de 14


Si nos dicen que un algoritmo tiene un coste de “n*log n” o mejor aún “log n” está claro que ese algoritmo
trabaja por división y no por resta. Me refiero a “avanzar”.

En el análisis de recurrencias por división nos encontramos con las mismas letras k, b y a. La diferencia está,
como ya he dicho que en el primer caso se calcula (n-b) y en este (n/b).

Comparamos a con bk. Como habitualmente k=0 comparamos a con 1. (No con b. b0=1).
Si a < 1 (difícil. Muy difícil) el coste es Θ(n0) (1).
Si a=1 entonces el coste está en Θ(logn).
Si a>1 entonces el coste está en Θ(n logba). Considerando a=2 y b=2 entonces (n).

Si k=1 (suponiendo un bucle interno de recurrencias por resta), y presuponemos b=2 entonces:
Si a<b (a=1) entonces el coste es Θ(nk)= Θ(n)
Si a=b (a=2) el coste será de Θ (n logn)
Y si a>b (a>2) entonces Θ(n logba). Para a=4 Θ(n 2).

Logba lo leemos como “Logaritmo en base b del número a”.

FIN

(*) ¿Realmente se contradice a sí mismo?. No. Lo que pasa es que juega al despiste.
Las páginas claves son 42-43, 86-87 y 134.

La página 42 comienza con una definición “equivocada”. La definición 2.13. En ella se define como “más
fuerte (resp. más débil)” entre predicados algo que no es exactamente “más fuerte”. Porque en castellano esas
dos palabras tienen sentido estricto: excluyen la igualdad. Cosa que no ocurre en este caso. Por tanto la
definición correcta debería ser “más fuerte o igual de débil que (resp. más débil o igual de fuerte que)”.

A continuación en la misma página nos enteramos que un predicado siempre puede ser reforzado añadiendo
una conjunción. Y eso es cierto incluso si el segundo predicado es una tautología (cierto), es decir, no
modificamos el primer predicado. Igualmente puede ser debilitado añadiéndole una disyunción sea cual sea el
segundo predicado.

Pero también podemos debilitar un predicado expresándolo en forma conjuntiva. Después de desmontar todos
nuestros esquemas de esa manera tan suave y sutil, después de un montón de horas pensándolo y sometiendo el
ejemplo al tercer grado (lo que en mi caso fueron tres años, más o menos), se puede llegar al convencimiento de
que, efectivamente, es más débil. Y más débil en sentido estricto. Pero hablando estrictamente ambos
predicados son incomparables. Que es lo que nos dicen a continuación. Roberto Alonso Menlle, en su excelente
trabajo (**)“Erratas y Escolios PeDos” (en Wainu) demuestra que no lo son; que son totalmente comparables y
además que el segundo es más débil, estrictamente, que el primero.

Al final de este apartado nos enteramos que: falso ⇒P y que P⇒cierto.

Pasemos a las páginas 86 y 87. No presumiré de comprender lo que pone en el apartado 2.b de la página 86. Sin
embargo da la impresión que la implicación está equivocada. Si pasamos al ejemplo 3.3, en la página 87,
después de la palabra “obviamente” aparece algo que sí parece comprensible. Y el símbolo ⇒ está al revés. El
libro se contradice a sí mismo.

PUES NO. No se contradice.

Despiste 1: Las expresiones de las páginas 42 y 87 no son iguales. En la página 42 se pone un rango a la
variable entre 0 y 10 mientras en la página 87 la variable tiene un único valor: n.

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II Página 13 de 14


Despiste 2: Rdébil (pág 87) es más débil que R. Pero ambas son incomparables, según hemos visto en la página
42.

Despiste 3: Rdébil (a,b,s,i) ∧ (i=n) es equivalente a R. Por tanto no sólo es más débil que R sino también más
fuerte que R. (en sentido no estricto, por supuesto).

La oportunidad más clara que tiene el libro de contradecirse a sí mismo está en la palabra “obviamente”. Porque
no es obvio que la implicación tenga que estar en ese sentido ni pasaría nada por ponerla en el otro sentido. Y
sólo es obvio si comprendemos que las dos expresiones son equivalentes. Pero el libro no dice que no lo sean.
Claro que tampoco dice en ningún momento que lo sean.

Terminaré con la página 134. En principio es una repetición de las páginas 86-87, pero enfocado en el
invariante P, que es equivalente al Rdébil. Incluso lo reconoce al hablar de la similitud del método con el
presentado en la Sección 3.4. Pero cada vez que utiliza la expresión “débil” (más débil, debilitar, debilitada,
debilitamiento. Hasta 6 veces aparecen términos que contienen “débil”) lo hace en sentido estricto. Lo que no
ayuda nada en absoluto.
Pero explica porqué en la página 87 se escribe la implicación al revés. Porque aquí se necesita que P ∧¬B⇒R.

Lo que nos lleva al despiste fundamental que hace que uno se rompa la cabeza y no logre entender nada:

Despiste 0: El término “más débil” tiene dos significados. Y el libro sólo lo utiliza en sentido estricto, que es el
sentido habitual. Es cuando no utiliza el término cuando lo utiliza con sentido no estricto. Es decir, cuando nos
encontramos la implicación pero el libro se cuida muy mucho de escribir en palabras la relación que existe
entre ambos predicados.

Veamos si podemos llegar a alguna conclusión:


Tenemos cuatro predicados. Son R, Rdébil, Bt y Bnt.
Alternativamente tenemos estos otros cuatro predicados: R, P, ¬B y B.

Tenemos que:
R⇒Rdébil. (R⇒P).
Rdébil ∧ Bt ⇒ R. Pero R⇒ Rdébil ∧ Bt. Por tanto R≡ Rdébil ∧ Bt. Alternativamente R≡P∧¬B.
Y lo que nos lleva directamente al ejemplo de la página 42:
R⇒ Rdébil ∧ Bnt (R⇒ P ∧ B).

Por último, y sólo porque soy un pesado:


El hecho de que en la página 43 se reconozca que falso⇒CualquierCosa no es una patente de corso. No puede
ser utilizado en ningún caso.

BIBLIOGRAFÍA:

He utilizado un único libro, el primero de los dos alternativos que se citan en la guía del curso, y tal como es
citado por el equipo docente del curso:

[Peña97]: Ricardo Peña Marí.. Diseño de Programas. Formalismo y Abstracción. 2ª Edición. Prentice Hall,
1998

Recomiendo encarecidamente la lectura de “Erratas y Escollos en P2” de Roberto Alonso Menlle. Puede
encontrarse en Wainu, en la página web de la asignatura.

Carlos de la Cruz Gómez.


Cdl006@madrid.org

GUÍA PARA APROBAR EL EXAMEN DE PROGRAMACIÓN II Página 14 de 14

Você também pode gostar