Publi

Reemplazar cadenas en C++, esta vez desde un map, para múltiples sustituciones

Hace unos días hablamos de cómo reemplazar cadenas de texto en C++, tuvimos un método para copiar y pegar en nuestros proyectos muy fácil, pero cuando queremos hacer múltiples sustituciones podemos tener un problema: demasiadas llamadas a la función que producirán un código un poco feo…

Para ello podemos utilizar el contenedor map de C++ y crear la misma función replace() que creamos hace unos días, pero esta vez aceptando un mapa como argumento, así buscaremos en cada una de las claves, y lo sustituiremos por cada uno de los valores que encontremos:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
#include <string>
#include <map>

using namespace std;

string replace(string source, std::map<string,string>strMap, int offset=0, int times=0)
{
  int total = 0;
  string::size_type pos;

  for (std::map<string, string>::iterator i=strMap.begin(); i!=strMap.end(); ++i)
    {
      string fromStr = i->first;
      string toStr = i->second;
      pos=offset;
      while ( (pos = source.find(fromStr, pos)) < string::npos)
    {
      if ( (times!=0) && (total++>=times) )
        return source;  // Don't work anymore

      source.replace(pos, fromStr.length(), toStr);
      pos+=toStr.size();
    }
    }
  return source;
}

int main()
{
  string original = "Algunas veces para hacer pruebas escribo tonterías";

  map<string,string> mapa;
  mapa["Algunas veces"] = "Siempre";
  mapa["tonterías"] = "lorem ipsum";

  cout << "Original string: "<<original<<endl;

  cout << "Resulting string: "<<replace2(original, mapa)<<endl;

  return 0;
}

Y en este caso, podemos añadir los elementos que queramos al mapa, y todos esos elementos se buscarán en la cadena a sustituir. Nos puede venir bien cuando no tenemos claro todas las cadenas que vamos a sustituir, sino que, sobre la marcha vamos generando diferentes reemplazos, y luego queremos aplicarlos todos juntos.

Pero con esta función podemos tener un problema, y es que, si las palabras clave se corresponden con algunas sustituciones y viceversa, esta función no vale, tendremos que iterar el mapa para cada una de las sustituciones, en lugar de hacerlo de forma global y más o menos llamar al antiguo replace() con cada par de cadenas que vayamos encontrando:

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#include <iostream>
#include <string>
#include <map>

using namespace std;

string replace2(string source, std::map<string,string>strMap, int offset=0, int times=0)
{
  int total = 0;
  string::size_type pos=offset;
  string::size_type newPos;
  string::size_type lowerPos;

  do
    {
      string rep;
      for (std::map<string, string>::iterator i=strMap.begin(); i!=strMap.end(); ++i)
    {
      string fromStr = i->first;

      newPos = source.find(fromStr, pos);
      if ( (i==strMap.begin()) || (newPos<lowerPos) )
        {
          rep = fromStr;
          lowerPos = newPos;
        }
    }

      pos = lowerPos;
      if (pos == string::npos)
    break;

      string toStr = strMap[rep];

      source.replace(pos, rep.length(), toStr);
      pos+=toStr.size();

    } while ( (times==0) || (++total<times) );

  return source;
}

int main()
{

  string original = "Donde dije digo digo diego, pero diego digo donde dije digo";
  map<string,string> mapa;
  mapa["digo"] = "diego";
  mapa["diego"] = "digo";

  cout << "Original string: "<<original<<endl;

  cout << "Resulting string: "<<replace2(original, mapa)<<endl;

  return 0;
}

El resultado esperado es:

Original string: Donde dije digo digo diego, pero diego digo donde dije digo
Resulting string: Donde dije diego diego digo, pero digo diego donde dije diego

Es un trabalenguas, pero creo que la esencia está clara.

Ahora, lo interesante es la forma de generar el mapa, porque muchas veces, seguro que tenemos claro las cadenas que van a formar parte del mismo, por lo tanto, vamos a crear una función con la que podamos insertar cadenas de forma más fácil, de paso, rescatamos un antiguo post:

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
map <string, string> strMap(const char* first, const char* second, ...)
{
  va_list args;
  map<string, string> ret;;
  int n=0;
  char *value;
  string _first, _second;

  ret.insert(pair<string, string>(first, second));
  va_start(args, second);

  do
    {
      value = va_arg(args, char*);
      if (value==NULL)
        break;

      if (++n % 2 ==0)
    {
      _second = string(value);
      ret.insert(pair<string, string>(_first, _second));
    }
      else
    _first = string(value);

    } while (1);

  return ret;
}

De esta manera podemos crear el mapa haciendo:

1
map <string, string> mapa = strMap("digo", "diego", "diego", "digo", NULL);

Es muy importante el último NULL, ya que nos indicará el final de los argumentos.

Espero que os haya sido de utilidad.

También podría interesarte...

Leave a Reply