2011-05-27 16 views
5

En base a esta pregunta, que estaba cerrado con bastante rapidez:
Trying to create a program to read a users input then break the array into seperate words are my pointers all valid?Cómo tokenize (palabras) la clasificación de puntuacion como el espacio

En lugar de cerrar Creo que un trabajo extra podría haber ido a ayudar a la OP para aclarar la cuestión.

La Pregunta:

Quiero tokenize entrada del usuario y almacenar las fichas en una serie de palabras.
Quiero usar la puntuación (., -) como delimitador y así eliminarla de la secuencia de token.

En C Yo usaría strtok() para dividir una matriz en tokens y luego crear manualmente una matriz.
De esta manera:

La función principal:

char **findwords(char *str); 

int main() 
{ 
    int  test; 
    char words[100]; //an array of chars to hold the string given by the user 
    char **word; //pointer to a list of words 
    int  index = 0; //index of the current word we are printing 
    char c; 

    cout << "die monster !"; 
    //a loop to place the charecters that the user put in into the array 

    do 
    { 
     c = getchar(); 
     words[index] = c; 
    } 
    while (words[index] != '\n'); 

    word = findwords(words); 

    while (word[index] != 0) //loop through the list of words until the end of the list 
    { 
     printf("%s\n", word[index]); // while the words are going through the list print them out 
     index ++; //move on to the next word 
    } 

    //free it from the list since it was dynamically allocated 
    free(word); 
    cin >> test; 

    return 0; 
} 

La línea tokenizer:

char **findwords(char *str) 
{ 
    int  size = 20; //original size of the list 
    char *newword; //pointer to the new word from strok 
    int  index = 0; //our current location in words 
    char **words = (char **)malloc(sizeof(char *) * (size +1)); //this is the actual list of words 

    /* Get the initial word, and pass in the original string we want strtok() * 
    * to work on. Here, we are seperating words based on spaces, commas, * 
    * periods, and dashes. IE, if they are found, a new word is created. */ 

    newword = strtok(str, " ,.-"); 

    while (newword != 0) //create a loop that goes through the string until it gets to the end 
    { 
     if (index == size) 
     { 
      //if the string is larger than the array increase the maximum size of the array 
      size += 10; 
      //resize the array 
      char **words = (char **)malloc(sizeof(char *) * (size +1)); 
     } 
     //asign words to its proper value 
     words[index] = newword; 
     //get the next word in the string 
     newword = strtok(0, " ,.-"); 
     //increment the index to get to the next word 
     ++index; 
    } 
    words[index] = 0; 

    return words; 
} 

Cualquier comentario sobre el código anterior sería apreciada.
Pero, además, ¿cuál es la mejor técnica para lograr este objetivo en C++?

+0

Aparte de 'cin >> test;' al final, no llamaría a este código de C++. Está claramente usando c técnicas. Hacer esto usando C++ moderno sería ** muy ** diferente. –

+0

Por si acaso decide ir con la versión C de todos modos, tiene una posible pérdida masiva de memoria (cuando cambia el tamaño), y si sucede, no copia los contenidos anteriores (devolviendo punteros a basura). ¿Tal vez tu intención era usar realloc en lugar de malloc? De fuente, solo te aconsejaría ir a la ruta C++, donde ya has recibido algunos consejos, por lo que no tendrás que lidiar con este tipo de problemas C de la vieja escuela;) – Shaggi

+0

@Shaggi: estoy seguro de que es lo que pretendía el autor original de la pregunta original. Pero como dices, la mejor manera es no usar C. –

Respuesta

5

Ya está cubierto por muchas preguntas la forma de tokenizar una secuencia en C++.
Ejemplo: How to read a file and get words in C++

Pero lo que es más difícil de encontrar es cómo obtener la misma funcionalidad que strtok():

Básicamente strtok() le permite dividir la cadena en un montón de caracteres definidos por el usuario, mientras la transmisión C++ solo le permite usar white space como separador. Afortunadamente, la definición de white space está definida por la configuración regional, por lo que podemos modificar la configuración regional para tratar otros caracteres como espacio y esto nos permitirá convertir la secuencia en una forma más natural.

#include <locale> 
#include <string> 
#include <sstream> 
#include <iostream> 

// This is my facet that will treat the ,.- as space characters and thus ignore them. 
class WordSplitterFacet: public std::ctype<char> 
{ 
    public: 
     typedef std::ctype<char> base; 
     typedef base::char_type  char_type; 

     WordSplitterFacet(std::locale const& l) 
      : base(table) 
     { 
      std::ctype<char> const& defaultCType = std::use_facet<std::ctype<char> >(l); 

      // Copy the default value from the provided locale 
      static char data[256]; 
      for(int loop = 0;loop < 256;++loop) { data[loop] = loop;} 
      defaultCType.is(data, data+256, table); 

      // Modifications to default to include extra space types. 
      table[','] |= base::space; 
      table['.'] |= base::space; 
      table['-'] |= base::space; 
     } 
    private: 
     base::mask table[256]; 
}; 

podemos usar esta faceta en un local de la siguiente manera:

std::ctype<char>* wordSplitter(new WordSplitterFacet(std::locale())); 

    <stream>.imbue(std::locale(std::locale(), wordSplitter)); 

La siguiente parte de su pregunta es ¿cómo iba a almacenar estas palabras en una matriz. Bueno, en C++ no lo harías. Usted delegaría esta funcionalidad en std :: vector/std :: string. Al leer su código, verá que su código está haciendo dos cosas principales en la misma parte del código.

  • Es la gestión de la memoria.
  • Está tokenizando los datos.

Hay un principio básico Separation of Concerns donde su código solo debe intentar y hacer una de dos cosas. Debe hacer gestión de recursos (gestión de memoria en este caso) o debe hacer lógica de negocios (tokenización de los datos). Al separarlos en diferentes partes del código, hace que el código sea más fácil de usar y más fácil de escribir. Afortunadamente, en este ejemplo, toda la administración de recursos ya está hecha por std :: vector/std :: string, lo que nos permite concentrarnos en la lógica de negocios.

Como se ha demostrado muchas veces, la forma más fácil de tokenizar una secuencia es utilizando operator >> y una cadena. Esto dividirá la corriente en palabras. A continuación, puede usar iteradores para recorrer automáticamente la secuencia que tokeniza la transmisión.

std::vector<std::string> data; 
for(std::istream_iterator<std::string> loop(<stream>); loop != std::istream_iterator<std::string>(); ++loop) 
{ 
    // In here loop is an iterator that has tokenized the stream using the 
    // operator >> (which for std::string reads one space separated word. 

    data.push_back(*loop); 
} 

Si combinamos esto con algunos algoritmos estándar para simplificar el código.

std::copy(std::istream_iterator<std::string>(<stream>), std::istream_iterator<std::string>(), std::back_inserter(data)); 

Ahora la combinación de todo lo anterior en una sola aplicación

int main() 
{ 
    // Create the facet. 
    std::ctype<char>* wordSplitter(new WordSplitterFacet(std::locale())); 

    // Here I am using a string stream. 
    // But any stream can be used. Note you must imbue a stream before it is used. 
    // Otherwise the imbue() will silently fail. 
    std::stringstream teststr; 
    teststr.imbue(std::locale(std::locale(), wordSplitter)); 

    // Now that it is imbued we can use it. 
    // If this was a file stream then you could open it here. 
    teststr << "This, stri,plop"; 

    cout << "die monster !"; 
    std::vector<std::string> data; 
    std::copy(std::istream_iterator<std::string>(teststr), std::istream_iterator<std::string>(), std::back_inserter(data)); 

    // Copy the array to cout one word per line 
    std::copy(data.begin(), data.end(), std::ostream_iterator<std::string>(std::cout, "\n")); 
} 
+0

+1. esto es muy similar al mío: http://stackoverflow.com/questions/5607589/right-way-to-split-an-stdstring-into-a-vectorstring – Nawaz

6

Tenga una mirada en boost tokenizer algo que es mucho mejor en un contexto de C++ strtok().

+0

+1 porque hubiera respondido lo mismo! – juanchopanza

+0

+1: Personalmente, creo que la parte de la configuración regional de las transmisiones es la parte más infrautilizada del estándar, más personas deberían aprenderla. Pero, por otro lado, deberían aprenderlo para que podamos hacer mejores abstracciones, como el tokenizador de refuerzo (no es que esté diciendo que así es como funciona el tokenizador de refuerzo). –

Cuestiones relacionadas