El razonamiento ecuacional es la idea de aplicar sustituciones, plantear ecuaciones, despejar variables, aplicar funciones a ambos lados de la igualdad y muchos otros trucos que practicamos en matematica, pero en expresiones de un lenguaje de programacion.
Obviamente podemos hacerlo con expresiones numericas.
Por ejemplo, si en un programa tenemos una variable hipotenusa
y una variable
cateto2
y queremos averiguar el valor de una variable cateto1
sabiendo que
estos son los lados de un triangulo rectangulo, podemos aplicar el teorema de
Pitagoras y aplicar una serie de manipulaciones algebraicas
cateto1 * cateto1 + cateto2 * cateto2 = hipotenusa * hipotenusa
cateto1 * cateto1 = hipotenusa * hipotenusa - cateto2 * cateto2
raiz(cateto1 * cateto1) = sqrt(hipotenusa * hipotenusa - cateto2 * cateto2)
cateto1 = sqrt(hipotenusa * hipotenusa - cateto2 * cateto2)
Pero esto no se queda ahi. Esta misma idea se puede aplicar con objetos mucho mas complejos, como listas, cadenas, vectores y grafos.
Por ejemplo, consideremos estas dos expresiones:
(str1 + str2).size()
= str1.size() + str2.size()
Esta claro que, aunque los procedimientos que realizan estos programas son muy distintos, el resultado que generan es siempre identico y, aun asi, uno de los dos es mucho mas eficiente que el otro.
Imaginemos un caso mas complicado.
// todas estas expresiones son equivalentes, pero algunas son mas costosas que otras
// ejercicio: ordenar de menor a mayor costo
sort(merge(sort(a), sort(b)))
= sort(concat(sort(a), sort(b)))
= merge(sort(a), sort(b))
= sort(concat(a, b))
Hay algoritmos de ordenamiento que tienen costo O(N) cuando su input ya esta ordenado. Si suponemos que
sort()
tiene esta caracteristica, cambia la respuesta al ejercicio?
Estos dos programas “A” y “B” son equivalentes.
// A
sort(begin(v), end(v);
sort(begin(v), end(v);
// B
sort(begin(v), end(v);
Por que? Bueno obviamente, porque ordenar una lista dos veces es lo mismo que ordenarla una sola vez: La primera vez se ordena y la segunda no pasa absolutamente nada, porque la lista ya se encuentra ordenada.
Refinando un poco la idea, tenemos que en realidad lo que esta pasando es que estos dos sub-programas son equivalentes dentro del contexto en el que viven:
// A
// suponiendo que v ya esta ordenado
sort(begin(v), end(v);
// entonces v esta ordenado
// B
// suponiendo que v ya esta ordenado
// entonces v esta ordenado
Y en la linea anterior tenemos el mismo sub-programa:
// sin suponer nada sobre v
sort(begin(v), end(v));
// entonces v esta ordenado
El hecho de que el primer sort hace que v quede ordenado nos permite aprovechar la equivalencia de los otros subprogramas y eliminar la segunda llamada redundante a sort.
Veamos un ejemplo mas interesante.
Los siguientes dos sub-programas son equivalentes, pero solo gracias a la suposicion que hacen.
// A
// suponiendo que v esta ordenado
auto it = find(begin(v), end(v), x);
bool encontrado = it != end(v);
// B
// suponiendo que v esta ordenado
auto it = lower_bound(begin(v), end(v), x);
bool encontrado = it != end(v) && *it == x;
En este caso resulta ser muy importante la suposicion, ya que nos permite pasar de usar un algoritmo O(N) a uno O(log N).
La Logica de Hoare es un formalismo que surge de observar que:
La logica de hoare se basa en plantear “ternas de Hoare”
{P} // pre-condicion
C // sub-programa
{Q} // post-condicion
Se lee “si antes del programa C se cumple la propiedad P, entonces despues se cumple la propiedad Q”, donde P y Q son proposiciones que hablan sobre las variables del entorno
Ejemplo interesante: como optimizar el algoritmo de test de primalidad (TO-DO)