4.3. Buscar en cadenas

La familia de funciones miembro de string find localiza un carácter o grupo de caracteres en una cadena dada. Aquí los miembros de la familia find() y su uso general:

Función miembro de búsqueda en un string

¿Qué/Cómo lo encuentra?

find()

Busca en un string un carácter determinado o un grupo de caracteres y retorna la posición de inicio de la primera ocurrencia o npos si ha sido encontrado.

find_first_of()

Busca en un string y retorna la posición de la primera ocurrencia de cualquier carácter en un grupo especifico. Si no encuentra ocurrencias, retorna npos.

find_last_of()

Busca en un string y retorna la posición de la última ocurrencia de cualquier carácter en un grupo específico. Si no encuentra ocurrencias, retorna npos.

find_first_not_of( )

Busca en un string y retorna la posición de la primera ocurrencia que no pertenece a un grupo específico. Si no encontramos ningún elemento, retorna un npos

find_last_not_of( )

Busca en un string y retorna la posición del elemento con el indice mayor que no pertenece a un grupo específico. Si no encontramos ningún elemento, retorna un npos

rfind()

Busca en un string, desde el final hasta el origen, un carácter o grupo de caracteres y retorna la posición inicial de la ocurrencia si se ha encontrado alguna. Si no encuentra ocurrencias, retorna npos.

El uso más simple de find(), busca uno o más caracteres en un string. La versión sobrecargada de find() toma un parámetro que especifica el/los carácter(es) que buscar y opcionalmente un parámetro que dice donde empezar a buscar en el string la primera ocurrencia. (Por defecto la posición de incio es 0). Insertando la llamada a la función find() dentro de un bucle puede buscar fácilmente todas las ocurrencias de un carácter dado o un grupo de caracteres dentro de un string.

El siguiente programa usa el método del Tamiz de Eratostenes para hallar los números primos menores de 50. Este método empieza con el número 2, marca todos los subsecuentes múltiplos de 2 ya que no son primos, y repite el proceso para el siguiente candidato a primo. El constructor de sieveTest inicializa sieveChars poniendo el tamaño inicial del arreglo de carácter y escribiendo el valor 'P' para cada miembro.

//: C03:Sieve.h
#ifndef SIEVE_H
#define SIEVE_H
#include <cmath>
#include <cstddef>
#include <string>
#include "../TestSuite/Test.h"
using std::size_t;
using std::sqrt;
using std::string;

class SieveTest : public TestSuite::Test {
  string sieveChars;
public:
  // Create a 50 char string and set each
  // element to 'P' for Prime:
  SieveTest() : sieveChars(50, 'P') {}
  void run() {
    findPrimes();
    testPrimes();
  }
  bool isPrime(int p) {
    if(p == 0 || p == 1) return false;
    int root = int(sqrt(double(p)));
    for(int i = 2; i <= root; ++i)
      if(p % i == 0) return false;
    return true;
  }
  void findPrimes() {
    // By definition neither 0 nor 1 is prime.
    // Change these elements to "N" for Not Prime:
    sieveChars.replace(0, 2, "NN");
    // Walk through the array:
    size_t sieveSize = sieveChars.size();
    int root = int(sqrt(double(sieveSize)));
    for(int i = 2; i <= root; ++i)
      // Find all the multiples:
      for(size_t factor = 2; factor * i < sieveSize;
           ++factor)
        sieveChars[factor * i] = 'N';
  }
  void testPrimes() {
    size_t i = sieveChars.find('P');
    while(i != string::npos) {
      test_(isPrime(i++));
      i = sieveChars.find('P', i);
    }
    i = sieveChars.find_first_not_of('P');
    while(i != string::npos) {
      test_(!isPrime(i++));
      i = sieveChars.find_first_not_of('P', i);
    }
  }
};
#endif // SIEVE_H ///:~

Listado 4.11. C03/Sieve.h


//: C03:Sieve.cpp
//{L} ../TestSuite/Test
#include "Sieve.h"

int main() {
  SieveTest t;
  t.run();
  return t.report();
} ///:~

Listado 4.12. C03/Sieve.cpp


La función find() puede recorrer el string, detectando múltiples ocurrencias de un carácter o un grupo de caracteres, y find_first_not_of() encuentra otros caracteres o subcadenas.

No existen funciones en la clase string para cambiar entre mayúsculas/minúsculas en una cadena, pero puede crear esa función fácilmente usando la función de la libreria estándar de C toupper() y tolower(), que cambian los caracteres entre mayúsculas/minúsculas de uno en uno. El ejemplo siguiente ilustra una búsqueda sensible a mayúsculas/minúsculas.

//: C03:Find.h
#ifndef FIND_H
#define FIND_H
#include <cctype>
#include <cstddef>
#include <string>
#include "../TestSuite/Test.h"
using std::size_t;
using std::string;
using std::tolower;
using std::toupper;

// Make an uppercase copy of s
inline string upperCase(const string& s) {
  string upper(s);
  for(size_t i = 0; i < s.length(); ++i)
    upper[i] = toupper(upper[i]);
  return upper;
}

// Make a lowercase copy of s
inline string lowerCase(const string& s) {
  string lower(s);
  for(size_t i = 0; i < s.length(); ++i)
    lower[i] = tolower(lower[i]);
  return lower;
}

class FindTest : public TestSuite::Test {
  string chooseOne;
public:
  FindTest() : chooseOne("Eenie, Meenie, Miney, Mo") {}
  void testUpper() {
    string upper = upperCase(chooseOne);
    const string LOWER = "abcdefghijklmnopqrstuvwxyz";
    test_(upper.find_first_of(LOWER) == string::npos);
  }
  void testLower() {
    string lower = lowerCase(chooseOne);
    const string UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    test_(lower.find_first_of(UPPER) == string::npos);
  }
  void testSearch() {
    // Case sensitive search
    size_t i = chooseOne.find("een");
    test_(i == 8);
    // Search lowercase:
    string test = lowerCase(chooseOne);
    i = test.find("een");
    test_(i == 0);
    i = test.find("een", ++i);
    test_(i == 8);
    i = test.find("een", ++i);
    test_(i == string::npos);
    // Search uppercase:
    test = upperCase(chooseOne);
    i = test.find("EEN");
    test_(i == 0);
    i = test.find("EEN", ++i);
    test_(i == 8);
    i = test.find("EEN", ++i);
    test_(i == string::npos);
  }
  void run() {
    testUpper();
    testLower();
    testSearch();
  }
};
#endif // FIND_H ///:~

Listado 4.13. C03/Find.h


//: C03:Find.cpp
//{L} ../TestSuite/Test
#include "Find.h"
#include "../TestSuite/Test.h"

int main() {
  FindTest t;
  t.run();
  return t.report();
} ///:~

Listado 4.14. C03/Find.cpp


Tanto las funciones upperCase() como lowerCase() siguen la misma forma: hacen una copia de la cadena argumento y cambian entre mayúsculas/minúsculas. El programa Find.cpp no es la mejor solución para el problema para las mayúsculas/minúsculas, por lo que lo revisitaremos cuando examinemos la comparación entre cadenas.

4.3.1. Busqueda inversa

Si necesita buscar en una cadena desde el final hasta el principio (para encontrar datos en orden "último entra / primero sale"), puede usar la función miembro de string rfind().

//: C03:Rparse.h
#ifndef RPARSE_H
#define RPARSE_H
#include <cstddef>
#include <string>
#include <vector>
#include "../TestSuite/Test.h"
using std::size_t;
using std::string;
using std::vector;

class RparseTest : public TestSuite::Test {
  // To store the words:
  vector<string> strings;
public:
  void parseForData() {
    // The ';' characters will be delimiters
    string s("now.;sense;make;to;going;is;This");
    // The last element of the string:
    int last = s.size();
    // The beginning of the current word:
    size_t current = s.rfind(';');
    // Walk backward through the string:
    while(current != string::npos) {
      // Push each word into the vector.
      // Current is incremented before copying
      // to avoid copying the delimiter:
      ++current;
      strings.push_back(s.substr(current, last - current));
      // Back over the delimiter we just found,
      // and set last to the end of the next word:
      current -= 2;
      last = current + 1;
      // Find the next delimiter:
      current = s.rfind(';', current);
    }
    // Pick up the first word -- it's not
    // preceded by a delimiter:
    strings.push_back(s.substr(0, last));
  }
  void testData() {
    // Test them in the new order:
    test_(strings[0] == "This");
    test_(strings[1] == "is");
    test_(strings[2] == "going");
    test_(strings[3] == "to");
    test_(strings[4] == "make");
    test_(strings[5] == "sense");
    test_(strings[6] == "now.");
    string sentence;
    for(size_t i = 0; i < strings.size() - 1; i++)
      sentence += strings[i] += " ";
    // Manually put last word in to avoid an extra space:
    sentence += strings[strings.size() - 1];
    test_(sentence == "This is going to make sense now.");
  }
  void run() {
    parseForData();
    testData();
  }
};
#endif // RPARSE_H ///:~

Listado 4.15. C03/Rparse.h


//: C03:Rparse.cpp
//{L} ../TestSuite/Test
#include "Rparse.h"

int main() {
  RparseTest t;
  t.run();
  return t.report();
} ///:~

Listado 4.16. C03/Rparse.cpp


La función miembro de string rfind() vuelve por la cadena buscando elementos y reporta el indice del arreglo de las coincidencias de caracteres o string::npos si no tiene éxito.