Publi

Reemplazar cadenas de texto en C++ (string y Glib::ustring)

Una herramienta muy útil a la hora de hacer nuestros programas es buscar y reemplazar texto de una cadena de caracteres. Imaginad por ejemplo el uso de plantillas, éstas serán cadenas de texto en las que reemplazaremos algunas palabras clave para generar el mensaje, o la salida que queremos. En principio, hacemos uso de las llamadas a métodos de string para encontrar una subcadena de texto dentro de una cadena, y más adelante reemplazarla por otra:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <string>

using namespace std;

int main()
{
  string original = "Me voy a dormir, que todos sabemos que a partir de las 12 de la noche es hora de dormir.";

  string::size_type pos = original.find("dormir", 0);

  cout << "Cadena original: "<<original<<endl;

  if (pos < string::npos)
    original.replace(pos, string("dormir").length(), "programar");

  cout << "Cadena resultante: "<<original<<endl;
  return 0;
}

Para compilar:

&g++ -o replace replace.cpp

En este caso, tenemos la cadena original, que con el método find() encontraremos en qué posición está la primera aparición de la palabra dormir, luego si se ha logrado encontrar (pos programar, para ello tendremos que decir en qué posición vamos a empezar a reemplazar y cuántos caracteres tenía la cadena antigua.

Ahora bien, si lo que queremos es reemplazar todo, basta con hacer un bucle en la búsqueda y reemplazo de la cadena, mientras vayamos encontrando posiciones con las que reemplazar el texto, de la siguiente forma:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <string>

using namespace std;

int main()
{
  string original = "Me voy a dormir, que todos sabemos que a partir de las 12 de la noche es hora de dormir.";

  string::size_type pos = 0;

  // Ahora vamos a poner las subcadenas de origen y destino en variables,
  // para tenerlas más a mano.
  string fromStr = "dormir";
  string toStr = "programar";

  cout << "Cadena original: "<<original<<endl;

  // Repetimos mientras estemos encontrando cadenas.
  while ((pos = original.find(fromStr, pos)) < string::npos)
    {
      original.replace(pos, fromStr.length(), toStr);
      pos+=toStr.size();    // Muy importante sumar el tamaño de la
                // cadena para evitar bucles infinitos.
    }

  cout << "Cadena resultante: "<<original<<endl;
  return 0;
}

Si ejecutamos este código, se reemplazarán las dos apariciones de dormir por programar. Aunque en medio del código hay un comentario curioso: “para evitar bucles infinitos”, y lo que hacemos es sumar a la posición el tamaño de la cadena por la que reemplazamos. Éste será un caso especial en el que la cadena a reemplazar esté contenida en el reemplazo (o sea la misma), es decir: si ahora en lugar de reemplazar “dormir” por “programar”, lo hacemos con “de” por “del”, es decir, tenemos que buscar “de” dentro de origen y cambiarlo por “del”, pero si no hacemos que tras el reemplazo, pos se sitúe detrás de “del”, volverá a encontrar de nuevo “de” por lo que el programa se quedará en bucle infinito y el primer “de” que se encuentre pasará a ser “delllllll…. con un número infinito de l” (sólo lo veremos si metemos un cout en medio).

Esto va tomando forma, tenemos una buena forma de buscar y reemplazar, por lo tanto vamos a crear una función para que sea fácil llamarla y realizar la tarea, aunque vamos a darle un valor añadido, podremos dar un offset (es decir, que no empiece por el principio, basta con inicializar pos con otro valor), y además, podremos sustituir un número determinado de veces, es decir, si nuestra palabra clave aparece 20 veces y sólo queremos sustituir 10, que lo podamos hacer.

Hacemos lo siguiente:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <string>

using namespace std;

string replace(string source, string fromStr, string toStr, int offset=0, int times=0)
{
  int total = 0;
  string::size_type pos=offset;
  while ( ( (pos = source.find(fromStr, pos)) < string::npos) && ( (times==0) || (total++<times) ) )
    {
      source.replace(pos, fromStr.length(), toStr);
      pos+=toStr.size();
    }
  return source;
}

int main()
{
  string original = "Me voy a dormir, que todos sabemos que dormir es bueno y dormir ayuda a regenerar neuronas. ¡Ay! A partir de las 12 de la noche es hora de dormir.";

  cout << "Cadena original: "<<original<<endl;

  cout << "Cadena resultante: "<<replace(original, "dormir", "programar", 20, 2)<<endl;

  return 0;
}

La salida tiene que ser algo como:

Cadena original: Me voy a dormir, que todos sabemos que dormir es bueno y dormir ayuda a regenerar neuronas. ¡Ay! A partir de las 12 de la noche es hora de dormir.
Cadena resultante: Me voy a dormir, que todos sabemos que programar es bueno y programar ayuda a regenerar neuronas. ¡Ay! A partir de las 12 de la noche es hora de dormir.

Es decir, empezaremos a buscar la subcadena desde el carácter 20, y sustituiremos la subcadena dos veces a partir de ahí. Aunque como la función tiene valores iniciales, por defecto, si no especificamos el offset empezaremos desde el principio, y si no decimos el número de veces, éste será 0, que significará reemplazar todas.

Una posible mejora de esta función es utilizar Glib::ustring (parte de Glibmm), Glib es una biblioteca de utilidades multiplataforma, que implementa su propia versión de string con soporte para cadenas UTF8 y es que si utilizamos caracteres especiales en nuestras cadenas (ñ, tildes, ç, símbolos y más), tarde o temprano vamos a tener problemas con la codificación. Lo más normal es lo siguiente:

1
2
3
4
5
6
7
#include <string>

int main()
{
  cout << string("mañana").length() << endl;
  return 0;
}

Y veamos qué valor nos da, porque sabemos que “mañana” tiene 6 letras, pero en algunos sistemas obtendremos 7 y esto es por utilizar una codificación multibyte. UTF-8 por ejemplo utiliza 2 bytes para representar la ñ. Ahí entra Glib::ustring, esta biblioteca (libre, por supuesto), se comporta exactamente igual que string, pero nos da ese soporte UTF-8 que nos falta. Como primer ejemplo podemos hacer:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <string>
#include <iostream>
#include <glibmm/ustring.h>

using namespace std;

int main()
{
  cout << Glib::ustring("mañana").length()<<endl;
  cout << string("mañana").length()<<endl;

  return 0;
}

Para compilar:

$ g++ -o wordlength wordlength.cpp `pkg-config –libs –cflags glibmm-2.4`

Tendremos que tener instalada la biblioteca glibmm (a día de hoy la versión 2.4)

En este ejemplo (si el archivo fuente lo hemos salvado como utf-8 nos dará el valor correcto de caracteres.

Y por supuesto podemos hacer que la función de reemplazar cadenas sea compatible con Glib::ustring, sólo sustituyendo cada “string” por “Glib::ustring”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <glibmm/ustring.h>

using namespace std;

Glib::ustring replace(Glib::ustring source, Glib::ustring fromStr, Glib::ustring toStr, int offset=0, int times=0)
{
  int total = 0;
  Glib::ustring::size_type pos=offset;
  while ( ( (pos = source.find(fromStr, pos)) < Glib::ustring::npos) && ( (times==0) || (total++<times) ) )
    {
      source.replace(pos, fromStr.length(), toStr);
      pos+=toStr.size();
    }
  return source;
}

int main()
{
  Glib::ustring original = "Seguro que mañana será un buen día, porque soy una persona que siempre piensa en el mañana.";

  cout << "Cadena original: "<<original.raw()<<endl;

  cout << "Cadena resultante: "<<replace(original, "mañana", "pasado mañana").raw()<<endl;

  return 0;
}

Tenemos que tener en cuenta también que para utilizar Glib::ustring junto con cout debemos utilizar el método raw(), para pasarle simplemente los bytes y no los caracteres. (De todas formas, no tenemos por qué tener problemas con string para este último ejemplo).

Foto: David J. Laporte (Flickr) CC-by

También podría interesarte...

Leave a Reply