sábado, 15 de junio de 2013

Callenge 2: Did you mean...?

Ver el enunciado original y descargar el archivo zip con los diccionarios
El objetivo del segundo problema del concurso, resumiendo el adornado enunciado del mismo, es dado un conjunto de caracteres (la palabra que requiere sugerencias) buscar todas las palabras de entre una lista de palabras dada en un fichero aparte (el diccionario) que se puedan construir con el conjunto de caracteres colocados en cualquier orden y, finalmente, ordenar estas palabras por orden alfabético.
Algunas veces me pregunto ¿por qué uso C++ en lugar de C cuando no uso objetos? Este programa me recuerda la respuesta: poder usar algunos objetos de clases ya programadas.
Una de ellas ya la empleé en el anterior problema, las clases de entrada y salida que me resultan más intuitivas que las funciones de entrada/salida de C. Para usarlas necesito incluir al principio, donde siempre detallo todos los ficheros de cabecera que empleo, la orden (la segunda es para acceder a un fichero del sistema de archivos):

#include <iostream>
#include <fstream>

El problema de emplear clases que no hemos programado nosotros es acceder a la documentación de la misma porque, al menos para mí, me resulta imposible recordar el nombre exacto de los métodos y propiedades que empleo ni el archivo de cabecera que debo añadir. Recuerdo, como mucho, el nombre de la clase por lo que constantemente tengo que acceder a la documentación. Aunque muchos de los IDE que se emplean para confeccionar programas (yo uso Anjuta en mi sistema Linux) te ayudan con los parámetros de las funciones, al final siempre tienes que buscar en la documentación. Yo empleo esta documentación on line que me resulta útil, cómoda y completa.
En este problema vamos a manejar cadenas de caracteres. Los arrays de char de C están bien pero tienen limitaciones, como tener que fijar su longitud máxima al definirlos, que hace que me incline a usar la clase string que tiene las ventajas de los anteriores pero soslayando sus inconvenientes. Obviamente no serán tan eficientes pero nos simplifican la vida. Así que incluyo el archivo de cabecera correspondiente:

#include <string.h>

Paso previo: representación por claves

Me he enrollado en generalidades inútiles y no me he centrado en el verdadero problema. ¡Vamos con él!
Al algoritmo a desarrollar le van a suministrar una palabra, iremos cambiando el orden de los caracteres de dicha palabra y, de las combinaciones que nos resulten seleccionaremos las que estén incluidas en el diccionario que nos propongan.
El algoritmo descrito en el párrafo anterior es lo que se suele llamar usar la fuerza bruta. Aunque generar todas las combinaciones no es un algoritmo difícil comprobar cada combinación contra todo el diccionario nos llevaría a un tiempo de ejecución inabordable.
Tiene que haber una solución mejor.
Como tenemos libertad para manipular los diccionarios mi propuesta es reordenar sus entradas guardando cada una de ellas por una clave por la que luego busquemos la palabra propuesta al algoritmo y, a ser posible, directamente obtengamos todas las combinaciones directamente.
¿Es posible esto? La respuesta es sí si obtenemos una representación para cada palabra que identifique a una palabra y a las diferentes combinaciones de sus letras por un mismo código. La representación más sencilla que se me ocurre es usar las mismas letras de la palabra pero ordenadas alfabéticamente. Así la palabra propuesta en el ejemplo de ejecución del enunciado (elvis), tendría como representación eilsv, que es la misma representación que tendrían las palabras soluciones lives y velis.
Resumiendo: procesamos el diccionario de tal forma que guardaremos las palabras lives y velis bajo la clave eislv. Cuando procesemos la palabra elvis la convertiremos a su representación (eislv) y con esa clave buscaremos en el diccionario modificado y directamente obtendremos las dos palabras guardadas con esa clave. Mucho más rápido que la fuerza bruta.
Ya tenemos por donde empezar, obtener la clave de una palabra. El algoritmo es sencillo aplicaremos a los caracteres que forman la palabra (tipo string) el conocido método de ordenación de la burbuja. No es muy eficiente para ordenar grandes conjuntos de datos pero es suficiente para el tamaño de las palabras que vamos a manejar.
Supongo que es conocido, pero para los que no trato de explicarlo en unas pocas palabras: Se trata de ir comparando todos los elementos con el primero y cada vez que se encuentra uno que debe ir antes en el orden se cambian entre sí los dos elementos. Al final de las comparaciones el elemento con el orden más bajo se habrá quedado en el primer lugar. Repetimos con el segundo elemento, comenzando a comparar con el tercero para conseguir que el segundo elemento quede en su sitio. Así sucesivamente hasta llegar al penúltimo lugar que se comparará, evidentemente, sólo con el último.
Nos vamos a aprovechar de la posibilidad de manejar las variables string como arrays de char con lo que el método queda tan sencillo como:

string calcKey(string word)
{
for (int i=0; i<word.size(); i++)
for (int j=i+1; j<word.size(); j++)
{
if (word[i]>word[j])
{
char c=word[i];
word[i]=word[j];
word[j]=c;
}
}
return word;
}

Le pasamos como parámetro un string, dentro del método se le aplica el método de la burbuja a sus letras quedando éstas ordenadas alfabéticamente y devolvemos como retorno de la función el string alterado.
Esta función falla si mezclamos mayúsculas con minúsculas en una palabra puesto que la representación de los caracteres minúsculos en el tipo char está antes que los minúsculos, de tal forma que se cumple 'Z' < 'a'.  Por ello, pese a que aparentemente todas las palabras, tanto de los diccionarios como las entradas al programa, están en minúsculas, como no nos lo especifica que tenga que ser así el enunciado vamos a programar un método que convierta todos los caracteres de una palabra a minúscula. Podría ser algo así:

void toLower(string& word)
{
for (int i=0; i<word.size(); i++)
if ((word[i]>='A') && (word[i]<='Z'))
word[i]=word[i]-'A'+'a';
}

El algoritmo es muy simple: si una letra está en el rango de las mayúsculas le sumamos la cantidad necesaria para convertirla a minúsculas.
Quiero hacer resaltar una diferencia en la implementación de ambos métodos. Ambos métodos trabajan directamente sobre la variable que representa al argumento pasado a la función, sin embargo en el primer caso devolvemos el valor de esta variable al final de la rutina con una instrucción return mientras que en el segundo no. ¿Por qué esta diferencia?
La respuesta está en la forma en que se pasa el argumento a la función. En el primer caso se trata de una variable, es decir de un valor (de hecho a esta forma de pasar argumentos se llama por valor) mientras que en el segundo caso estamos pasando no el valor de la variable sino la dirección de la variable que contiene el valor (eso significa el & detrás del tipo). En el primer caso si llamamos al método pasando como argumento una variable de tipo word lo que hará el programa es extraer el valor de esta variable y pasar el valor como argumento que se almacena en una variable interna del método (se dice que su alcance se limita al método y fuera de él no existe) lo que significa que el valor de la variable que se ha pasado como argumento en la llamada no se modifica. En el segundo caso lo que almacena la variable interna del método no es el valor sino la dirección de la variable que se pasa como argumento (esta forma de pasar argumento se llama por referencia) por lo que las modificaciones que hacemos en el interior del método se realizan directamente sobre la variable original. Por ello en el primer caso tenemos que devolver el valor final mientras que en el segundo no, aunque podríamos hacerlo por redundancia. Cuando luego usemos estas funciones veremos cómo este funcionamiento diferente nos permite hacer cosas diferentes con los parámetros de llamada.
También se puede definir un parámetro por referencia empleando la definición de puntero a tipo normal (string* en este caso). El funcionamiento es idéntico pero dificulta la legibilidad del código porque nos obliga a usar el operador dirección (&) al llamar a la función y el operador apunta a (*) en el interior del método. Realmente con la notación usada este trabajo lo realiza el compilador, de forma transparente, por nosotros.

Guardar el diccionario en memoria. std::multimap

Cuando necesito guardar datos en la memoria me alegro de haber cambiado de C a C++ gracias a algunas de las clases definidas en la enorme cantidad de clases definidas en el espacio de nombre std.
Estas clases son un conjunto de clases que permiten almacenar datos de cualquier tipo según cómo los vayamos a necesitar emplear. Unas clases los guardarán como vectores que, a diferencia de los arrays, pueden crecer o disminuir, otras como listas enlazadas (cada elemento guarda un puntero al elemento anterior o posterior en el orden), etc.
En nuestro caso necesitamos guardar un dato junto con una clave que lo representa y por el que lo buscaremos entre todos los datos guardados. Este tipo de almacenamiento lo implementa las clases map. Como en nuestro caso necesitamos almacenar varios elementos diferentes con la misma clave la clase que necesitamos es la clase multimap.
Para que estas clases puedan manejar cualquier tipo de dato el lenguaje C++ permite definir clases por plantillas. Más o menos es implementar una clase donde los datos con los que se trabaja son genéricos, es decir, pueden ser cualesquiera. En el momento de instanciar un objeto de esta clase, además del nombre de la clase como se especifica en las clases normales, definiremos una serie de tipos que serán con los que en concreto trabaje la instancia de la clase que definimos. Estos tipos se encierran entre signos menor/mayor a la derecha del nombre de la clase para especificar el tipo completo del objeto. Como esta declaración puede resultar larga y farragosa es preferible usar typedef para llamarla de una forma más amistosa. Así, por ejemplo, la clase que almacenará las palabras del diccionario será:

typedef multimap<string,string> strMap;

El primer tipo (string) es el tipo del dato que usamos como clave y el segundo (también string) es el tipo del dato en sí. En nuestro programa strMap va a ser el mismo tipo que multimap<strin,string>.
instanciamos un objeto de esta clase para guardar las palabras del diccionario. Lo vamos a definir fuera de todos los métodos del programa para que sea una variable global que podamos emplear en cualquier parte del programa:

strMap dict;

Los elementos que se guardan en este diccionario son pares de valores de los tipos que hemos establecido. Tenemos definida una clase que encapsula esta pareja de valores que también vamos a renombrar en otro typedef:

typedef pair<string,string> mapValue;

Estos tipos de almacenes de datos necesitan un tipo de datos que permita recorrerlos. Es decir, una tipo que nos permita definir una variable que guarda la posición de un dato de los del diccionario. Tiene la forma:

typedef multimap<string,string>::iterator itrMap;

Finalmente cuando busquemos entre los datos guardados en el diccionario lo haremos por su clave y la clase nos devolverá una pareja de valores del tipo anterior que apuntan al primer y al último valor del diccionario con esa clave. Como es una pareja de valores usará la clase pair así:

typedef pair<itrMap,itrMap> rangeMap;

Con todo esto ya tenemos la artillería necesaria para guardar las palabras de un diccionario con un índice que nos va a permitir recuperarlas en la forma deseada.
Vamos a programar un método al que pasaremos como argumento el nombre del fichero con la lista de palabras del diccionario.
El método abrirá el archivo usando una variable de la clase ifstream y leerá las palabras que contiene a una variable de tipo string llamada word.
Para cada palabra llamaremos al método toLower, programado con anterioridad, para asegurarnos que sus letras sean minúsculas. Después llamaremos al método calcKey para que nos devuelva la clave que representa a esta palabra. Con la clave y la propia palabra instanciaremos un objeto de la clase mapValue que guardaremos en la variable dict mediante su método insert (que almacena pares de valores clave-valor en su interior).
En lenguaje C++:

void readDictionary(const char* dictName)
{
string word;
ifstream fileDict;
fileDict.open(dictName, ios::in);
while (true)
{
fileDict >> word;
if (fileDict.eof()) break;
if (word.size()>0)
{
toLower(word);
dict.insert(mapValue(calcKey(word), word));
}
}
}

Observemos que construimos el objeto mapValue directamente en la llamada al método insert y que el primer parámetro en el constructor es el resultado de la llamada al método calcKey y que el segundo es la misma variable word aprovechándonos del hecho de que no se modifica al ser pasada por valor al método anterior.

Algoritmo principal

Ya casi tenemos todo hecho. Sólo nos falta leer los datos con el formato que nos van a ir llegando por el canal de entrada, procesarlos y escribir el resultado en la salida.
Vayamos por partes.
En primer lugar nos indican que en la entrada nos llegarán líneas que comienzan por el carácter # que son comentarios, por lo que su contenido debe ser ignorado por nuestro programa. Aunque parece que nos van a llegar sólo tres comentarios en donde nos marca el ejemplo vamos a hacer nuestro programa como si en cualquier parte de los datos nos pudiera llegar un comentario.
Vamos a escribir un par de métodos para saltarnos los comentarios. Los llamaremos (llamaremos, en realidad a uno de ellos y éste llamará al otro si es necesario) antes de leer un dato de la entrada. Usaremos el método peek de la clase cin (entrada estandar) que nos devuelve el siguiente carácter de la entrada pero sin eliminarlo, es decir que se devolverá también en la siguiente lectura, para averiguar si es el carácter #. En caso afirmativo se llamará al segundo método que, en un bucle, leerá caracteres de la entrada, usando el método get, hasta que se devuelva e carácter que representa el final de la línea.
El resultado puede ser:

void jumpEol()
{
while (cin.get()!='\n');
}

void jumpComment()
{
while (cin.peek()=='#')
jumpEol();
}

Cada vez que necesitemos leer un dato de la entrada llamaremos al primer método, que se saltará las líneas que sean comentarios, luego leeremos el dato y finalmente llamaremos al segundo método por si en alguna línea hay más datos (aunque en la especificación se nos indique que sólo hay un dato por línea) y, sobre todo, para asegurarnos que leemos el final de línea y nos quedamos posicionados en el comienzo de la siguiente.
Leemos el primer dato, que es el nombre del archivo que vamos a usar como diccionario y llamamos al método que lee el archivo y lo almacena en nuestra variable multimap.
El segundo dato es un entero que nos marca la cantidad de palabras a procesar. Lo guardamos y lo usamos como límite de un bucle.
Para cada palabra la leeremos del canal de entrada. Extraeremos la clave que corresponde a esta palabra y con ella buscamos en la variable dict. Nos devolverá dos punteros a las palabras primera y última del diccionario con esa clave.
Las variables de tipo puntero en estos contenedores funcionan pueden funcionar como índices en un bucle y, con el operador ->, funcionan como punteros al tipo guardado en el diccionario (string en nuestro caso).
Usaremos los punteros devueltos como límites de un bucle que extraerá los datos string que serán las palabras que sugeriremos.
Como tenemos que ordenarlos alfabéticamente usamos un segundo contenedor más sencillo pensado para guadar datos ordenados. Es la clase set para tipo de datos string. En este contenedor iremos añadiendo las palabras que sacamos del diccionario pero ya ordenadas, por lo que emplearemos un bucle.
Cuando acabe el primer bucle escribiremos las palabras de este contenedor set recorriéndolo con un último bucle y escribiendo su contenido en la salida.
Es más claro si lo expresamos con instrucciones de C++:

int main()
{
set<string> sugg;
string word;
jumpComment(); cin >> word; jumpEol();
readDictionary(word.c_str());
int N;
jumpComment(); cin >> N; jumpEol();
for (int i=0; i<N; i++)
{
jumpComment(); cin >> word ; jumpEol();
cout << word << " ->";
toLower(word);
rangeMap range=dict.equal_range(calcKey(word));
sugg.clear();
for (itrMap it=range.first; it!=range.second; ++it)
{
if (it->second.compare(word)!=0)
sugg.insert(it->second);
}
for (set<string>::iterator it=sugg.begin(); it!=sugg.end(); ++it)
cout << " " << *it;
cout << endl;
}
return 0;
}

En contra de lo dicho al principio no he usado sinónimos para los tipos derivados de set. Ha sido por pura vagancia.
Y como siempre aquí se puede ver el resultado final al completo.

domingo, 9 de junio de 2013

Challenge 1: Bitcoin to the future

Ver el enunciado original.
El sueño de cualquier inversor: conocer las cotizaciones de un valor con anticipación suficiente para beneficiarnos de las fluctuaciones.
En nuestro problema se trata del cambio entre la moneda por excelencia en los intercambios (futuros) en Internet y nuestro querido (y cada vez más deseado) Euro.
El programa que se busca (como todos los de este concurso) es una aplicación de consola, es decir sin entorno gráfico, que tiene que leer los datos de su entrada standard (cin en el lenguaje C++) y escribir los resultados en su salida standard (cout). Tanto los datos de entrada como los de salida son ASCII, es decir, texto legible por los humanos corriente y moliente. Las utilidades para la presentación de las soluciones en el concurso se ocuparán de redirigir la entrada y salida de nuestra aplicación para que los datos adecuados fluyan por ellos para poner a prueba nuestra propuesta de solución y verificar que es correcta. Todo esto es común para todos los problemas del concurso, ya no incidiré más sobre ello (¡prometo!).
La aplicación debe resolver un número determinado de casos distintos. Esto nos obliga a mejorar la rapidez de resolución de nuestro algoritmo para que éste encuentre la respuesta en un tiempo razonable. En este primer problema el número máximo de casos propuestos será de 100.
Cuando el problema indica claramente los límites de los parámetros nos facilita la vida enormemente. Sobre todo cuando esos límites deben traducirse en reserva de memoria (dimensionamiento de variables). No va a ser el caso de esta aplicación.
El valor que nos llegará en la primera línea de datos a nuestra entrada va a ser el número de casos a estudiar. Como los casos son completamente independientes entre sí el resolver un número variable de casos solamente va a implicar en nuestra aplicación la existencia de un bucle en cuyo interior se encierre el verdadero algoritmo.
Lo codificaremos así:

    int T;
    cin >> T;
    for (int t=0; t<T; t++)
    {
        // algoritmo
    }

Poco que comentar. Leemos el número de casos a resolver (T) y ejecutamos un bucle, controlado por la variable t, que repita el algoritmo el número adecuado de veces. Es una costumbre, a la que nadie nos obliga por supuesto, en los bucles de C y C++ que el índice que lo controla comience en cero y termine en uno menos que el número de repeticiones. Esto es así por coherencia con la forma de indexar los arrays en estos lenguajes, aunque en este caso concreto no haya ningún array al que acceda el bucle.
Para cada caso recibiremos la cantidad de euros con la que comenzamos en una sola línea y, en la siguiente línea, un número indeterminado de valores de la cotización (euros por bitcoin) sucesivos.
Nuestra misión es calcular la máxima cantidad de euros que podemos tener al final del periodo si hacemos las compras y ventas de moneda en los momentos más adecuados.
Nos indican que nunca compraremos ni venderemos fracciones de moneda ni la cotización de las monedas va a ser fraccionaria. Esto nos dice que las variables que emplearemos serán de tipo int (admite en C++ un valor máximo razonable para ser muy rico). Es un tipo de datos que se maneja muy bien en C++.
Cuando no se limita, como en este caso, la cantidad de datos que debemos manejar nos entra un nerviosismo especial a los programadores porque no tenemos ni idea de cuánto dimensionar la variable que va a contener los datos con los que trabajaremos para no quedarnos cortos pero sin pasarnos para no obtener errores de memoria agotada.
Esta dificultad desaparece cuando no se necesitan guardar los datos que se manejan sino que se puede ir aplicando sobre ellos el algoritmo conforme los vamos adquiriendo. ¿Será este nuestro caso? ¡Ojalá!
El algoritmo del primer problema del concurso, para no desmoralizar, es muy sencillo. ¿Cuándo debemos comprar bitcoins? Cuando vayan a empezar a subir. ¿Y cuándo venderlos? Cuando vayan a empezar a bajar. Así que es un problema que se resume en encontrar máximos y mínimos relativos en la serie de datos que vamos recibiendo. Es una buena noticia porque sólo necesitamos guardar dos datos de la serie para localizar los máximos y los mínimos relativos, el valor anterior y el valor actual. Así que definiremos dos valores int: last, para el valor del cambio anterior y cambio para el actual. Cuando acabemos el ciclo pasaremos el valor de cambio al valor de last, perdiendo el valor guardado en ésta, y leeremos el siguiente valor de la serie en cambio.
Es evidente que cuando sea bueno comprar bitcoins será mejor vender todos los euros que sólo parte. Y lo mismo al revés. Así que primero compraremos bitcoins y después compraremos euros y así sucesivamente. Como comprar o vender euros y las condiciones que lo rigen son iguales, inversas en realidad, no haremos dos trozos de programa diferentes sino uno mismo controlando la transacción que toque por medio de una variable de tipo bool (valores true o false) que llamaremos buyBit y que valdrá true cuando toque vender euros y comprar bitcoins.
Como he dicho antes en cada transacción venderemos toda la moneda que tengamos por la otra. O sea que podemos caer en la tentación de usar una única variable para la cantidad de dinero que tenemos en cada momento y, por medio de la variable buyBit saber qué moneda representa. Sería un error porque nos han especificado que no podemos comprar o vender fracciones de moneda y, como el cambio se expresa en euros por bit (y tampoco puede ser fraccionario) cuando cambiemos bitcoins por euros siempre podremos cambiar todos los bitcoins por euros, pero lo contrario no es cierto puesto que al dividir los euros que tengamos por los euros por bitcoin del cambio nos puede quedar un resto de euros que no podemos cambiar. Estos euros quedarán en nuestra variable de euros para ser sumados a los euros que obtengamos por la venta de los bitcoins. Así necesitaremos una variable por cada moneda. Las llamaremos euro y bitcoin.
Empecemos a escribir el interior del bucle definiendo las variables que vamos a emplear y dándoles, o leyendo según el caso, sus valores iniciales:

        int euro,bitcoin,cambio,last;
        bitcoin=0;
        cin >> euro;
        cin >> last;
        bool buyBit=true;

Comenzamos con 0 bitcoins y la cantidad en euros que nos digan. buyBit vale true porque la primera operación que debemos hacer es comprar bitcoins. Como con el primer valor de la serie de cambios no podemos hacer nada (porque no hay valor anterior que guardar) leemos directamente el primer valor en la variable last.
Y ahora comenzará un bucle en el que en cada repetición procesaremos cada valor de la serie de cambios que recibamos. Cuando no sé qué valor poner en la comparación de finalización de un bucle pongo true (como si no fuera a terminar nunca) y después, según lo vea más sencillo, o escribo la comparación en el lugar normal o hago una comparación en mitad del bucle y programo una instrucción break para que salga del bucle.
Quedaría algo así:

        while (true)
        {
            cin >> cambio;
            if (buyBit)
            {
                if (cambio>last)  // buy bitcoin at last
                {
                    bitcoin=euro/last;
                    euro-=last*bitcoin;
                    buyBit=false;
                }
            }
            else
            {
                if (cambio<last) // buy euros at last
                {
                    euro+=bitcoin*last;
                    bitcoin=0;
                    buyBit=true;
                }
            }
            last=cambio;
        }

Creo que necesita poca explicación. Leemos un nuevo valor de cambio. Según el valor de buyBit hacemos la comparación en un sentido o la inversa. Si toca comprar bitcoins esperaremos a que el cambio empiece a subir (cambio>last) y en ese momento cambiamos al valor de cambio guardado en last. Para el cambio inverso lo mismo pero al revés. La única diferencia entre ambos procesos es que en un caso obtenemos la cantidad de la nueva moneda mediante una división y en el inverso por una multiplicación, por lo que en el primer caso nos pueden quedar unos euros de resto mientras que en el segundo siempre se cambian todos los bitcoins. Cuando se cambia de moneda cambiamos el valor de la variable buyBit. Al final del bucle guardamos el valor de cambio en last.
La condición de final del bucle es que se termine la línea de datos, es decir, toda la serie de cambios están en una única línea.
Yo sé que las variables de tipo stream (como cin) tienen un método eof que vale true después de que intentamos hacer una lectura después del final de un fichero. ¿Habrá algo parecido para detectar el final de una línea? Como no lo sé la solución es buscar en la ayuda de la clase stream. En otros lenguajes el método se denomina eol (end of line), así que busco algo que se parezca a eso sin suerte. No digo que no exista, digo que yo no lo he encontrado.
El inconveniente, para este caso particular, es que a la hora de procesar la entrada de caracteres la librería de C++ trata los finales de línea como si fueran espacios.
Hay un método que lee una línea de caracteres entera y la almacena en una variable de tipo string. Desde esta variable string podríamos procesar la lectura de los enteros.
Yo me he decidido por otra forma usando el método peek de cin que devuelve el siguiente carácter que va a ser leído del stream pero sin quitarlo, es decir que se usará en la siguiente lectura. Me beneficio de que, después de leer un entero de cin el siguiente carácter listo para ser leído es el que está justo detrás del último número leído (normalmente el espacio que separa las cantidades). ¿Cómo me aseguro de que realmente las cosas funcionan así? Usando el debug y ejecutando paso a paso el programa para ver cómo se comporta. ¡Qué gran invento para la programación!
Lo que voy a hacer es, al comienzo del bucle, comprobar si el próximo carácter que se va a leer es un final de línea ('\n') y el bucle debe terminar sin procesar nada más porque significará que el anterior número leído, en la repetición del bucle anterior, era el último de la línea de datos. Añado una comprobación anterior que, usando el mismo método peek, va a ir leyendo todos los caracteres espacio en blanco de tal forma que nos funcione el algoritmo aunque detrás del último número de la línea haya espacios en blanco. El comienzo del bucle quedaría:

        while (cin.peek()==' ') cin.get(); // salto espacios
        if (cin.peek()=='\n') break;  // EOL

Cuando ha terminado el bucle, si la última transacción que hemos hecho ha sido compra de bitcoins, deberemos cambiarlos a euros por el último cambio que se habrá quedado guardado en la variable last. Es la mejor opción porque ni no los habríamos cambiado antes.

        if (!buyBit)
            euro+=bitcoin*last;

Así que ya sólo queda, para terminar el algoritmo, escribir en la salida del programa el resultado que nos piden que es la cantidad de euros que obtendremos:

        cout << euro << endl;

Y eso es todo. Compilar, ejecutar y enviar la solución.
De aquí se puede descargar el programa completo.

Introducción

Me encanta la programación y me encantan los lenguajes de programación.
Es por ello que no soy fan de ninguno de ellos. Creo que todos tienen su lugar en este mundo de la programación y, cuantos más conoces, mejor vas a elegir el lenguaje que mejor se adapte a la resolución de un problema específico. Aunque, a la postre, cualquier problema se va a poder resolver con cualquier lenguaje de carácter general.
Hecha esta declaración de principios quiero dejar claro que aunque, como se adivina por el título del blog, quiero dedicar este blog al lenguaje de programación C++ (al que como mucha gente de mi edad he llegado desde el mítico C) mi interés no quiere centrarse en el lenguaje (que emplearé como vehículo inevitable) sino en la programación.
Y es que en mi modesta opinión hay excesivos cursos de programación que al final son simples cursos de un lenguaje específico (mejores, pocos, o peores, más frecuentes) y carestía de verdaderos cursos en que se enseñe a programar, a pensar como lo hacemos (perdóneseme la inmodestia) los programadores que es relativamente independiente del lenguaje en el que expresemos estos pensamientos.
Así que el objetivo de este hueco del ciberespacio es presentar la forma en que yo he resuelto una serie de problemas de programación (en lenguaje C++) intentando centrar el foco en el proceso de resolución más que en la explicación del funcionamiento del programa resultado.
Sé que es difícil, pero lo voy a intentar.
Como problemas voy a usar los que propusieron en el concurso de programación de Tuenti de este año. Es un concurso de una mecánica divertida, que recomiendo encarecidamente para la edición del próximo año, al que me he presentado ya dos veces y que ambos años, por unas circunstancias u otras, no he podido dedicar el tiempo que necesitan (por circunstancias laborales) durante la semana de duración de la prueba.
Es seguro que las entradas con la resolución de los problemas se alargarán en el tiempo más allá de la semana de tiempo que había en el concurso para resolverlos (el tiempo, ese bien tan apreciado y escaso) y no puedo prometer que sepa resolver todos pero prometo transcribir, lo mejor que sepa, el proceso que seguí para su resolución.

¡Espero que sea de utilidad para alguien! Si es así me gustaría recibir comentarios. Y si estos incluyen críticas y sugerencias mejor que mejor. Y si a alguien le parece tan bueno que merece la pena hacerse seguidor ya no os imagináis el gusto que me daréis.
En cualquier caso, gracias por leer este blog...