Maneras elegantes de contar la frecuencia de palabras en un archivo

¿Cuáles son las maneras elegantes y efectivas de contar la frecuencia de cada palabra “inglesa” en un archivo?

En primer lugar, defino letter_only std::locale para ignorar los signos que provienen de la secuencia, y para leer solo letras ” letter_only ” válidas de la secuencia de entrada. De esta forma, la transmisión tratará las palabras "ways" , "ways." y "ways!" como la misma palabra "ways" , porque la transmisión ignorará las puntuaciones como "." y "!" .

 struct letter_only: std::ctype { letter_only(): std::ctype(get_table()) {} static std::ctype_base::mask const* get_table() { static std::vector rc(std::ctype::table_size,std::ctype_base::space); std::fill(&rc['A'], &rc['z'+1], std::ctype_base::alpha); return &rc[0]; } }; 

Solución 1

 int main() { std::map wordCount; ifstream input; input.imbue(std::locale(std::locale(), new letter_only())); //enable reading only letters! input.open("filename.txt"); std::string word; while(input >> word) { ++wordCount[word]; } for (std::map::iterator it = wordCount.begin(); it != wordCount.end(); ++it) { cout << it->first <<" : "<< it->second << endl; } } 

Solución 2

 struct Counter { std::map wordCount; void operator()(const std::string & item) { ++wordCount[item]; } operator std::map() { return wordCount; } }; int main() { ifstream input; input.imbue(std::locale(std::locale(), new letter_only())); //enable reading only letters! input.open("filename.txt"); istream_iterator start(input); istream_iterator end; std::map wordCount = std::for_each(start, end, Counter()); for (std::map::iterator it = wordCount.begin(); it != wordCount.end(); ++it) { cout << it->first <<" : "<< it->second << endl; } } 

Aquí está la solución de trabajo. Esto debería funcionar con texto real (incluida la puntuación):

 #include  #include  #include  #include  #include  #include  std::string getNextToken(std::istream &in) { char c; std::string ans=""; c=in.get(); while(!std::isalpha(c) && !in.eof())//cleaning non letter charachters { c=in.get(); } while(std::isalpha(c)) { ans.push_back(std::tolower(c)); c=in.get(); } return ans; } int main() { std::map words; std::ifstream fin("input.txt"); std::string s; std::string empty =""; while((s=getNextToken(fin))!=empty ) ++words[s]; for(std::map::iterator iter = words.begin(); iter!=words.end(); ++iter) std::cout<first<<' '<second< 

Editar: Ahora mi código llama tolower para cada letra.

Mi solución es la siguiente. En primer lugar, todos los símbolos se convierten en espacios. Entonces, básicamente, la misma solución proporcionada aquí antes se usa para extraer palabras:

 const std::string Symbols = ",;.:-()\t!¡¿?\"[]{}&<>+-*/=#'"; typedef std::map WCCollection; void countWords(const std::string fileName, WCCollection &wcc) { std::ifstream input( fileName.c_str() ); if ( input.is_open() ) { std::string line; std::string word; while( std::getline( input, line ) ) { // Substitute punctuation symbols with spaces for(std::string::const_iterator it = line.begin(); it != line.end(); ++it) { if ( Symbols.find( *it ) != std::string::npos ) { *it = ' '; } } // Let std::operator>> separate by spaces std::istringstream filter( line ); while( filter >> word ) { ++( wcc[word] ); } } } } 

Pseudocódigo para un algoritmo que creo que está cerca de lo que desea:

 counts = defaultdict(int) for line in file: for word in line.split(): if any(x.isalpha() for x in word): counts[word.toupper()] += 1 freq = sorted(((count, word) for word, count in counts.items()), reversed=True) for count, word in freq: print "%d\t%s" % (count, word) 

La comparación insensible a mayúsculas y minúsculas se maneja ingenuamente y probablemente combina palabras que no desea combinar en un sentido absolutamente general. Tenga cuidado con los caracteres que no sean ASCII en su implementación de lo anterior. Los falsos positivos pueden incluir “1-800-555-TELL”, “0xDEADBEEF” y “42 km”, según lo que desee. Las palabras perdidas incluyen “servicios de emergencia 911” (probablemente querría que contaran como tres palabras).

En resumen, el análisis sintáctico del lenguaje natural es difícil: es probable que puedas cumplir con cierta aproximación dependiendo de tu caso de uso real.

Es posible que Perl no sea tan elegante, pero sí muy efectivo.
Publiqué una solución aquí: Procesando archivos de texto enormes

En una palabra,

1) Si es necesario, quite la puntuación y convierta mayúsculas a minúsculas:
perl -pe "s/[^a-zA-Z \t\n']/ /g; tr/AZ/az/" file_raw > file

2) Cuente la ocurrencia de cada palabra. Imprima los resultados ordenados primero por frecuencia y luego alfabéticamente:
perl -lane '$h{$_}++ for @F; END{for $w (sort {$h{$b}<=>$h{$a} || $a cmp $b} keys %h) {print "$h{$w}\t$w"}}' file > freq

Ejecuté este código en un archivo de texto de 3,3 GB con 580,000,000 de palabras.
Perl 5.22 completado en menos de 3 minutos.

Una manera más simple es contar el número de espacios en el archivo hasta que se encuentre más de un espacio, si considera solo un espacio entre las palabras …

  1. Decida exactamente qué quiere decir con “una palabra en inglés”. La definición debería abarcar aspectos como si “apto para el cuerpo” es una palabra o dos, cómo manejar los apóstrofes (“No confíes en ellos”), si las mayúsculas son importantes, etc.

  2. Cree un conjunto de casos de prueba para asegurarse de que todas las decisiones del paso 1 sean correctas.

  3. Cree un tokenizador que lea la siguiente palabra (como se define en el paso 1) de la entrada y la devuelva en una forma estándar. Dependiendo de cómo defina su definición, esta podría ser una máquina de estado simple, una expresión regular o simplemente confiar en los operadores de extracción de (por ejemplo, std::cin >> word; ). Pon a prueba tu tokenizer con todos los casos de prueba del paso 2.

  4. Elija una estructura de datos para mantener las palabras y los recuentos. En el C ++ moderno, probablemente terminarías con algo como std::map o std::unordered_map .

  5. Escriba un ciclo que obtenga la siguiente palabra del tokenizador e incremente su conteo en el histogtwig hasta que no haya más palabras en la entrada.

 string mostCommon( string filename ) { ifstream input( filename ); string line; string mostFreqUsedWord; string token; map< string, int > wordFreq; if ( input.is_open() ) { while ( true ) { input >> token; if( input ) { wordFreq[ token ]++; if ( wordFreq[ token] > wordFreq[ mostFreqUsedWord ] ) mostFreqUsedWord = token; } else break; } input.close(); } else { cout << "Unable to ope file." << endl; } return mostFreqUsedWord; }