Los pasos son simples:
- tenemos como entrada una cadena de caracteres,
- dividimos la cadena según el caracter '.' (punto),
- convertimos cada parte a entero,
- guardamos cada número entero de forma ordenada en un vector.
Antes de comenzar ¿Podemos usar strcmp para comparar dos versiones? No, no podemos. Ejemplo: strcmp("1.9", "1.10") > 0, cuando en realidad la versión 1.9 es menor a 1.10.
Imaginemos que tenemos una cadena:
std::string ver = "1.10.2.3";La clase std::string tiene la función miembro find, la cual podemos usar para buscar casi cualquier cosa dentro de la cadena. ¿Qué debemos buscar? Los puntos, sabiendo donde está cada punto, podemos ir recortando la cadena en sus distintas partes ("1", "10", "2", y "3"). Buscamos la ubicación del primer punto:
size_t i = ver.find('.');
if (i != std::string::npos)
std::printf("El punto fue encontrado en la posición %d\n", i);
else
std::printf("No hay punto en la cadena\n");
Ejecutando el código anterior, deberíamos obtener el mensaje: El punto fue encontrado en la posición 1¿Qué es size_t? Es como el tipo "unsigned int", el tipo de dato retornado por el operador sizeof() y utilizado como índice en las funciones de std::string.
¿Qué es std::string::npos? Es el máximo valor posible de un size_t y se utiliza para indicar (en este caso) que la función find falló (no encontró el punto).
Una vez que tenemos la posición del punto, podemos obtener la porción de texto que contiene la primer cifra, para eso usamos la función miembro substr:
std::string primer_cifra = ver.substr(0, i);Esto significa: che vos, función substr, devolveme el pedazo de cadena de caracteres que va desde el índice 0, y tiene una longitud de i-caracteres.
El proceso puede ser repetido tantas veces como queramos para seguir obteniendo cifras. Por ejemplo, para la siguiente cifra debemos buscar (find) el siguiente punto, pero comenzando desde el que encontramos hace un rato:
i++; // ir a la posición siguiente del punto '.'
size_t j = ver.find('.', i);
¿Qué es (o hace) el segundo argumento de find? Le indica a la función desde donde debe comenzar a buscar. La primera vez que usamos find este argumento no se especificó, porque por omisión toma el valor 0, es decir, buscar desde el inicio de la cadena.¿Por qué i++? Porque si comenzáramos a buscar un punto desde i, nos devolvería la misma posición i (porque justamente, en i, está el primer punto que encontramos). Entonces debemos avanzar una posición.
Ahora, debemos recortar la segunda cifra:
std::string segunda_cifra = ver.substr(i, j-i);La segunda cifra, comienza desde i y tiene una longitud igual a j-i. Para comprender esto, vea la siguiente figura:
De esta forma, ver.substr(i, j-i) nos devuelve la cadena "10". La versión completa del algoritmo puede quedar algo así:
#include <vector> // Por std::vector
#include <string> // Por std::string
#include <cstdlib> // Por std::strtol
#include <cstdio> // Por std::printf
int main()
{
std::string ver = "1.10.2.3";
// Vector con cada cifra.
// Luego del procesamiento esto debería ser = { 1, 10, 2, 3 }
std::vector<int> cifras;
size_t i = 0; // Comenzamos desde i=0
size_t j = 0;
// Repetir hasta no llegar al final de la cadena
while (j != std::string::npos) {
// Buscar próximo punto
j = ver.find('.', i);
std::string cifra;
// Si se encontró un punto
if (j != std::string::npos) {
// Recortamos desde i hasta j
cifra = ver.substr(i, j-i);
// El nuevo comienzo para buscar puntos será "j+1"
i = j+1;
}
// Si no se encontró un punto
else {
// Recortamos desde "i" hasta el final de la cadena
cifra = ver.substr(i);
}
// Obtener el valor entero de la cifra
int cifra_int = std::strtol(cifra.c_str(), NULL, 10);
// Agregar la cifra al vector
cifras.push_back(cifra_int);
}
for (size_t i=0; i<cifras.size(); ++i)
printf("cifras[%d] = %d\n", i, cifras[i]);
return 0;
}
La salida del anterior programa es:cifras[0] = 1 cifras[1] = 10 cifras[2] = 2 cifras[3] = 3Con este código, podríamos implementar una clase "Version" con la siguiente interfaz:
class Version
{
public:
Version(const char*);
Version(const std::string&);
bool operator==(const Version& u) const;
bool operator<(const Version& u) const;
// ...
};
En un próximo post voy a colocar una posible implementación de la clase "Version".


4 comments:
Epa cifras de tipo int, esto significa que tu PC tiene mucha memoria (o que planeas tener versiones hasta 32767).
Los cabernicolas de los microcontroladores usariamos.
unsigned char cifras;
Hablando enserio, si el número de versión estuviera limitado (lo cual tiene sentido), ejemplo de 0 a 99, sería más fácil hacer la comparativa en la clase Version.
Ejemplo en una version tipo:
d1.d2.d3
se hace
long ver_comp=d3+d2*100+d1*100*100;
(se puede generalizar para diferentes rangos y diferente nro de digitos)
y listo comparando ese valor se pueden comparar versiones.
Sobre el manejo de String, ya sabés mi opinión.. ¿puedo nombrar Java en un blog de C++ o se tilda todo? =P, una linea de código:
String vect[]=version.Split("\\.");
Luego parseo los nros para calcular ver_comp y listo =)
> Epa cifras de tipo int, esto significa que tu PC tiene mucha memoria (o que planeas tener versiones hasta 32767).
Sí, también significa que mi compilador es de 32 bits, al parecer vos estás usando micros de 16 bits (ints de 16 bits no los veo desde que dejé de usar el Turbo C :)
> Los cabernicolas de los microcontroladores usariamos.
>
> unsigned char cifras;
Sí, un "unsigned char" estaría bastante bien para ahorrar espacio.
> Hablando enserio, si el número de versión estuviera limitado (lo cual tiene sentido), ejemplo de 0 a 99, sería más fácil hacer la comparativa en la clase Version.
A veces el número de versión no está muy limitado (por ejemplo, si colocamos el número de compilación, 1.0.7080)
> Ejemplo en una version tipo:
> d1.d2.d3
>
> se hace
>
> long ver_comp=d3+d2*100+d1*100*100;
>
> (se puede generalizar para diferentes rangos y diferente nro de digitos)
>
> y listo comparando ese valor se pueden comparar versiones.
La solución es muy buena, y de lo más rápida. Tengo otra forma que te va a gustar más :)
long ver_comp = d3 | (d2 << 8) | (d1 << 16);
En este caso d1, d2 y d3 pueden llegar hasta 255.
> Sobre el manejo de String, ya sabés mi opinión.. ¿puedo nombrar Java en un blog de C++ o se tilda todo? =P
Yo ya nombré Java en uno de los primeros post, y el blog no se colgó, pero noté que blogspot se volvió un poco más lento :P
> una linea de código:
>
> String vect[]=version.Split("\\.");
>
> Luego parseo los nros para calcular ver_comp y listo =)
Diste justo en el clavo, el problema principal es la falta de un algoritmo split en la STL. Por suerte hay implementaciones como la de Boost, pero por mala suerte Boost es gigantesca. En otro post voy a ir colocando algunas funciones "must to have" de C++ (split, trim, etc.).
Gracias por los comentarios!
Hola David,
Yo para este tipo de cosas me hice un string tokenizer:
/*
Devuelve en el vector tokens los elementos obtenidos del string str, delimitados
por los caracteres de delimiters
*/
void tokenize (const string& str, vector& tokens, const string& delimiters = " ") {
string::size_type last_pos = str.find_first_not_of (delimiters, 0);
string::size_type pos = str.find_first_of (delimiters, last_pos);
while (string::npos != pos || string::npos != last_pos) {
tokens.push_back (str.substr (last_pos, pos - last_pos));
last_pos = str.find_first_not_of (delimiters, pos);
pos = str.find_first_of (delimiters, last_pos);
}
}
perdón, por alguna razón no sale el ampersand en la signatura, lo reemplazo por su HTML: amp;
void tokenize (const string amp; str, vector amp; tokens, const string amp; delimiters = " ") {
Publicar un comentario en la entrada