Dificultad: Fácil

El problema que propongo resolver hoy fue propuesto en el año 1997 en la Olimpiada Informática Española para alumnos de bachillerato. ¿Puede un estudiante o titulado en ingeniería ser capaz de resolver este problema en 2 horas?

Contexto

En unos trabajos arqueológicos recientes, se han encontrado restos de una hipotética civilización precolombina. Los restos encontrados apuntan a que dicha civilización utilizaba un método algo original para representar números, mediante dados. Los dados se disponen en bandejas divididas en porciones, numeradas consecutivamente de izquierda a derecha a partir del cero.
El número representado se calcula como la suma del valor de la cara visible de cada dado, ponderándolo adecuadamente según la porción de la bandeja que se encuentra: un dado que tenga el número X en la cara visible y que se encuentre en la posición de número r tiene como valor X*10^r (X multiplicado por 10 elevado a r). Por ejemplo, el número 1.024 puede representarse de las maneras siguientes (con dados de seis caras y una bandeja dividida en cuatro porciones):

Unid.millar Centenas Decenas Unidades Núm. representado
1 - 2 4 1.024
- 6, 4 1 6, 6, 2 1.024
- 4, 5 6, 6 4 1.024

Ahora bien, existe un fallo en este sistema de numeración: hay números que no pueden representarse. Por ejemplo, si disponemos de 2 dados de 6 caras, es imposible representar el número 17; no existe ninguna distribución de los dados que permita obtenerlo. Se pide un programa que calcule el menor número no representable con N dados de k caras, suponiendo que la bandeja está dividida en N porciones y que en cada porción caben N dados.

Ejemplos:

Dados Caras Cubetas Resultado
2 4 2 9
2 6 2 17
3 4 3 19
3 6 3 77

Reconocimiento

Podrás enviar tu solución haciendo uso del formulario de abajo para que la compruebe personalmente. Puedes enviarlo todo en un archivo zip, aunque lo más recomendable es hacer uso de Git/SVN para evitar el envío de archivos y poder actualizar el código en caso de encontrar errores sin necesidad de realizar reenvíos. Es importante que además comentes tu solución en los comentarios y ofrezcas tu código de forma abierta. A cambio recibirás un badge de Credly que reconocerá tu esfuerzo y pericia.

Usted debe estar logueado para enviar un post.

Algo de código y pruebas

De nuevo ofrezco el marco para desarrollar la solución en lenguaje Java. El tipo de problemas que estoy proponiendo no pretende sacar partido de la orientación a objetos sino de la programación procedimental, ofreciendo un marco para que los que no estén familiarizados con los objetos puedan desarrollar soluciones en igualdad de condiciones.

Por un lado se ofrece una interfaz del problema que habrá que implementar para su resolución. En segundo lugar se aporta un fichero de JUnit 4 que deberán utilizarse como ejemplo para comprobar que la solución es correcta al menos para los casos definidos y que apoya el seguimiento de un desarrollo dirigido por pruebas. Por último, se ofrece un lanzador de pruebas (Test Runner) para aquellos que no utilicen un entorno de desarrollo que integre las pruebas de JUnit 4 en su interfaz.

En esta ocasión he introducido pruebas parametrizadas de JUnit, que permite definir varias pruebas que únicamente se diferencian en los parámetros de entrada de una forma sencilla (ver el método data para más detalle). Se recomienda que además de las pruebas facilitadas, se intente desarrollar otras pruebas antes de la resolución del problema.

Si desarrolláis soluciones o encontráis problemas, podéis enviar un comentario o bien un correo electrónico.

Interfaz del problema
package problems.dice;

public interface IDiceProblem {
	/*
	 * This method sets the input data for the problem.
	 * @param dice the total number of dice.
	 * @param faces the number of faces each die has.
	 * @param trays the number of trays (units, tens, hundreds, etc.) the numbering system has.
	 * @throws IllegalArgumentException if any of the input parameters are zero or negative.
	 */
	public abstract void setData(int diceNumber, int faces, int trays);
	
	/*
	 * Executes the problem if the data has been correctly set
	 */
	public abstract void run();
	
	/*
	 * @return the first number that is impossible to be represented with the provided system.
	 */
	public abstract int getResult();
}
Prueba JUnit parametrizada
package problems.dice;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import problems.dice.DiceProblemOptRefactored;
import problems.dice.IDiceProblem;

@RunWith(Parameterized.class)
public class DiceProblemTest {
	IDiceProblem diceProblem;
	int dice, faces, trays, expectedResult;
	
	public DiceProblemTest (int dice, int faces, int trays, int result) {
			this.dice = dice;
			this.faces = faces;
			this.trays = trays;
			this.expectedResult = result;
	}
	
	 // creates the test data
	@Parameters
	public static Collection<Object[]> data() {
	    Object[][] data = new Object[][] { {3,4,3,19}, {2,6,2,17}, {3,6,3,77}, {2,4,2,9} };

	    return Arrays.asList(data);
	  }
	
	@Test
	public void testDiceProblem() {
		diceProblem = new DiceProblemOpt();
		diceProblem.setData(dice,faces,trays);
		diceProblem.run();
		int result = diceProblem.getResult();
		Assert.assertEquals("[dice:"+dice+",faces:"+faces+",trays:"+trays+"]: "
							, expectedResult, result );
	}
}
Test Runner
package problems.dice;
import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestRunner {
   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(DiceProblemTest.class);
      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
      if(result.wasSuccessful())
    	 System.out.println("All tests passed!");
   }
}  

8 octubre, 2014

Posted In: Aprendiendo algoritmia, Tecnologería

7 Comments

Atención: No leer sin antes haber realizado el problema de la colonia de bacterias.

Ninguna prueba es suficiente

Cuando se aplica un enfoque de desarrollo dirigido por pruebas (TDD), la principal novedad a nivel metodológico es comenzar el diseño de una solución con las pruebas que nos harán comprobar finalmente que el funcionamiento es el deseado. Para ello debemos imaginar cómo se va a utilizar una determinada interfaz, cuál va a ser el comportamiento que va a observar un usuario de dicha interfaz desde fuera, sin acceso alguno al código fuente. Con este enfoque se acaba mejorando la usabilidad de las propias interfaces, que ya no vienen definidas por los caprichos, vicios y/o estilo de programación del desarrollador de la implementación, sino que tienen al consumidor de la interfaz como prioridad en el diseño. Y la interfaz no podrá modificarse porque la implementación sea más o menos complicada. Debemos adaptarnos a ella.

Aunque se recomienda separar el rol de probador y programador, esto no es siempre posible y cuando estamos solos ante el peligro debemos asumir un cambio de roles en el que en un momento diseñas pruebas y en otro diseñas código. Para evitar que el código pervierta las pruebas, os recomiendo que establezcáis una regla que impida realizar el paso de un rol a otro sin al menos 15 minutos de descanso entre ambos. Así evitáis la tentación de cambiar las pruebas sin "haberlo dejado macerar" un rato. En este caso, ir a pensar a cualquier rincón alicatado que tengáis a mano fomentará un mejor funcionamiento de vuestras neuronas.

En el caso de la colonia de bacterias habéis partido de pruebas desarrolladas por otra persona ajena al código y que os ha impuesto una interfaz y un comportamiento que se infiere de la documentación en el código y de los propios ejemplos de uso de las pruebas. Al ir desarrollando una solución, habéis podido comprobar en cada momento si vuestro código es correcto o no respecto a las pruebas. Sin embargo, las pruebas facilitadas no son suficientemente exhaustivas y dejan muchos casos de uso sin probar, y por tanto comprometiendo la calidad de la implementación de vuestra solución.

En el mundo del software, es imposible realizar pruebas completas del código fuente. Una prueba es completa cuando se comprueban las salidas obtenidas para todas las posibles entradas. De esta manera, para un único método de una interfaz deberíamos probar todas las combinaciones de valores que puedan tomar cada uno de los parámetros de entrada. Así para un método suma(int izq, int der), debemos probar todas las posibles combinaciones de números. Ya que un número entero de 32 bits puede tomar 4.294.967.296 valores, para dos números tenemos un total de 1.8446744E18 posibles combinaciones. En el caso del método setData tenemos dos números enteros además de una lista de posiciones de bacterias. ¿Somos acaso capaces de probar todos los posibles escenarios?

Realizar pruebas completas es imposible; ser exhaustivo es una obligación.

Ya que no es posible realizar pruebas completas, debemos ser al menos exhaustivos en el diseño de pruebas. Se deben escoger aquellos casos que prueben el mayor número de comportamientos diferentes posibles. Así por ejemplo, en la colonia de bacterias una prueba de una sola iteración en la que sólo existe una bacteria sólo prueba la muerte de la misma al no tener comida. Esta prueba es menos efectiva que crear una colonia donde en una sola iteración muera una bacteria, se genere una nueva y otra sobreviva. Sin embargo, al encontrar un error en el primer caso sabemos que existe un error en la regla de muerte por inhanición, mientras que encontrar el error en el segundo caso es más complejo al deber discriminar cuál o cuáles de las tres reglas de evolución está fallando.

Pero las pruebas no se acaban con los casos de prueba habituales. Un buen probador debe tener una mente perversa que le lleve a pensar mal de los programadores. Un buen probador debe pensar que quien utiliza la interfaz no se ha leido la documentación sobre su uso, envía accidentamente parámetros incorrectos, utiliza los métodos en un orden inadecuado, etc. Con las pruebas anteriormente facilitadas habéis trabajado con un probador novato. En el capítulo de hoy os animo a probar vuestro código con la siguiente prueba unitaria:

package problems.bacteriaColony;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class BacteriaColonyIntensiveTest {
	IBacteriaColonyProblem colonyProblem;
	
	@Before
	public void setup() {
		colonyProblem = new BacteriaColonyProblem();
	}
	
	@Test
	public void testBacteriaProblem1DoubleSetData() {
		int [][]bacteria = {{0,2},{1,1},{1,2},{2,2},{3,2}};
		boolean [][]expected = {{false,true,true,false},
								{false,true,true,true},
								{false,false,true,true},
								{false,false,false,false}};
		colonyProblem.setData(4,1,bacteria);
		colonyProblem.setData(4,1,bacteria);
		colonyProblem.run();
		checkResult(expected,colonyProblem.getResult());
	}
	
	@Test
	public void testBacteriaProblem1Iter2TwoRun() {
		int [][]bacteria = {{0,2},{1,1},{1,2},{2,2},{3,2}};
		boolean [][]expected = {{false,true,false,true},
								{false,false,false,false},
								{false,true,false,true},
								{false,false,false,false}};
		colonyProblem.setData(4,2,bacteria);
		colonyProblem.run();
		colonyProblem.run();
		checkResult(expected,colonyProblem.getResult());
	}
	
	@Test
	public void testBacteriaProblem2() {
		colonyProblem.run();
	}
	
	@Test
	public void testBacteriaProblemTwoSetData() {
		int [][]unusedBacteria = {{0,2},{1,1},{1,2},{2,2},{3,2}};
		colonyProblem.setData(4,2,unusedBacteria);
		int [][]bacteria = {{0,2},{1,1},{1,2},{2,2},{3,2}};
		boolean [][]expected = {{false,true,true,false},
								{false,true,true,true},
								{false,false,true,true},
								{false,false,false,false}};
		colonyProblem.setData(4,1,bacteria);
		colonyProblem.run();
		checkResult(expected,colonyProblem.getResult());
	}
	
	@Test (expected=IllegalArgumentException.class)
	public void testBacteriaIllegalData() {
		int [][]bacteria = {{0,2},{1,1},{1,2},{2,2},{3,2}};
		colonyProblem.setData(-1,-1,bacteria);
	}
	
	private void checkResult (boolean[][] expected, boolean[][] result) {
		Assert.assertEquals("Colony width error", expected.length, result.length);
		for (int x = 0; x < result.length; x++) {
			Assert.assertEquals("Colony height error", expected[x].length, result[x].length);
			for (int y = 0; y < result[x].length; y++)
				Assert.assertEquals ("Incorrect value at ["+x+","+y+"]",
									expected[x][y], result[x][y]);
		}	
	}


}

¿Era vuestro código correcto? ¿Podemos hacer pruebas mejores y más exhaustivas? ¿Podemos confiar siempre en que nuestro código fuente funciona? ¡Responded a estas preguntas en los comentarios!

3 octubre, 2014

Posted In: Aprendiendo algoritmia, Tecnologería

3 Comments

Dificultad: Muy fácil

Hoy planteo un sencillo problema de programación que casi se puede considerar un clásico de la programación y que ha sido propuesto en multitud de ediciones de olimpiadas informáticas en diferentes paises con pequeñas variantes. Se considera un problema básico pero que ayuda a plantearse algunas cuestiones importantes sobre la gestión de tablas, memoria y que ayudará a introducir algunos conceptos básicos sobre legibilidad del código.

Contexto

Un laboratorio de investigación distribuye una colonia de bacterias en un cultivo, que se puede considerar como una superficie cuadriculada de N filas y N columnas; cada casilla de esta superficie puede estar vacía o puede contener una bacteria. A partir de cualquier configuración inicial, la colonia evoluciona generación a generación según unas leyes genéticas fijas y determinadas y que dependen del número de vecinos que tenga cada casilla.

Para todas y cada una de las generaciones las leyes genéticas son:

  • Nacimiento: en toda casilla vacía que tenga exactamente tres vecinos.
  • Muerte por aislamiento: toda bacteria que ocupe una casilla con uno o ningún vecinos
  • Muerte por asfixia: toda bacteria que ocupe una casilla con más de tres vecinos.
  • Supervivencia: toda bacteria que ocupe una casilla con dos o tres vecinos.

La casilla que ocupa la fila i y la columna j se identifica mediante (i, j) (0<=i<N, 0<=j<N). Los vecinos de una casilla (i, j) son las posiciones (i-1, j-1), (i-1, j), (i-1, j+1), (i, j-1), (i, j+1), (i+1, j-1), (i+1, j) e (i+1, j+1) que estén comprendidas dentro de la superficie y que estén ocupadas por una bacteria.

Ejemplos

Para una superficie 4x4, la colonia de la izquierda de la figura siguiente evoluciona en las dos próximas generaciones tal y como se muestra:

*
* *
*
*
* *
* * *
* *
 
* *
 
* *
 

Objetivo

El objetivo será crear un programa que sea capaz de simular evolución de cualquier colonia inicial durante un cierto número de transiciones. En concreto, ¿quién puede indicarme cuál será la evolución de esta colonia tras 500 iteraciones?

* *
* *
*
* *

Reconocimiento

Podrás enviar tu solución haciendo uso del formulario de abajo para que la compruebe personalmente. Puedes enviarlo todo en un archivo zip, aunque lo más recomendable es hacer uso de Git/SVN para evitar el envío de archivos y poder actualizar el código en caso de encontrar errores sin necesidad de realizar reenvíos. Es importante que además comentes tu solución en los comentarios y ofrezcas tu código de forma abierta. A cambio recibirás un badge de Credly que reconocerá tu esfuerzo y pericia.

Usted debe estar logueado para enviar un post.

Algo de código y pruebas

A fin de homogeneizar la resolución de este problema, voy a facilitar código fuente para aquellos que están familiarizados con el lenguaje Java (cuyo uso es mayoritario en mi entorno). En primer lugar una interfaz del problema que habrá que implementar para su resolución. En segundo lugar se aporta un fichero de JUnit 4 que deberán utilizarse como ejemplo para comprobar que la solución es correcta al menos para los casos definidos y que apoya el seguimiento de un desarrollo dirigido por pruebas. Por último, se ofrece un lanzador de pruebas (Test Runner) para aquellos que no utilicen un entorno de desarrollo que integre las pruebas de JUnit 4 en su interfaz.

Si desarrolláis soluciones o encontráis problemas, podéis enviar un comentario o bien un correo electrónico.

Interfaz del problema
package problems.bacteriaColony;

public interface IBacteriaColonyProblem {
	/*
	 * This method sets the input data for the problem.
	 * @param size Width and height of the colony, i.e. size=4 creates a 4x4 matrix.
	 * @param transitions Number of transition steps to compute in the problem.
	 * @param bacteriaPosition An array of bacteria positions as (i,j) pairs
	 * such that 0 <= i,j < size.
	 * @throws IllegalArgumentException if bacteriaPosition is null, size or 
	 * transitions are 0 or negative numbers, 
	 * or the bacteriaPosition is not a list of pairs. 
	 */

	public abstract void setData(int size, int transitions, int [][]bacteriaPosition);
	
	/*
	 * Executes the problem if the data has been correctly set
	 */

	public abstract void run();
	
	/*
	 * @return a boolean matriz of size x size dimensions. True value indicates a living bacteria; 
	 * false value represents an empty space.
	 */
	public abstract boolean[][] getResult();
}
Prueba JUnit
package problems.bacteriaColony;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

public class BacteriaColonyTest {
	IBacteriaColonyProblem colonyProblem;
	
	@Before
	public void setup() {
		colonyProblem = new BacteriaColonyProblem();
	}
	
	@Test
	public void testBacteriaProblem1Iter1() {
		int [][]bacteria = {{0,2},{1,1},{1,2},{2,2},{3,2}};
		boolean [][]expected = {{false,true,true,false},
								{false,true,true,true},
								{false,false,true,true},
								{false,false,false,false}};
		colonyProblem.setData(4,1,bacteria);
		colonyProblem.run();
		checkResult(expected,colonyProblem.getResult());
	}
	
	@Test
	public void testBacteriaProblem1Iter2() {
		int [][]bacteria = {{0,2},{1,1},{1,2},{2,2},{3,2}};
		boolean [][]expected = {{false,true,false,true},
								{false,false,false,false},
								{false,true,false,true},
								{false,false,false,false}};
		colonyProblem.setData(4,2,bacteria);
		colonyProblem.run();
		checkResult(expected,colonyProblem.getResult());
	}
	
	@Test
	public void testBacteriaProblem2() {
		int [][]bacteria = {{1,1}};
		boolean [][]expected = {{false,false,false},
								{false,false,false},
								{false,false,false}};
		colonyProblem.setData(3,1,bacteria);
		colonyProblem.run();
		checkResult(expected,colonyProblem.getResult());
	}
	
	@Test
	public void testBacteriaProblem3Iter1() {
		int [][]bacteria = {{1,1},{2,1},{2,2}};
		boolean [][]expected = {{false,false,false},
								{false,true,true},
								{false,true,true}};
		colonyProblem.setData(3,1,bacteria);
		colonyProblem.run();
		checkResult(expected,colonyProblem.getResult());
	}
	
	@Test
	public void testBacteriaProblem3Iter2() {
		int [][]bacteria = {{1,1},{2,1},{2,2}};
		boolean [][]expected = {{false,false,false},
								{false,true,true},
								{false,true,true}};
		colonyProblem.setData(3,16,bacteria);
		colonyProblem.run();
		checkResult(expected,colonyProblem.getResult());
	}
	
	@Test
	public void testBacteriaProblem4() {
		int [][]bacteria = {{1,1},{2,1},{2,2}};
		boolean [][]expected = {{false,false,false,false,false},
								{false,true,true,false,false},
								{false,true,true,false,false},
								{false,false,false,false,false},
								{false,false,false,false,false}};
		colonyProblem.setData(5,15,bacteria);
		colonyProblem.run();
		checkResult(expected,colonyProblem.getResult());
	}
	
	@Test (expected=IllegalArgumentException.class)
	public void testBacteriaIllegalData() {
		colonyProblem.setData(0,1,null);
	}
	
	private void checkResult (boolean[][] expected, boolean[][] result) {
		Assert.assertEquals("Colony width error", expected.length, result.length);
		for (int x = 0; x < result.length; x++) {
			Assert.assertEquals("Colony height error", expected[x].length, result[x].length);
			for (int y = 0; y < result[x].length; y++)
				Assert.assertEquals ("Incorrect value at ["+x+","+y+"]",
									expected[x][y], result[x][y]);
		}	
	}


}

Test Runner
package problems.bacteriaColony;

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class TestRunner {
   public static void main(String[] args) {
      Result result = JUnitCore.runClasses(BacteriaColonyTest.class);
      for (Failure failure : result.getFailures()) {
         System.out.println(failure.toString());
      }
      if(result.wasSuccessful())
    	 System.out.println("All tests passed!");;
   }
}  

29 septiembre, 2014

Posted In: Aprendiendo algoritmia, Tecnologería

4 Comments

Tras unos días para poner todo en orden, continúo con mi malvado plan de enseñar al mundo a programar. Algunas de mis actuaciones tendrán lugar a través de este blog. Para ello voy a escribir 3 tipos de artículos:

  • Aprendiendo a programar: este tipo de artículos planteará retos básicos que ayuden a desarrollar la mente de un programador. Apto para todos los públicos
  • Aprendiendo algoritmia: aquí se plantearán problemas de la vida real cuya solución se apoye en principios matemáticos y algorítmicos. Apto para quien tenga al menos nociones básicas de un lenguaje de programación.
  • Programación avanzada: porque hay muchas formas de programar pero sólo algunas de ellas son capaces de sacar el máximo rendimiento de un computador. Para programadores con ganas de marcha.

Para que todo esto sirva para algo, deben cumplirse algunas cosas:

  • Debes estar al liquindoi [1]: esta expresión gaditana resume a la perfección el mecanismo básico del aprendizaje. la única forma de aprender a hacer las cosas es observar con atención y haciéndolas. Con mirar únicamente no basta.
  • Participa: no sólo basta con hacerlo y guardarlo en un cajón, debes mostrarlo al mundo. Seguramente se te hayan escapado detalles importantes o se te ha ocurrido una solución ingeniosa de la que merece la pena presumir.
  • Tu objetivo: como comenté anteriormente, la clave para estar motivado es tener un objetivo ambicioso. Intenta pensar en cada momento cómo cada cosa que aprendas puede contribuir a sentirte capaz de cumplir tu objetivo
  • Mi motivación: hago esto porque me da la gana. En el momento en el que no me sienta motivado dejaré de hacerlo. Y la única fuente de motivación es vuestro feedback y participación.

¿Empezamos?

Referencias

[1] La expresión "estar al liquindoi" tiene su origen (o al menos así lo reconoce la cultura gaditana popular) en los puertos de la provincia gaditana donde el contacto con comerciantes ingleses era habitual. Aunque existen muchas interpretaciones sobre su origen, la acepción que nos ocupa e interesa, establece su origen en la interpretación que realizaban los aprendices gaditanos cuando escuchaban de sus maestros ingleses la expresión "look and do it!". De ahí su significado actual de "presta atención y aprende".

21 septiembre, 2014

Posted In: Tecnologería

Leave a Comment

Las buenas costumbres no hay que perderlas. Es por ello que sigo jugando al fútbol y la pachanga del lunes forma parte de un ritual al que no se puede faltar. Como toda buena pachanga, no vamos con un portero fijo sino que nos vamos rotando cada 7 minutos con un orden que sorteamos al llegar. Aquí se pueden aplican distintos métodos para realizar el sorteo:

  • Cada jugador saca un número del 0 al 5 con una mano, se suman todos, se comienza a contar en sentido horario y al que le toque va primero y el resto de posiciones se deciden en orden horario.
  • Lo mismo pero en orden anti-horario.
  • 25 y la pirula, o lo que es lo mismo, uno cuenta mentalmente hasta que alguien le hace parar en el número que toque, empieza a contar en voz alta en sentido anti-horario hasta 25 y uno más de regalo (la pirula).
  • Lo mismo pero la pirula se silabea (la pi-ru-la) lo que hacen 25 + 4.
  • Lo mismo que lo anterior pero el que cuenta hace trampa y dice el número que le hace ser el último.
  • Cuencos de cristal con bolas calientes.
  • (Regla transversal) El que llega tarde va primero.

Seguramente existan más variantes pero para el objetivo que nos ocupa es suficiente. La cuestión que hoy nos trae es que cualesquiera de estos métodos consumen un tiempo que podríamos emplear en jugar al fútbol. Así que pretendemos que la tecnología nos ayude a crear una aplicación que nos genere al azar el orden en el que los jugadores se turnan como portero antes del partido. Ya que estamos aprendiendo a programar, qué mejor que buscar un problema sencillo y abordable con el que empezar en este mundillo.

Como decía en capítulos anteriores, saber programar es cuestión de saber expresar un problema en unos términos que entienda un computador. Imaginaos que tenéis que aterrizar un avión sin tener ni idea y que tenéis a un piloto dándoos instrucciones por teléfono. En este caso no haréis nada a menos que se os den instrucciones muy precisas y actuaréis como máquinas tontas sin capacidad tomar decisiones autónomas. En ambos casos el lenguaje empleado no es lo esencial sino que simplemente es el medio para comunicar un mensaje que debe conducir a que se realice una determinada acción. En este contexto y para poder expresar nuestro mensaje de forma que un ordenador, un móvil o cualquier dispositivo con capacidad de computación lo entienda, debemos tener en cuenta las siguientes características de los computadores:

  • Son máquinas muy tontas que sólo tienen la capacidad de realizar operaciones aritméticas básicas (sumas, restas, divisiones, multiplicaciones y módulo) y lógicas (que por ahora vamos a ignorar).
  • No se acuerdan de los datos por arte de magia, necesitan apuntarlos en algún sitio: la memoria.
  • Serán máquinas muy tontas, pero puede hacer la misma tarea muchas veces sin quejarse y por tanto hacer una "jartá" de cálculos por segundo.
  • Los computadores no son máquinas creativas. Hacen lo que les decimos y en el orden que les indicamos. Ni más ni menos.
  • Son deterministas, es decir, no se toman la libertad de hacer lo que les da la gana en función de cómo se levantan. Si le damos unos datos para que haga un cálculo siempre va a devolvernos lo mismo. Por eso sospecha de un programador que dice "Esto ayer funcionaba".
  • Ya que son deterministas, el azar no existe. Si queremos que exista, hay que crearlo.

Dicho todo esto, os planteo un reto que puede parecer hasta más simple que el "Hola Mundo" y que está al alcance de todo el mundo: Describir paso a paso qué tiene que hacer una computadora para realizar un sorteo aleatorio que defina el orden en el que 7 u 8 jugadores deben turnarse como porteros. Para ello podéis comentar esta entrada (mira más abajo) proponiendo un orden numerado de acciones que indique a una computadora qué debe hacer paso a paso. Cada acción debe estar definida en lenguaje natural, nada de lenguajes de programación, ni pseudo-código, ni lenguas muertas. Y cuanto más detallado sea el proceso, más cerca estaremos de que una máquina lo entienda y estaremos mejor preparados para construir nuestra pequeña aplicación por fin en un lenguaje de programación. ¿Te atreves?

Nota: a fin de evitar el Spam y los Trolls los comentarios están moderados. La primera vez que realices un comentario, tendré que aceptarlo. A partir de ahí puedes comentar y automáticamente será aceptado siempre que utilices la misma dirección de correo.

 

 

16 septiembre, 2014

Posted In: Aprendiendo a programar, Tecnologería

Leave a Comment

« Página anteriorPágina siguiente »