CodeAttack – Introducción a la Recursión
No hace falta ser un experto para ser un gran programador.
Hoy les quiero mostrar una técnica muy simple para hacer cosas muy complejas. Generalmente uno se enfrenta con problemas donde la solución informática es siempre aparente, o sea, al desarrollar una aplicación empresarial, un sitio web, un videojuego simple, uno sabe que esta yendo por terreno conocido, que el sistema va a tener cierta forma, que la programación no va a ser compleja pero si va a llevar su tiempo. Pero no todos los problemas que hay ahí afuera esperandonos son tan simples como aparentan, es más, los más difíciles son a veces los más engañosos en apariencia. Muchos de estos dejan sin dormir a los pobres matemáticos, pero nosotros no vamos a intentar hacer lo que ellos.
Sin embargo estos problemas no son poco frecuentes como uno quisiera creer, por ejemplo, supongamos el caso de un sistema de ecommerce que trabajan con un sistema de envio que cobra por caja y cada caja tiene una capacidad dada, a su vez cada item tiene características distintas, queremos armarle al cliente las cajas de manera que tenga que pagar lo mínimo posible en el envio… no es tan fácil como parece ¿no?
Primero notemos que varios de estos problemas son “acotados”, lo que significa que aún si no sabemos cual es la solución, sabemos que está dentro de ciertas posibilidades, en este caso la solución es una de todas las posibles combinaciones de items posibles. Una forma de resolver el problema es trivial: probar cada una de las combinaciones hasta encontrar una que cumpla los requisitos (a veces puede haber más de una solución). Esta solución se conoce como “por fuerza bruta” y no es la más eficiente, pero es una solución y muchas veces no trabajamos con una complejidad tal que requiera eficiencia (en este caso, si son pocos items los que hay que acomodar no deberíamos preocuparnos tanto).
De todas maneras, aunque sabemos esta solución, la fuerza bruta, a veces, no es tan fácil de poner en práctica, y muchas veces no es la solución más agradable. Pero mediante la técnica de recursión podemos ponerla en práctica de una manera elegante, si el tipo de problema lo permite.
Vamos a intentar resolver una variante del problema anterior, mucho más simple. Supongamos que tenemos una bolsa de monedas de distintos valores, no necesariamente los habituales, y queremos obtener cierto monto en particular con las monedas que disponemos. El problema tiene 2 parámetros: un conjunto de valores y una suma deseada, y vamos a asumir que todos son valores positivos.
La técnica de recursión es muy simple: vamos a resolver el problema para una cantidad de monedas N, asumiendo que la solución para N-1 monedas ya existe. Entonces nosotros quitamos una moneda de la bolsa y sabemos que ahora podemos obtener una solución para esa bolsa, y nos falta saber que hacer para obtener una solución que considere a la moneda que quitamos. Podríamos chequear primero si existe una solución sin la moneda que quitamos, si existe el problema está resuelto. Si no existe deberíamos considerar que tal vez la moneda era necesaria para una solución, pero en vez de agregarla y probar con otra, simplemente cambiamos la suma deseada restandole el valor de dicha moneda. En pseudo-código:
funcion obtenerMonto(bolsa, monto) donde bolsa tiene N monedas
moneda <- bolsa.sacarMoneda()
solucion1 <- obtenerMonto(bolsa, monto)
si solucion1 existe
devolver solucion1
si no
solucion2 <- obtenerMonto(bolsa, monto - moneda)
si solucion2 existe
solucion2.agregarMoneda(moneda)
devolver solucion2
si no
devolver no existe
Ahora, para resolver el problema con N monedas, usamos la solución con N-1, y esa usa la solución con N-2, y así sucesivamente, en algún momento debemos encontrar un solución que decida por sí sola, si no la recursión sería infinita. Lo que necesitamos se llama “caso base” y generalmente es una instancia del problema que es trivial resolver, en este caso, si la bolsa ya no contiene monedas. Si no hay monedas solo hay un caso que puede tener solución, éste es cuando el monto deseado es cero, en cuyo caso devolvemos una solución vacía. Si no indicamos que la solución no existe. En pseudo-código:
funcion obtenerMonto(bolsa, monto) donde bolsa no tiene monedas
si monto = 0
devolver solucion vacia
si no
devolver no existe
Luego, podemos combinar el caso resursivo y el caso base en una única función, simplemente chequeando la condición de la entrada:
funcion obtenerMonto(bolsa, monto)
si bolsa no tiene monedas
aplicar caso base
si no
aplicar caso recursivo
Eso es todo. Observen que, por cada moneda, el caso recursivo comprueba la posibilidad de que la moneda esté o no esté en la solución, o sea que, en definitiva, está comprobando todos los casos posibles hasta encontrar uno. Esto es algo usual al usar recursión, y es una buena guía para saber si estamos usando esta técnica correctamente, considerar si estamos probando todas las opciones posibles para el item que estamos evaluando en la etapa recursiva.
Ahora te toca a tí (?)
Capítulo gratis (y crítica igualmente gratuita) del libro “PHP in Action” – Design Patterns
En tres partes:
http://devzone.zend.com/node/view/id/1684
http://devzone.zend.com/node/view/id/1734
http://devzone.zend.com/node/view/id/1755
El link directo a los pdf:
http://devzone.zend.com/content/pdfs/PHPinAction_part1of3.pdf
http://devzone.zend.com/content/pdfs/PHPinAction_part2of3.pdf
http://devzone.zend.com/content/pdfs/PHPinAction_part3of3.pdf
Pero hay que tener cuidado de no quedarse con una sola opinión sobre el tema. Por mi parte considero que hay algunos errores conceptuales o algunas explicaciones poco completas.
Por ejemplo, sobre el patrón Strategy, decir que su propósito es crear componentes reusables o plugins es un concepto incompleto e incluso incorrecto. En general todos los patrones de diseño tienen el propósito de aumentar la reusabilidad, generando una base común de estrategias de diseño. La creación de plugins es una posibilidad de diseño donde el patrón Strategy es aplicable, sin embargo el propósito del patrón Strategy es separar un algoritmo de su entorno de aplicación, por ejemplo, un objeto puede tener entre sus funcionalidades la capacidad de ordenamiento, pero sabemos que existen diferentes algoritmos de ordenamiento, o, en otras palabras, diferentes estrategias de ordenamiento. El objeto recibe un objeto Strategy que decide como ordenar sin saber nada sobre como lo hace, y a su vez el objeto Strategy ordena sin saber nada acerca del objeto donde se esta corriendo el ordenamiento (de esta manera los objetos Strategy se pueden reusar fuera de su contexto, y pueden ser cambiados sin tener que rediseñar el objeto que lo usaba).
Prestar atención que el patrón Strategy puede confundirse con el patrón Bridge ya que son semejantes en su gráfico UML. El patrón Bridge tiene como propósito separar un tipo de datos abstracto de su implementación, por ejemplo, un objeto “Colección” puede ser implementado mediante arrays, listas enlazadas, arboles binarios, etc. El uso de Strategy y Bridge en conjunto se conoce generalmente como policy-based design.
No recomiendo el uso del patrón Null, especialmente si la existencia del null esta contemplada por el lenguaje y especialmente si se utiliza este patrón para pasar por alto un resultado nulo a una función como se sugiere en el libro. Si una función tiene la opción de retornar nulo es por algo y la utilización del if para chequear la salida no solo es necesaria si no adecuada. En el ejemplo que da el libro es coherente que si una búsqueda da por resultado un valor nulo, indicando la inexistencia del objeto buscado, es coherente para el sistema y es coherente que uno sea quien tenga que verificar la nulidad de esta salida y actuar acorde. En cambio no resulta coherente que la función devuelva un objeto cuando este no existe, incluso si el objeto representa la inexistencia (siempre y cuando el entorno contemple la inexistencia como un valor posible).
En cuanto a la explicación del patrón Iterator, falla en explicar correctamente el por qué de este patrón, especialmente en tratar de comparar a los iteradores con los arrays. El propósito de los iteradores es mantener una interfase común para recorrer listas de elementos, sin importar su implementación. Un array es la implementación, no tiene sentido compararlo con los iteradores.
Y finalmente, confunde el patrón Composite con la estructura de datos Tree (bah, arboles). El uso del Composite implica estructuras en forma de arboles pero es más que eso en cuestión de concepto. Un Composite es un objeto que permite actuar sobre más de un objeto como si fuera en realidad uno solo, por ejemplo, un dibujo es un composite de trazos, figuras, colores, e incluso de otros dibujos.
En busca del santo grial de los videojuegos
Aproveche hoy, que estando en cama por culpa de uno de esos virus pasajeros que visitan en los cambios de temporada, para leer algunas de los varios artículos que tengo archivados para su lectura. Entre ellos había uno que creí sería de grata lectura, por ser una tesis que prometía vislumbrar acerca la narración de historias en videojuegos de acción singleplayer. El abstract y el pdf completo de la tesis esta para bajar acá:
http://www.gamecareerguide.com/features/320/masters_thesis_storytelling_in_.php
En principio me entusiasmo la tesis, ya que aparentemente se concentraba en un sector muy interesante de los videojuegos, y donde siempre hubo controversias con respecto al tema si la historia hace al juego o no. Pero lo deje para leer más adelante.
Ahora que tuve la oportunidad de leerlo completo (aunque finalmente no lo leí en su totalidad, ya que fui saltando partes), note primero algo que diezmó mi entusiasmo: los juegos analizados en la tesis en cuestión. La lista es la siguiente: Just Cause, Gun, GTA: San Andreas y Fable. Los primeros tres son tan similares que me parece ridículo no haber tomado uno solo como muestra de los otros. Fable realmente no lo jugué (esta en mis planes), pero a partir de la crítica especializada parece ser una buena opción (y en todo caso la evidencia sugiere que fue agregado a ultimo momento en la tesis y por sugerencia externa).
¿Podemos sacar conclusiones provechosas sobre el efecto de la historia en un videojuego a partir de esos 3 títulos (dejando Fable de lado)? Mi opinión es simplemente NO, y para ser más detallistas: el único que vale la pena analizar es el GTA:SA, pero no por el juego en sí, si no por la evolución de toda la serie GTA, desde sus inicios hasta lo que es hoy. La evolución de la serie GTA es un ejemplo de como la narrativa evoluciono en conjunto con el gameplay para crear un producto que hoy es el primer desarrollo extranjero en tener éxito de ventas en el mercado cerrado y exigente de Japon.
Hay miles de títulos a analizar sobre el tema, con miles de variantes, pero hay uno que no puede dejarse de lado en una investigación de esta índole, ya que marco un antes y un después en la relación historia/juegos de acción: el Half-life. Para entender por qué digo esto un buen punto de partida es el siguiente artículo, que relata los momentos claves de este juego:
http://www.gamespot.com/features/halflife_final/index.html
En cuanto a la tesis en sí, resulto ser otra voz critica en el debate narratología vs ludología, que, como es esperarse de tal, niega ser parte del mismo. Es evidencia clara sobre esto el que la tesis empiece con una critica directa a otro escrito de una voz semejante en el debate: “Games telling stories?” de Jesper Juul, el cual se puede leer en:
http://www.gamestudies.org/0101/juul-gts/
Aunque no niego que muchas de las criticas a este escrito sean meritorias, no es este un buen punto de partida para los objetivos de la tesis, y demuestra un bias en el autor que sigue apareciendo a lo largo del escrito, y le quita objetividad.
Mi opinión sobre la contienda narratología vs ludología la dejo para otro momento.