Projeto Analisador de Texto
Quantas palavras tem esse texto? Qual palavra eu mais repito? Neste projeto você vai escrever um programa que lê um arquivo de texto e responde a essas perguntas, em sete etapas. No caminho aparece uma surpresa que todo programador de língua portuguesa encontra um dia: para o computador, café tem 5 "letras". Você vai entender por quê, contar do jeito certo e conferir cada número com o wc, o contador de palavras do Linux.
- Ler um arquivo linha por linha (arquivos e getline) e tratar o arquivo que não existe.
- A diferença entre bytes e caracteres no UTF-8 (Acentos e UTF-8).
- Separar palavras com
std::istringstreame limpar a pontuação com<cctype>. - Contar cada palavra com std::map e ordenar pela contagem com std::sort e uma lambda.
- Um relatório com std::format e um programa que recebe o nome do arquivo na linha de comando.
Todos os programas foram compilados e executados na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/cpp/texto, com o g++ 15.2 e o comando padrão do guia. O programa fica num arquivo só, analisar.cpp. Nas caixas Experimente, o texto está numa aba ao lado do código: você pode trocá-lo pelo seu.
O que vamos construir
$ g++ -std=c++23 -Wall -Wextra analisar.cpp -o analisar $ ./analisar Análise de texto.txt Linhas 7 Palavras 65 Palavras diferentes 45 Caracteres 332 Bytes 342 Palavra mais longa: paciência (9 letras) Mais frequentes: 1. a 6 ###### 2. um 4 #### 3. ana 3 ### 4. caderno 3 ### 5. café 3 ###
- Abrir o arquivo e contar as linhas.
- Bytes × caracteres: o UTF-8.
- Contar as palavras.
- Contar cada palavra, sem pontuação e sem maiúsculas.
- As mais frequentes: ordenar pela contagem.
- A palavra mais longa.
- O relatório e o nome do arquivo na linha de comando.
Etapa 1 · Abrir o arquivo e contar as linhas
Primeiro, o texto. A Ana escreveu sobre um dia na lojinha, com acentos, vírgulas, pontos e até uma exclamação, tudo o que vai dar trabalho. Ela criou o arquivo com um here-document do Bash (no seu computador, use qualquer editor de texto e salve em UTF-8):
$ mkdir -p cpp/texto $ cd cpp/texto $ cat > texto.txt <<'FIM' > A lojinha da Ana abre cedo, antes do sol. > O café está quente, e a Ana conta os cadernos. > Um caderno azul, um caderno verde, um caderno sem cor. > Às dez horas chega a primeira cliente: a avó do Bruno. > Ela quer lápis, borracha e paciência. > A Ana não vende paciência, mas oferece um café. > No fim do dia, a lojinha fecha. O café acabou! > FIM
O programa abre o arquivo, lê uma linha por vez e numera:
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
int linhas = 0;
std::string linha;
while (std::getline(arq, linha)) {
++linhas;
std::cout << linhas << ": " << linha << '\n';
}
std::cout << "Linhas: " << linhas << '\n';
}
| Trecho | O que faz |
|---|---|
std::ifstream arq{"texto.txt"}; | Abre o arquivo para leitura (input file stream, "fluxo de entrada de arquivo"). O nome é procurado na pasta em que o programa roda. |
if (!arq) | "Se o arquivo não abriu." Um ifstream vale false quando algo deu errado (arquivo que não existe, sem permissão…). |
std::cerr | A saída de erros (character error). Aparece no terminal como o cout, mas pode ser separada dele (por exemplo, 2> erros.txt). |
return 1; | Termina o main com código de saída 1: "deu errado". Scripts e o Bash olham esse número. |
while (std::getline(arq, linha)) | Lê a próxima linha (sem o \n do fim) para linha. Quando o arquivo acaba, o getline "falha" e o laço termina. |
$ cat texto.txt A lojinha da Ana abre cedo, antes do sol. O café está quente, e a Ana conta os cadernos. Um caderno azul, um caderno verde, um caderno sem cor. Às dez horas chega a primeira cliente: a avó do Bruno. Ela quer lápis, borracha e paciência. A Ana não vende paciência, mas oferece um café. No fim do dia, a lojinha fecha. O café acabou! $ g++ -std=c++23 -Wall -Wextra analisar.cpp -o analisar $ ./analisar 1: A lojinha da Ana abre cedo, antes do sol. 2: O café está quente, e a Ana conta os cadernos. 3: Um caderno azul, um caderno verde, um caderno sem cor. 4: Às dez horas chega a primeira cliente: a avó do Bruno. 5: Ela quer lápis, borracha e paciência. 6: A Ana não vende paciência, mas oferece um café. 7: No fim do dia, a lojinha fecha. O café acabou! Linhas: 7
Sem o if (!arq) e com o nome digitado errado ("testo.txt"), o programa não reclama de nada:
$ g++ -std=c++23 -Wall -Wextra analisar.cpp -o analisar $ ./analisar Linhas: 0 $ echo $? 0
"0 linhas" e código de saída 0, "sucesso". Um ifstream que não abriu não lança exceção: ele só fica em estado de falha, e o primeiro getline já devolve "acabou". O programa mente sem saber. Com a verificação (e o mesmo nome errado):
$ g++ -std=c++23 -Wall -Wextra analisar.cpp -o analisar $ ./analisar Não consegui abrir testo.txt $ echo $? 1
Regra: depois de abrir um arquivo, confira sempre se abriu.
Missão (etapa 1): na aba texto.txt desta caixa há uma linha em branco entre a 3 e a 4. Conte também as linhas em branco (linha.empty() é true para uma linha vazia) e mostre no fim:
Linhas: 8 Linhas em branco: 1
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
int linhas = 0;
std::string linha;
while (std::getline(arq, linha)) {
++linhas;
std::cout << linhas << ": " << linha << '\n';
}
std::cout << "Linhas: " << linhas << '\n';
}
A lojinha da Ana abre cedo, antes do sol. O café está quente, e a Ana conta os cadernos. Um caderno azul, um caderno verde, um caderno sem cor. Às dez horas chega a primeira cliente: a avó do Bruno. Ela quer lápis, borracha e paciência. A Ana não vende paciência, mas oferece um café. No fim do dia, a lojinha fecha. O café acabou!
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
int linhas = 0;
int emBranco = 0;
std::string linha;
while (std::getline(arq, linha)) {
++linhas;
if (linha.empty()) {
++emBranco;
}
std::cout << linhas << ": " << linha << '\n';
}
std::cout << "Linhas: " << linhas << '\n';
std::cout << "Linhas em branco: " << emBranco << '\n';
}
Etapa 2 · Bytes × caracteres
Quantas letras tem a palavra café? Quatro. Pergunte ao C++ com std::string("café").size() e ele responde 5. Veja os bytes do arquivo com o od (octal dump, aqui em hexadecimal) e conte com o wc, que tem -c para bytes e -m para caracteres:
$ printf 'café' | od -An -tx1 63 61 66 c3 a9 $ printf 'café' | wc -c 5 $ printf 'café' | wc -m 4
c, a e f ocupam um byte cada (63 61 66). O é ocupa dois: c3 a9. É a codificação UTF-8, a do Linux, da web e deste guia. Ela guarda cada caractere em 1 a 4 bytes, e o primeiro byte diz quantos vêm:
| Bits do byte | Teste em C++ | Significa | Exemplo |
|---|---|---|---|
0xxxxxxx | c < 0x80 | Caractere de 1 byte (os do inglês, números, pontuação). | a = 61 |
110xxxxx | (c & 0xE0) == 0xC0 | Começo de um caractere de 2 bytes. | é = c3 a9 |
1110xxxx | (c & 0xF0) == 0xE0 | Começo de um caractere de 3 bytes. | € = e2 82 ac |
11110xxx | (c & 0xF8) == 0xF0 | Começo de um caractere de 4 bytes (emojis). | 😀 = f0 9f 98 80 |
10xxxxxx | (c & 0xC0) == 0x80 | Continuação: o 2º, 3º ou 4º byte de um caractere. | o a9 do é |
Daí a receita: para contar caracteres, conte os bytes que não são de continuação. O & (E bit a bit) com 0xC0 (11000000) guarda só os dois primeiros bits do byte; se eles forem 10 (0x80), é continuação.
#include <fstream>
#include <iostream>
#include <string>
// Quantos caracteres (não bytes) um texto em UTF-8 tem.
std::size_t contarCaracteres(const std::string& texto) {
std::size_t n = 0;
for (unsigned char c : texto) {
if ((c & 0xC0) != 0x80) { // não é byte de continuação
++n;
}
}
return n;
}
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
int linhas = 0;
std::size_t bytes = 0, caracteres = 0;
std::string linha;
while (std::getline(arq, linha)) {
++linhas;
std::cout << linhas << ": " << linha.size() << " bytes, "
<< contarCaracteres(linha) << " caracteres\n";
bytes += linha.size() + 1; // +1: o '\n' que o getline tirou
caracteres += contarCaracteres(linha) + 1;
}
std::cout << "Linhas: " << linhas << '\n';
std::cout << "Caracteres: " << caracteres << '\n';
std::cout << "Bytes: " << bytes << '\n';
}
| Trecho | O que faz |
|---|---|
for (unsigned char c : texto) | Percorre o texto byte a byte. unsigned char vê o byte como um número de 0 a 255; com char (que no Linux x86-64 tem sinal), o c3 viraria −61. Veja char. |
(c & 0xC0) != 0x80 | "Não é byte de continuação": começa um caractere novo. |
std::size_t | O tipo sem sinal que o C++ usa para tamanhos (o mesmo de size()). |
linha.size() + 1 | O getline tira o \n do fim de cada linha, mas ele ocupa um byte (e é um caractere) no arquivo. Somamos de volta. |
$ g++ -std=c++23 -Wall -Wextra analisar.cpp -o analisar $ ./analisar 1: 41 bytes, 41 caracteres 2: 48 bytes, 46 caracteres 3: 54 bytes, 54 caracteres 4: 56 bytes, 54 caracteres 5: 39 bytes, 37 caracteres 6: 50 bytes, 47 caracteres 7: 47 bytes, 46 caracteres Linhas: 7 Caracteres: 332 Bytes: 342 $ wc -c texto.txt 342 texto.txt $ wc -m texto.txt 332 texto.txt
Os números batem com o wc: 342 bytes e 332 caracteres. A linha 1 não tem acento (41 = 41); a linha 2 tem dois, café e está (48 bytes, 46 caracteres). A diferença total, 10, é o número de letras acentuadas do texto.
Esta contagem é a de pontos de código, a mesma do wc -m. Na maioria dos textos em português ela coincide com as letras que você vê. Mas há casos em que não: um é pode ser gravado como e + um acento "solto" (dois pontos de código) e alguns emojis são vários pontos de código juntos. Para esses casos existem bibliotecas especializadas, como a ICU. Mais em Acentos e UTF-8.
Missão (etapa 2): a função contarCaracteres está "trapaceando": devolve o número de bytes, e o total de caracteres sai 342. Escreva-a de verdade, contando só os bytes que não são de continuação. A saída deve terminar com:
Caracteres: 332 Bytes: 342
#include <fstream>
#include <iostream>
#include <string>
// Quantos caracteres (não bytes) um texto em UTF-8 tem.
std::size_t contarCaracteres(const std::string& texto) {
// Missão: conte só os bytes que NÃO são de continuação.
return texto.size();
}
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
int linhas = 0;
std::size_t bytes = 0, caracteres = 0;
std::string linha;
while (std::getline(arq, linha)) {
++linhas;
std::cout << linhas << ": " << linha.size() << " bytes, "
<< contarCaracteres(linha) << " caracteres\n";
bytes += linha.size() + 1; // +1: o '\n' que o getline tirou
caracteres += contarCaracteres(linha) + 1;
}
std::cout << "Linhas: " << linhas << '\n';
std::cout << "Caracteres: " << caracteres << '\n';
std::cout << "Bytes: " << bytes << '\n';
}
A lojinha da Ana abre cedo, antes do sol. O café está quente, e a Ana conta os cadernos. Um caderno azul, um caderno verde, um caderno sem cor. Às dez horas chega a primeira cliente: a avó do Bruno. Ela quer lápis, borracha e paciência. A Ana não vende paciência, mas oferece um café. No fim do dia, a lojinha fecha. O café acabou!
#include <fstream>
#include <iostream>
#include <string>
// Quantos caracteres (não bytes) um texto em UTF-8 tem.
std::size_t contarCaracteres(const std::string& texto) {
std::size_t n = 0;
for (unsigned char c : texto) {
if ((c & 0xC0) != 0x80) { // não é byte de continuação
++n;
}
}
return n;
}
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
int linhas = 0;
std::size_t bytes = 0, caracteres = 0;
std::string linha;
while (std::getline(arq, linha)) {
++linhas;
std::cout << linhas << ": " << linha.size() << " bytes, "
<< contarCaracteres(linha) << " caracteres\n";
bytes += linha.size() + 1; // +1: o '\n' que o getline tirou
caracteres += contarCaracteres(linha) + 1;
}
std::cout << "Linhas: " << linhas << '\n';
std::cout << "Caracteres: " << caracteres << '\n';
std::cout << "Bytes: " << bytes << '\n';
}
Etapa 3 · Contar as palavras
O que é uma palavra? Para começar, a mesma definição do wc: um pedaço de texto entre espaços. O C++ já sabe separar assim: o >> de um fluxo pula os espaços e lê até o próximo espaço. Para usar o >> numa std::string, transformamos a linha num fluxo com std::istringstream (input string stream, de <sstream>):
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
// Quantos caracteres (não bytes) um texto em UTF-8 tem.
std::size_t contarCaracteres(const std::string& texto) {
std::size_t n = 0;
for (unsigned char c : texto) {
if ((c & 0xC0) != 0x80) { // não é byte de continuação
++n;
}
}
return n;
}
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
int linhas = 0;
std::size_t bytes = 0, caracteres = 0, palavras = 0;
std::string linha;
while (std::getline(arq, linha)) {
++linhas;
bytes += linha.size() + 1;
caracteres += contarCaracteres(linha) + 1;
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
++palavras;
}
}
std::cout << "Linhas: " << linhas << '\n';
std::cout << "Palavras: " << palavras << '\n';
std::cout << "Caracteres: " << caracteres << '\n';
std::cout << "Bytes: " << bytes << '\n';
}
| Trecho | O que faz |
|---|---|
std::istringstream fluxo{linha}; | Um "cin de mentira" que lê de dentro da linha. |
while (fluxo >> palavra) | Lê a próxima palavra; quando a linha acaba, o laço termina. Espaços repetidos e tabulações são pulados. |
$ g++ -std=c++23 -Wall -Wextra analisar.cpp -o analisar $ ./analisar Linhas: 7 Palavras: 65 Caracteres: 332 Bytes: 342 $ wc texto.txt 7 65 342 texto.txt
As colunas do wc são linhas, palavras e bytes: 7, 65, 342, iguais às nossas. Mas repare que, para o >>, cedo, (com a vírgula) é uma palavra. Para contar palavras, tanto faz. Para saber quais palavras se repetem, vai fazer diferença.
Missão (etapa 3): conte também as frases, contando os caracteres ., ! e ? de cada linha (um for (char c : linha)). Mostre Frases: 8 logo depois das palavras.
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
// Quantos caracteres (não bytes) um texto em UTF-8 tem.
std::size_t contarCaracteres(const std::string& texto) {
std::size_t n = 0;
for (unsigned char c : texto) {
if ((c & 0xC0) != 0x80) { // não é byte de continuação
++n;
}
}
return n;
}
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
int linhas = 0;
std::size_t bytes = 0, caracteres = 0, palavras = 0;
std::string linha;
while (std::getline(arq, linha)) {
++linhas;
bytes += linha.size() + 1;
caracteres += contarCaracteres(linha) + 1;
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
++palavras;
}
}
std::cout << "Linhas: " << linhas << '\n';
std::cout << "Palavras: " << palavras << '\n';
std::cout << "Caracteres: " << caracteres << '\n';
std::cout << "Bytes: " << bytes << '\n';
}
A lojinha da Ana abre cedo, antes do sol. O café está quente, e a Ana conta os cadernos. Um caderno azul, um caderno verde, um caderno sem cor. Às dez horas chega a primeira cliente: a avó do Bruno. Ela quer lápis, borracha e paciência. A Ana não vende paciência, mas oferece um café. No fim do dia, a lojinha fecha. O café acabou!
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
// Quantos caracteres (não bytes) um texto em UTF-8 tem.
std::size_t contarCaracteres(const std::string& texto) {
std::size_t n = 0;
for (unsigned char c : texto) {
if ((c & 0xC0) != 0x80) { // não é byte de continuação
++n;
}
}
return n;
}
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
int linhas = 0;
std::size_t bytes = 0, caracteres = 0, palavras = 0, frases = 0;
std::string linha;
while (std::getline(arq, linha)) {
++linhas;
bytes += linha.size() + 1;
caracteres += contarCaracteres(linha) + 1;
for (char c : linha) {
if (c == '.' || c == '!' || c == '?') {
++frases;
}
}
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
++palavras;
}
}
std::cout << "Linhas: " << linhas << '\n';
std::cout << "Palavras: " << palavras << '\n';
std::cout << "Frases: " << frases << '\n';
std::cout << "Caracteres: " << caracteres << '\n';
std::cout << "Bytes: " << bytes << '\n';
}
Etapa 4 · Contar cada palavra
Para saber quantas vezes cada palavra aparece, um std::map da palavra para a contagem: std::map<std::string, int>. O truque é ++contagem[palavra]: se a palavra é nova, o [] a cria com 0 e o ++ faz 1; se já existe, soma 1. (Aqui o [] criar a chave é exatamente o que queremos, ao contrário do estoque.) Primeira tentativa, sem limpar nada:
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
std::map<std::string, int> contagem;
std::string linha;
while (std::getline(arq, linha)) {
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
++contagem[palavra];
}
}
std::cout << contagem.size() << " palavras diferentes:\n";
int i = 0;
for (const auto& [palavra, vezes] : contagem) {
std::cout << palavra << '=' << vezes << ' ';
if (++i % 8 == 0) {
std::cout << '\n';
}
}
std::cout << '\n';
}
$ g++ -std=c++23 -Wall -Wextra analisar.cpp -o analisar $ ./analisar 49 palavras diferentes: A=2 Ana=3 Bruno.=1 Ela=1 No=1 O=2 Um=1 a=4 abre=1 acabou!=1 antes=1 avó=1 azul,=1 borracha=1 caderno=3 cadernos.=1 café=2 café.=1 cedo,=1 chega=1 cliente:=1 conta=1 cor.=1 da=1 dez=1 dia,=1 do=3 e=2 está=1 fecha.=1 fim=1 horas=1 lojinha=2 lápis,=1 mas=1 não=1 oferece=1 os=1 paciência,=1 paciência.=1 primeira=1 quente,=1 quer=1 sem=1 sol.=1 um=3 vende=1 verde,=1 Às=1
O map mostra tudo em ordem, e os problemas aparecem: A e a contados separados, café e café. também, assim como paciência, e paciência.. Precisamos limpar cada palavra antes de contar: tirar a pontuação e passar para minúsculas. As funções de <cctype> ajudam:
| Função | Pergunta ou faz | Exemplo |
|---|---|---|
std::isalnum(c) | É letra ou número? (alphanumeric) | 'a' sim, ',' não |
std::isalpha(c) | É letra? | '7' não |
std::ispunct(c) | É pontuação? | '!' sim |
std::tolower(c) | Devolve a minúscula. | 'A' → 'a' |
Essas funções só entendem um byte por vez, e só os caracteres do inglês. Por isso a nossa limpar guarda as letras e números e todo byte de 0x80 para cima (os pedaços das letras acentuadas), sem tentar mexer neles:
#include <cctype>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
// Tira a pontuação e passa para minúsculas (só as letras sem acento).
std::string limpar(const std::string& palavra) {
std::string limpa;
for (unsigned char c : palavra) {
if (std::isalnum(c) || c >= 0x80) {
limpa += static_cast<char>(std::tolower(c));
}
}
return limpa;
}
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
std::map<std::string, int> contagem;
std::string linha;
while (std::getline(arq, linha)) {
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
std::string limpa = limpar(palavra);
if (!limpa.empty()) {
++contagem[limpa];
}
}
}
std::cout << contagem.size() << " palavras diferentes:\n";
int i = 0;
for (const auto& [palavra, vezes] : contagem) {
std::cout << palavra << '=' << vezes << ' ';
if (++i % 8 == 0) {
std::cout << '\n';
}
}
std::cout << '\n';
}
| Trecho | O que faz |
|---|---|
std::isalnum(c) || c >= 0x80 | Fica: letra ou número do inglês, ou byte de caractere acentuado. Sai: vírgula, ponto, dois-pontos, exclamação. |
static_cast<char>(std::tolower(c)) | tolower recebe e devolve int; convertemos de volta para char. Com bytes ≥ 0x80 ele não muda nada. |
if (!limpa.empty()) | Um "!" ou um "-" sozinho vira texto vazio: não conta. (Um travessão "—" não: ele tem 3 bytes ≥ 0x80 e passaria. Fica para os desafios.) |
$ g++ -std=c++23 -Wall -Wextra analisar.cpp -o analisar $ ./analisar 45 palavras diferentes: a=6 abre=1 acabou=1 ana=3 antes=1 avó=1 azul=1 borracha=1 bruno=1 caderno=3 cadernos=1 café=3 cedo=1 chega=1 cliente=1 conta=1 cor=1 da=1 dez=1 dia=1 do=3 e=2 ela=1 está=1 fecha=1 fim=1 horas=1 lojinha=2 lápis=1 mas=1 no=1 não=1 o=2 oferece=1 os=1 paciência=2 primeira=1 quente=1 quer=1 sem=1 sol=1 um=4 vende=1 verde=1 Às=1
De 49 para 45 palavras diferentes: a=6, café=3, paciência=2. Duas coisas ficaram estranhas, e as duas são o UTF-8 de novo:
Àscontinua com maiúscula: otolowernão sabe quec3 80é um "À". Se o texto tivesseÀseàs, seriam contadas separadas (desafio no fim).lápisveio depois delojinha, eÀsno fim de tudo. O map ordena comparando bytes: oácomeça comc3, que é maior que qualquer letra sem acento.
Missão (etapa 4): a limpar está devolvendo a palavra sem mudar nada, e o programa mostra 49 palavras diferentes. Escreva-a: monte um texto novo só com os bytes que são std::isalnum ou >= 0x80, passando cada um por std::tolower. Devem aparecer 45 palavras diferentes e café=3.
#include <cctype>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
// Tira a pontuação e passa para minúsculas (só as letras sem acento).
std::string limpar(const std::string& palavra) {
// Missão: monte um texto novo só com letras, números e bytes >= 0x80,
// passando cada um por std::tolower.
return palavra;
}
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
std::map<std::string, int> contagem;
std::string linha;
while (std::getline(arq, linha)) {
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
std::string limpa = limpar(palavra);
if (!limpa.empty()) {
++contagem[limpa];
}
}
}
std::cout << contagem.size() << " palavras diferentes:\n";
int i = 0;
for (const auto& [palavra, vezes] : contagem) {
std::cout << palavra << '=' << vezes << ' ';
if (++i % 8 == 0) {
std::cout << '\n';
}
}
std::cout << '\n';
}
A lojinha da Ana abre cedo, antes do sol. O café está quente, e a Ana conta os cadernos. Um caderno azul, um caderno verde, um caderno sem cor. Às dez horas chega a primeira cliente: a avó do Bruno. Ela quer lápis, borracha e paciência. A Ana não vende paciência, mas oferece um café. No fim do dia, a lojinha fecha. O café acabou!
#include <cctype>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
// Tira a pontuação e passa para minúsculas (só as letras sem acento).
std::string limpar(const std::string& palavra) {
std::string limpa;
for (unsigned char c : palavra) {
if (std::isalnum(c) || c >= 0x80) {
limpa += static_cast<char>(std::tolower(c));
}
}
return limpa;
}
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
std::map<std::string, int> contagem;
std::string linha;
while (std::getline(arq, linha)) {
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
std::string limpa = limpar(palavra);
if (!limpa.empty()) {
++contagem[limpa];
}
}
}
std::cout << contagem.size() << " palavras diferentes:\n";
int i = 0;
for (const auto& [palavra, vezes] : contagem) {
std::cout << palavra << '=' << vezes << ' ';
if (++i % 8 == 0) {
std::cout << '\n';
}
}
std::cout << '\n';
}
Etapa 5 · As mais frequentes
O map está em ordem de palavra; queremos em ordem de contagem. Como no estoque, copiamos para um vetor e ordenamos. Cada item do map é um par (palavra, contagem), então o vetor é de std::pair<std::string, int> (pair). No lugar da listagem da etapa 4, no fim do main (com #include <algorithm>, <utility> e <vector>):
std::vector<std::pair<std::string, int>> ranking(contagem.begin(), contagem.end());
std::sort(ranking.begin(), ranking.end(), [](const auto& a, const auto& b) {
if (a.second != b.second) {
return a.second > b.second; // mais vezes primeiro
}
return a.first < b.first; // empate: ordem alfabética
});
for (std::size_t i = 0; i < ranking.size() && i < 5; ++i) {
std::cout << i + 1 << ". " << ranking[i].first << ": " << ranking[i].second << '\n';
}
}
| Trecho | O que faz |
|---|---|
ranking(contagem.begin(), contagem.end()) | Cria o vetor já com uma cópia de todos os pares do map, de uma vez. |
[](const auto& a, const auto& b) | Uma lambda com auto nos parâmetros: o compilador descobre que são pares. Evita escrever std::pair<std::string, int> duas vezes. |
a.second, a.first | A contagem e a palavra do par. |
if (a.second != b.second) return a.second > b.second; | Contagens diferentes: a maior vem antes. |
return a.first < b.first; | Empate: ordem alfabética. Sem esse desempate, palavras com a mesma contagem poderiam sair em qualquer ordem. |
i < ranking.size() && i < 5 | Até 5 palavras, sem passar do fim num texto com menos de 5. |
$ g++ -std=c++23 -Wall -Wextra analisar.cpp -o analisar $ ./analisar 1. a: 6 2. um: 4 3. ana: 3 4. caderno: 3 5. café: 3
Num texto de verdade, o topo sempre é ocupado por palavras como "a", "o", "de", "que" (as stopwords, "palavras vazias"). Ignorá-las é um dos desafios.
Missão (etapa 5): em vez das 5 primeiras, mostre todas as palavras que aparecem 2 vezes ou mais. Só a condição do for muda (dica: ranking[i].second >= 2). A última linha deve ser:
10. paciência: 2
#include <algorithm>
#include <cctype>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
// Tira a pontuação e passa para minúsculas (só as letras sem acento).
std::string limpar(const std::string& palavra) {
std::string limpa;
for (unsigned char c : palavra) {
if (std::isalnum(c) || c >= 0x80) {
limpa += static_cast<char>(std::tolower(c));
}
}
return limpa;
}
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
std::map<std::string, int> contagem;
std::string linha;
while (std::getline(arq, linha)) {
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
std::string limpa = limpar(palavra);
if (!limpa.empty()) {
++contagem[limpa];
}
}
}
std::vector<std::pair<std::string, int>> ranking(contagem.begin(), contagem.end());
std::sort(ranking.begin(), ranking.end(), [](const auto& a, const auto& b) {
if (a.second != b.second) {
return a.second > b.second; // mais vezes primeiro
}
return a.first < b.first; // empate: ordem alfabética
});
for (std::size_t i = 0; i < ranking.size() && i < 5; ++i) {
std::cout << i + 1 << ". " << ranking[i].first << ": " << ranking[i].second << '\n';
}
}
A lojinha da Ana abre cedo, antes do sol. O café está quente, e a Ana conta os cadernos. Um caderno azul, um caderno verde, um caderno sem cor. Às dez horas chega a primeira cliente: a avó do Bruno. Ela quer lápis, borracha e paciência. A Ana não vende paciência, mas oferece um café. No fim do dia, a lojinha fecha. O café acabou!
#include <algorithm>
#include <cctype>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
// Tira a pontuação e passa para minúsculas (só as letras sem acento).
std::string limpar(const std::string& palavra) {
std::string limpa;
for (unsigned char c : palavra) {
if (std::isalnum(c) || c >= 0x80) {
limpa += static_cast<char>(std::tolower(c));
}
}
return limpa;
}
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
std::map<std::string, int> contagem;
std::string linha;
while (std::getline(arq, linha)) {
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
std::string limpa = limpar(palavra);
if (!limpa.empty()) {
++contagem[limpa];
}
}
}
std::vector<std::pair<std::string, int>> ranking(contagem.begin(), contagem.end());
std::sort(ranking.begin(), ranking.end(), [](const auto& a, const auto& b) {
if (a.second != b.second) {
return a.second > b.second; // mais vezes primeiro
}
return a.first < b.first; // empate: ordem alfabética
});
for (std::size_t i = 0; i < ranking.size() && ranking[i].second >= 2; ++i) {
std::cout << i + 1 << ". " << ranking[i].first << ": " << ranking[i].second << '\n';
}
}
Etapa 6 · A palavra mais longa
Para achar a mais longa, guardamos a "campeã" até agora e comparamos cada palavra limpa com ela. A pergunta é: comparar o quê? size() conta bytes. Já temos a função certa da etapa 2, contarCaracteres (copie-a para este arquivo). As linhas novas:
std::string maisLonga; // começa vazia: 0 letras
…
if (!limpa.empty()) {
++contagem[limpa];
if (contarCaracteres(limpa) > contarCaracteres(maisLonga)) {
maisLonga = limpa;
}
}
…
std::cout << "Palavra mais longa: " << maisLonga << " ("
<< contarCaracteres(maisLonga) << " letras)\n";
$ g++ -std=c++23 -Wall -Wextra analisar.cpp -o analisar $ ./analisar Palavra mais longa: paciência (9 letras) 1. a: 6 2. um: 4 3. ana: 3 4. caderno: 3 5. café: 3
size()
Troque as duas contarCaracteres(…) do teste e da frase por .size() (limpa.size() > maisLonga.size()). Compila sem aviso e mostra:
$ g++ -std=c++23 -Wall -Wextra analisar.cpp -o analisar $ ./analisar Palavra mais longa: paciência (10 letras) 1. a: 6 2. um: 4 3. ana: 3 4. caderno: 3 5. café: 3
"paciência" tem 9 letras, não 10: o ê ocupa 2 bytes. Aqui a palavra escolhida ainda foi a certa, por sorte. Num texto com "atenção" (7 letras, 9 bytes) e "primeiro" (8 letras, 8 bytes), o size() escolheria "atenção". Regra: em texto com acentos, size() responde "quantos bytes"; para "quantas letras", conte os caracteres.
Missão (etapa 6): ache também a palavra mais curta e mostre Palavra mais curta: a. Cuidado: a maisLonga pode começar vazia (0 letras) porque qualquer palavra é maior; a maisCurta não. Dica: troque quando maisCurta.empty() ou quando a palavra for menor.
#include <algorithm>
#include <cctype>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
// Quantos caracteres (não bytes) um texto em UTF-8 tem.
std::size_t contarCaracteres(const std::string& texto) {
std::size_t n = 0;
for (unsigned char c : texto) {
if ((c & 0xC0) != 0x80) { // não é byte de continuação
++n;
}
}
return n;
}
// Tira a pontuação e passa para minúsculas (só as letras sem acento).
std::string limpar(const std::string& palavra) {
std::string limpa;
for (unsigned char c : palavra) {
if (std::isalnum(c) || c >= 0x80) {
limpa += static_cast<char>(std::tolower(c));
}
}
return limpa;
}
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
std::map<std::string, int> contagem;
std::string maisLonga;
std::string linha;
while (std::getline(arq, linha)) {
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
std::string limpa = limpar(palavra);
if (!limpa.empty()) {
++contagem[limpa];
if (contarCaracteres(limpa) > contarCaracteres(maisLonga)) {
maisLonga = limpa;
}
}
}
}
std::cout << "Palavra mais longa: " << maisLonga << " ("
<< contarCaracteres(maisLonga) << " letras)\n";
std::vector<std::pair<std::string, int>> ranking(contagem.begin(), contagem.end());
std::sort(ranking.begin(), ranking.end(), [](const auto& a, const auto& b) {
if (a.second != b.second) {
return a.second > b.second; // mais vezes primeiro
}
return a.first < b.first; // empate: ordem alfabética
});
for (std::size_t i = 0; i < ranking.size() && i < 5; ++i) {
std::cout << i + 1 << ". " << ranking[i].first << ": " << ranking[i].second << '\n';
}
}
A lojinha da Ana abre cedo, antes do sol. O café está quente, e a Ana conta os cadernos. Um caderno azul, um caderno verde, um caderno sem cor. Às dez horas chega a primeira cliente: a avó do Bruno. Ela quer lápis, borracha e paciência. A Ana não vende paciência, mas oferece um café. No fim do dia, a lojinha fecha. O café acabou!
#include <algorithm>
#include <cctype>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
// Quantos caracteres (não bytes) um texto em UTF-8 tem.
std::size_t contarCaracteres(const std::string& texto) {
std::size_t n = 0;
for (unsigned char c : texto) {
if ((c & 0xC0) != 0x80) { // não é byte de continuação
++n;
}
}
return n;
}
// Tira a pontuação e passa para minúsculas (só as letras sem acento).
std::string limpar(const std::string& palavra) {
std::string limpa;
for (unsigned char c : palavra) {
if (std::isalnum(c) || c >= 0x80) {
limpa += static_cast<char>(std::tolower(c));
}
}
return limpa;
}
int main() {
std::ifstream arq{"texto.txt"};
if (!arq) {
std::cerr << "Não consegui abrir texto.txt\n";
return 1;
}
std::map<std::string, int> contagem;
std::string maisLonga;
std::string maisCurta;
std::string linha;
while (std::getline(arq, linha)) {
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
std::string limpa = limpar(palavra);
if (!limpa.empty()) {
++contagem[limpa];
if (contarCaracteres(limpa) > contarCaracteres(maisLonga)) {
maisLonga = limpa;
}
if (maisCurta.empty() || contarCaracteres(limpa) < contarCaracteres(maisCurta)) {
maisCurta = limpa;
}
}
}
}
std::cout << "Palavra mais longa: " << maisLonga << " ("
<< contarCaracteres(maisLonga) << " letras)\n";
std::cout << "Palavra mais curta: " << maisCurta << '\n';
std::vector<std::pair<std::string, int>> ranking(contagem.begin(), contagem.end());
std::sort(ranking.begin(), ranking.end(), [](const auto& a, const auto& b) {
if (a.second != b.second) {
return a.second > b.second; // mais vezes primeiro
}
return a.first < b.first; // empate: ordem alfabética
});
for (std::size_t i = 0; i < ranking.size() && i < 5; ++i) {
std::cout << i + 1 << ". " << ranking[i].first << ": " << ranking[i].second << '\n';
}
}
Etapa 7 · O relatório
Falta juntar tudo num relatório bonito e deixar o programa analisar qualquer arquivo, não só o texto.txt. Quem diz qual é o usuário, na linha de comando: ./analisar outro.txt. O main passa a receber dois parâmetros:
int main(int argc, char* argv[]) {
std::string nome = argc > 1 ? argv[1] : "texto.txt";
std::ifstream arq;
if (nome != "-") { // "-" quer dizer: ler do teclado
arq.open(nome);
if (!arq) {
std::cerr << "Não consegui abrir " << nome << '\n';
return 1;
}
}
std::istream& entrada = (nome == "-") ? std::cin : arq;
…
| Trecho | O que faz |
|---|---|
int main(int argc, char* argv[]) | A forma do main que recebe a linha de comando. argc (argument count) é quantos pedaços ela tem; argv (argument vector) são os pedaços. Em ./analisar outro.txt: argc = 2, argv[0] = "./analisar", argv[1] = "outro.txt". |
argc > 1 ? argv[1] : "texto.txt" | Com argumento, usa ele; sem, o padrão (operador ternário). |
arq.open(nome); | Abre depois de criado. Só abrimos se o nome não for "-". |
std::istream& entrada = … ? std::cin : arq; | Uma referência para "de onde ler": o teclado (std::cin) ou o arquivo. Os dois são fluxos de entrada (std::istream), e o resto do programa usa entrada sem saber qual é. O - para "ler da entrada padrão" é um costume dos programas do Linux (o cat - faz o mesmo). |
E o fim do main, com o relatório em colunas e uma barrinha para cada palavra frequente:
…
std::cout << std::format("Análise de {}\n\n", nome);
std::cout << std::format("{:<20}{:>6}\n", "Linhas", linhas);
std::cout << std::format("{:<20}{:>6}\n", "Palavras", palavras);
std::cout << std::format("{:<20}{:>6}\n", "Palavras diferentes", contagem.size());
std::cout << std::format("{:<20}{:>6}\n", "Caracteres", caracteres);
std::cout << std::format("{:<20}{:>6}\n", "Bytes", bytes);
std::cout << std::format("\nPalavra mais longa: {} ({} letras)\n", maisLonga,
contarCaracteres(maisLonga));
std::cout << "\nMais frequentes:\n";
for (std::size_t i = 0; i < ranking.size() && i < 5; ++i) {
const auto& [palavra, vezes] = ranking[i];
std::cout << std::format("{:>2}. {:<10}{:>3} {}\n", i + 1, palavra, vezes,
std::string(vezes, '#'));
}
}
| Trecho | O que faz |
|---|---|
"{:<20}{:>6}\n" | Rótulo em 20 colunas à esquerda, número em 6 à direita: os números se alinham. |
const auto& [palavra, vezes] = ranking[i]; | Desestrutura o par em dois nomes que dizem o que são (melhor que .first e .second). |
std::string(vezes, '#') | Um texto com vezes repetições de #: a barra do gráfico. |
O resultado com o texto.txt está no começo da página. Com outro nome de arquivo, com um que não existe e lendo do teclado:
$ ./analisar texto.txt | head -3 Análise de texto.txt Linhas 7 $ ./analisar nada.txt Não consegui abrir nada.txt $ echo $? 1 $ echo "Oi, oi, OI! Tudo bem? Tudo." | ./analisar - Análise de - Linhas 1 Palavras 6 Palavras diferentes 3 Caracteres 28 Bytes 28 Palavra mais longa: tudo (4 letras) Mais frequentes: 1. oi 3 ### 2. tudo 2 ## 3. bem 1 #
Com ./analisar - sozinho, o programa espera você digitar; termine com Ctrl+D numa linha vazia (o "fim da entrada") para ver o relatório.
Missão (etapa 7): a caixa roda ./analisar texto.txt. Acrescente ao relatório a média de letras por palavra: some contarCaracteres(limpa) de cada palavra numa variável letras e mostre letras / palavras com uma casa decimal ({:>6.1f}; converta para double antes de dividir, senão a divisão é inteira). A linha nova:
Letras por palavra 3.9
#include <algorithm>
#include <cctype>
#include <format>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
// Quantos caracteres (não bytes) um texto em UTF-8 tem.
std::size_t contarCaracteres(const std::string& texto) {
std::size_t n = 0;
for (unsigned char c : texto) {
if ((c & 0xC0) != 0x80) { // não é byte de continuação
++n;
}
}
return n;
}
// Tira a pontuação e passa para minúsculas (só as letras sem acento).
std::string limpar(const std::string& palavra) {
std::string limpa;
for (unsigned char c : palavra) {
if (std::isalnum(c) || c >= 0x80) {
limpa += static_cast<char>(std::tolower(c));
}
}
return limpa;
}
int main(int argc, char* argv[]) {
std::string nome = argc > 1 ? argv[1] : "texto.txt";
std::ifstream arq;
if (nome != "-") { // "-" quer dizer: ler do teclado
arq.open(nome);
if (!arq) {
std::cerr << "Não consegui abrir " << nome << '\n';
return 1;
}
}
std::istream& entrada = (nome == "-") ? std::cin : arq;
int linhas = 0;
std::size_t bytes = 0, caracteres = 0, palavras = 0;
std::map<std::string, int> contagem;
std::string maisLonga;
std::string linha;
while (std::getline(entrada, linha)) {
++linhas;
bytes += linha.size() + 1; // +1: o '\n' que o getline tirou
caracteres += contarCaracteres(linha) + 1;
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
++palavras;
std::string limpa = limpar(palavra);
if (limpa.empty()) {
continue;
}
++contagem[limpa];
if (contarCaracteres(limpa) > contarCaracteres(maisLonga)) {
maisLonga = limpa;
}
}
}
std::vector<std::pair<std::string, int>> ranking(contagem.begin(), contagem.end());
std::sort(ranking.begin(), ranking.end(), [](const auto& a, const auto& b) {
if (a.second != b.second) {
return a.second > b.second; // mais vezes primeiro
}
return a.first < b.first; // empate: ordem alfabética
});
std::cout << std::format("Análise de {}\n\n", nome);
std::cout << std::format("{:<20}{:>6}\n", "Linhas", linhas);
std::cout << std::format("{:<20}{:>6}\n", "Palavras", palavras);
std::cout << std::format("{:<20}{:>6}\n", "Palavras diferentes", contagem.size());
std::cout << std::format("{:<20}{:>6}\n", "Caracteres", caracteres);
std::cout << std::format("{:<20}{:>6}\n", "Bytes", bytes);
std::cout << std::format("\nPalavra mais longa: {} ({} letras)\n", maisLonga,
contarCaracteres(maisLonga));
std::cout << "\nMais frequentes:\n";
for (std::size_t i = 0; i < ranking.size() && i < 5; ++i) {
const auto& [palavra, vezes] = ranking[i];
std::cout << std::format("{:>2}. {:<10}{:>3} {}\n", i + 1, palavra, vezes,
std::string(vezes, '#'));
}
}
A lojinha da Ana abre cedo, antes do sol. O café está quente, e a Ana conta os cadernos. Um caderno azul, um caderno verde, um caderno sem cor. Às dez horas chega a primeira cliente: a avó do Bruno. Ela quer lápis, borracha e paciência. A Ana não vende paciência, mas oferece um café. No fim do dia, a lojinha fecha. O café acabou!
#include <algorithm>
#include <cctype>
#include <format>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
// Quantos caracteres (não bytes) um texto em UTF-8 tem.
std::size_t contarCaracteres(const std::string& texto) {
std::size_t n = 0;
for (unsigned char c : texto) {
if ((c & 0xC0) != 0x80) { // não é byte de continuação
++n;
}
}
return n;
}
// Tira a pontuação e passa para minúsculas (só as letras sem acento).
std::string limpar(const std::string& palavra) {
std::string limpa;
for (unsigned char c : palavra) {
if (std::isalnum(c) || c >= 0x80) {
limpa += static_cast<char>(std::tolower(c));
}
}
return limpa;
}
int main(int argc, char* argv[]) {
std::string nome = argc > 1 ? argv[1] : "texto.txt";
std::ifstream arq;
if (nome != "-") { // "-" quer dizer: ler do teclado
arq.open(nome);
if (!arq) {
std::cerr << "Não consegui abrir " << nome << '\n';
return 1;
}
}
std::istream& entrada = (nome == "-") ? std::cin : arq;
int linhas = 0;
std::size_t bytes = 0, caracteres = 0, palavras = 0;
std::map<std::string, int> contagem;
std::string maisLonga;
std::size_t letras = 0;
std::string linha;
while (std::getline(entrada, linha)) {
++linhas;
bytes += linha.size() + 1; // +1: o '\n' que o getline tirou
caracteres += contarCaracteres(linha) + 1;
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
++palavras;
std::string limpa = limpar(palavra);
if (limpa.empty()) {
continue;
}
++contagem[limpa];
letras += contarCaracteres(limpa);
if (contarCaracteres(limpa) > contarCaracteres(maisLonga)) {
maisLonga = limpa;
}
}
}
std::vector<std::pair<std::string, int>> ranking(contagem.begin(), contagem.end());
std::sort(ranking.begin(), ranking.end(), [](const auto& a, const auto& b) {
if (a.second != b.second) {
return a.second > b.second; // mais vezes primeiro
}
return a.first < b.first; // empate: ordem alfabética
});
std::cout << std::format("Análise de {}\n\n", nome);
std::cout << std::format("{:<20}{:>6}\n", "Linhas", linhas);
std::cout << std::format("{:<20}{:>6}\n", "Palavras", palavras);
std::cout << std::format("{:<20}{:>6}\n", "Palavras diferentes", contagem.size());
std::cout << std::format("{:<20}{:>6}\n", "Caracteres", caracteres);
std::cout << std::format("{:<20}{:>6}\n", "Bytes", bytes);
std::cout << std::format("{:<20}{:>6.1f}\n", "Letras por palavra",
static_cast<double>(letras) / palavras);
std::cout << std::format("\nPalavra mais longa: {} ({} letras)\n", maisLonga,
contarCaracteres(maisLonga));
std::cout << "\nMais frequentes:\n";
for (std::size_t i = 0; i < ranking.size() && i < 5; ++i) {
const auto& [palavra, vezes] = ranking[i];
std::cout << std::format("{:>2}. {:<10}{:>3} {}\n", i + 1, palavra, vezes,
std::string(vezes, '#'));
}
}
Experimente: esta caixa roda ./analisar - e digita duas linhas para você. Troque o texto da entrada pelo seu (um parágrafo de um livro, uma letra de música) e veja o relatório.
#include <algorithm>
#include <cctype>
#include <format>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
// Quantos caracteres (não bytes) um texto em UTF-8 tem.
std::size_t contarCaracteres(const std::string& texto) {
std::size_t n = 0;
for (unsigned char c : texto) {
if ((c & 0xC0) != 0x80) { // não é byte de continuação
++n;
}
}
return n;
}
// Tira a pontuação e passa para minúsculas (só as letras sem acento).
std::string limpar(const std::string& palavra) {
std::string limpa;
for (unsigned char c : palavra) {
if (std::isalnum(c) || c >= 0x80) {
limpa += static_cast<char>(std::tolower(c));
}
}
return limpa;
}
int main(int argc, char* argv[]) {
std::string nome = argc > 1 ? argv[1] : "texto.txt";
std::ifstream arq;
if (nome != "-") { // "-" quer dizer: ler do teclado
arq.open(nome);
if (!arq) {
std::cerr << "Não consegui abrir " << nome << '\n';
return 1;
}
}
std::istream& entrada = (nome == "-") ? std::cin : arq;
int linhas = 0;
std::size_t bytes = 0, caracteres = 0, palavras = 0;
std::map<std::string, int> contagem;
std::string maisLonga;
std::string linha;
while (std::getline(entrada, linha)) {
++linhas;
bytes += linha.size() + 1; // +1: o '\n' que o getline tirou
caracteres += contarCaracteres(linha) + 1;
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
++palavras;
std::string limpa = limpar(palavra);
if (limpa.empty()) {
continue;
}
++contagem[limpa];
if (contarCaracteres(limpa) > contarCaracteres(maisLonga)) {
maisLonga = limpa;
}
}
}
std::vector<std::pair<std::string, int>> ranking(contagem.begin(), contagem.end());
std::sort(ranking.begin(), ranking.end(), [](const auto& a, const auto& b) {
if (a.second != b.second) {
return a.second > b.second; // mais vezes primeiro
}
return a.first < b.first; // empate: ordem alfabética
});
std::cout << std::format("Análise de {}\n\n", nome);
std::cout << std::format("{:<20}{:>6}\n", "Linhas", linhas);
std::cout << std::format("{:<20}{:>6}\n", "Palavras", palavras);
std::cout << std::format("{:<20}{:>6}\n", "Palavras diferentes", contagem.size());
std::cout << std::format("{:<20}{:>6}\n", "Caracteres", caracteres);
std::cout << std::format("{:<20}{:>6}\n", "Bytes", bytes);
std::cout << std::format("\nPalavra mais longa: {} ({} letras)\n", maisLonga,
contarCaracteres(maisLonga));
std::cout << "\nMais frequentes:\n";
for (std::size_t i = 0; i < ranking.size() && i < 5; ++i) {
const auto& [palavra, vezes] = ranking[i];
std::cout << std::format("{:>2}. {:<10}{:>3} {}\n", i + 1, palavra, vezes,
std::string(vezes, '#'));
}
}
O código completo
Compile com g++ -std=c++23 -Wall -Wextra analisar.cpp -o analisar e rode com ./analisar, ./analisar arquivo.txt ou ./analisar -.
#include <algorithm>
#include <cctype>
#include <format>
#include <fstream>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
// Quantos caracteres (não bytes) um texto em UTF-8 tem.
std::size_t contarCaracteres(const std::string& texto) {
std::size_t n = 0;
for (unsigned char c : texto) {
if ((c & 0xC0) != 0x80) { // não é byte de continuação
++n;
}
}
return n;
}
// Tira a pontuação e passa para minúsculas (só as letras sem acento).
std::string limpar(const std::string& palavra) {
std::string limpa;
for (unsigned char c : palavra) {
if (std::isalnum(c) || c >= 0x80) {
limpa += static_cast<char>(std::tolower(c));
}
}
return limpa;
}
int main(int argc, char* argv[]) {
std::string nome = argc > 1 ? argv[1] : "texto.txt";
std::ifstream arq;
if (nome != "-") { // "-" quer dizer: ler do teclado
arq.open(nome);
if (!arq) {
std::cerr << "Não consegui abrir " << nome << '\n';
return 1;
}
}
std::istream& entrada = (nome == "-") ? std::cin : arq;
int linhas = 0;
std::size_t bytes = 0, caracteres = 0, palavras = 0;
std::map<std::string, int> contagem;
std::string maisLonga;
std::string linha;
while (std::getline(entrada, linha)) {
++linhas;
bytes += linha.size() + 1; // +1: o '\n' que o getline tirou
caracteres += contarCaracteres(linha) + 1;
std::istringstream fluxo{linha};
std::string palavra;
while (fluxo >> palavra) {
++palavras;
std::string limpa = limpar(palavra);
if (limpa.empty()) {
continue;
}
++contagem[limpa];
if (contarCaracteres(limpa) > contarCaracteres(maisLonga)) {
maisLonga = limpa;
}
}
}
std::vector<std::pair<std::string, int>> ranking(contagem.begin(), contagem.end());
std::sort(ranking.begin(), ranking.end(), [](const auto& a, const auto& b) {
if (a.second != b.second) {
return a.second > b.second; // mais vezes primeiro
}
return a.first < b.first; // empate: ordem alfabética
});
std::cout << std::format("Análise de {}\n\n", nome);
std::cout << std::format("{:<20}{:>6}\n", "Linhas", linhas);
std::cout << std::format("{:<20}{:>6}\n", "Palavras", palavras);
std::cout << std::format("{:<20}{:>6}\n", "Palavras diferentes", contagem.size());
std::cout << std::format("{:<20}{:>6}\n", "Caracteres", caracteres);
std::cout << std::format("{:<20}{:>6}\n", "Bytes", bytes);
std::cout << std::format("\nPalavra mais longa: {} ({} letras)\n", maisLonga,
contarCaracteres(maisLonga));
std::cout << "\nMais frequentes:\n";
for (std::size_t i = 0; i < ranking.size() && i < 5; ++i) {
const auto& [palavra, vezes] = ranking[i];
std::cout << std::format("{:>2}. {:<10}{:>3} {}\n", i + 1, palavra, vezes,
std::string(vezes, '#'));
}
}
Desafios extras
- Maiúsculas acentuadas. Faça
Àsviraràs. Dica: em UTF-8, as maiúsculas acentuadas do português sãoc3 80ac3 9d, e a minúscula é o segundo byte +0x20(À=c3 80,à=c3 a0). - Palavras vazias. Ignore "a", "o", "e", "de", "do", "da", "um" no ranking, guardando-as num std::set.
- Palavras com hífen. "guarda-chuva" vira "guardachuva". Mantenha o hífen quando ele estiver entre letras.
- Arquivos do Windows. Linhas terminadas em
\r\ndeixam um\rno fim de cada linha lida pelogetline, e os bytes contados mudam. Detecte e remova. - Vários arquivos.
./analisar a.txt b.txt c.txt: um relatório para cada um (um laço emargv) e um total no fim, como owcfaz. - Ordem alfabética de gente. Faça "lápis" vir antes de "lojinha" comparando as palavras sem acento (troque
áporasó para comparar).
Resumo
std::ifstream arq{nome};+if (!arq): um arquivo que não abriu não lança exceção, só fica em falha. Confira sempre; avise emstd::cerre saia comreturn 1.while (std::getline(arq, linha))lê linha por linha, sem o\n.- Em UTF-8, um caractere tem de 1 a 4 bytes.
size()conta bytes; para contar caracteres, conte os bytes com(c & 0xC0) != 0x80, usandounsigned char. Confira comwc -cewc -m. std::istringstream+>>separa palavras por espaços, como owc -w; a pontuação vem grudada.<cctype>(isalnum,tolower) só entende os caracteres do inglês: preserve os bytes ≥ 0x80.++contagem[palavra]numstd::map<std::string, int>conta ocorrências; para ordenar por contagem, copie para umvectordepaire usestd::sortcom uma lambda que desempata.int main(int argc, char* argv[])recebe a linha de comando;std::istream&deixa o mesmo código ler de arquivo ou do teclado.