分别用C和C++实现词频统计,要求用fopen打开文本文件,最后将统计结果输出至文件。
1、首先用纯C实现,定义一个存放单词和出现频次的结点,用链表实现存储,代码如下:
#include <stdio.h>#include <stdlib.h>#include <string.h>// 定义一个存放单词和频次的节点,用链表实现存储typedef struct WordNode { char word[100]; // 存放单词 int word_cnt; // 单词出现次数 struct WordNode* p_next; // 结构体指针} WordNode;// 函数声明void ProcessWord(char* word); // 处理当前单词并统计void CountWord(char* word); // 统计当前单词WordNode* SearchWord(char* word); // 查找当前单词void SortResult(void); // 对统计结果按字典顺序排序void PrintResult(void); // 输出统计结果void Release(void); // 释放内存// 全局变量WordNode* g_p_head = NULL; // 存放单词链表的头指针int main(int argc, char* argv[]) { if (argc != 2) { printf("usage: %s filepath", argv[0]); exit(1); } char temp_word[100]; // 用于临时存放当前单词 FILE* fp_read = NULL; if ((fp_read = fopen(argv[1], "r")) == NULL) { printf("open %s error!\n", argv[1]); exit(1); } // 循环读取文本中的单词并进行处理 while (fscanf(fp_read, "%s", temp_word) != EOF) { ProcessWord(temp_word); } fclose(fp_read); // 关闭文件 SortResult(); // 按字典顺序排序所统计的单词 PrintResult(); // 输出统计结果至文本 Release(); // 释放内存 return 0;}// 处理当前单词并统计void ProcessWord(char* word) { char* special_char = ",;.:?\"\'\\><-+=|*^&%$#@!()"; // 可能存在的特殊字符,标点符号 int spec_length = strlen(special_char); int word_length = strlen(word); int int_res = atoi(word); // 如果当前单词不是数字而是英文单词,才进行单词统计 if (int_res == 0 && strcmp("0", word) != 0) { int i, j; for (i = 0; i < word_length; i++) { // 将单词中的大写字母均转化为小写字母 if (word[i] >= 'A' && word[i] <= 'Z') { word[i] = word[i] + 32; } // 将单词中可能存在的特殊字符均以空格符代替 for (j = 0; j < spec_length; j++) { if (word[i] == special_char[j]) { word[i] = ' '; } } } // 用strtok分割经上述步骤处理的单词(可能存在多个单词),并对每个单词进行统计 char* temp_word = NULL; for (temp_word = strtok(word, " "); temp_word != NULL; temp_word = strtok(NULL, " ")) { CountWord(temp_word); } } else { // 说明传入的字符串是数字,直接返回不做处理 return; }}// 统计当前单词void CountWord(char* word) { WordNode* p_find = NULL; p_find = SearchWord(word); if (p_find == NULL) { return; } else { p_find->word_cnt++; // 对应单词次数加一 }}// 查找当前单词(链表查找效率较低下)WordNode* SearchWord(char* word) { // 链表为空时,统计第一个单词 if (g_p_head == NULL) { g_p_head = new WordNode; strcpy(g_p_head->word, word); g_p_head->word_cnt = 0; g_p_head->p_next = NULL; return g_p_head; } // 检查现有链表,确定当前查找单词的位置 WordNode* p_cur = g_p_head; WordNode* p_pre = NULL; while ((p_cur != NULL) && (strcmp(p_cur->word, word) != 0)) { p_pre = p_cur; p_cur = p_cur->p_next; } // 该单词不存在时,直接加入链表尾部 if (p_cur == NULL) { p_cur = new WordNode; strcpy(p_cur->word, word); p_cur->word_cnt = 0; p_cur->p_next = NULL; p_pre->p_next = p_cur; } return p_cur;}// 对统计结果按字典顺序排序void SortResult(void) { WordNode* p_pre = NULL; WordNode* p_cur = NULL; WordNode p_tmp; for (p_pre = g_p_head; p_pre->p_next != NULL; p_pre = p_pre->p_next) { for (p_cur = p_pre->p_next; p_cur != NULL; p_cur = p_cur->p_next) { if (strcmp(p_pre->word, p_cur->word) > 0) { // 交换结点的数据域和指针域 p_tmp = *p_pre; *p_pre = *p_cur; *p_cur = p_tmp; p_tmp.p_next = p_pre->p_next; p_pre->p_next = p_cur->p_next; p_cur->p_next = p_tmp.p_next; } } }}// 输出统计结果void PrintResult(void) { FILE* fp_write = fopen("result_liang.txt", "w"); if (g_p_head == NULL) { printf("No word in this file!\n"); return; } WordNode* p_cur = g_p_head; while (p_cur != NULL) { fprintf(fp_write, "%-20s\t%d\n", p_cur->word, p_cur->word_cnt); p_cur = p_cur->p_next; } fclose(fp_write);}// 释放内存void Release(void) { if (g_p_head == NULL) { return; } WordNode* p_cur = g_p_head; WordNode* p_p_tmp = NULL; while (p_cur != NULL) { p_p_tmp = p_cur->p_next; delete p_cur; p_cur = p_p_tmp; }}
经编译运行结果可以发现两个问题:
一是整个过程用链表实现,在统计单词时需要查找整个链表,效率较低,且后期还需要对链表进行排序,总之,利用这种方式实现整体效率较为低下;二是在代码的ProcessWord函数中,以下过程是在处理文本中可能存在的数值的情况,利用atoi函数判断,atoi函数返回值为0则代表当前word不是数值而是英文单词,否则返回转换的数值。但有一种情况,即当word为“0”或是“00”或是“000”等时,返回的结果也为0,只利用strcmp("0", word)判断是不行的,若文本中存在类似“00”或者“000”等多0的情况,统计结果中还是会出现“0”和它的频次。
int int_res = atoi(word);// 如果当前单词不是数字而是英文单词,才进行单词统计if (int_res == 0 && strcmp("0", word) != 0) { int i, j; for (i = 0; i < word_length; i++) { // 将单词中的大写字母均转化为小写字母 if (word[i] >= 'A' && word[i] <= 'Z') { word[i] = word[i] + 32; } // 将单词中可能存在的特殊字符均以空格符代替 for (j = 0; j < spec_length; j++) { if (word[i] == special_char[j]) { word[i] = ' '; } } } // 用strtok分割经上述步骤处理的单词(可能存在多个单词),并对每个单词进行统计 char* temp_word = NULL; for (temp_word = strtok(word, " "); temp_word != NULL; temp_word = strtok(NULL, " ")) { CountWord(temp_word); }} else { // 说明传入的字符串是数字,直接返回不做处理 return;}
2、下面用C++实现,绝大部分功能实现都使用库函数,涉及了一些C++11的内容,代码如下:
#include <iostream>#include <map>#include <vector>#include <algorithm>#include <string>#include <locale>#include <cstdio>#include <cstring>// 函数声明bool GetFileContent(const std::string& file_path, std::string* content);bool SplitWords(const std::string& content, std::vector<std::string>* words);bool CountWordFrequency(const std::vector<std::string>& words, std::map<std::string, int>* word_2_count);bool OutputResultToFile(const std::map<std::string, int>& word_2_count, const std::string& file_path);int main(int argc, char* argv[]) { if (argc != 2) { printf("Usage: %s filepath", argv[0]); return false; } std::string file_content; // 读取文件内容并存入file_content中 bool get_file_result = GetFileContent(argv[1], &file_content); if (!get_file_result) { printf("Get file %s content error!\n", argv[1]); return false; } // 划分file_content中的单词并存入vector容器words中 std::vector<std::string> words; bool split_word_result = SplitWords(file_content, &words); if (!split_word_result) { printf("Split file %s content to words error!\n ", argv[1]); return false; } // 统计单词和词频并用map容器words_frequency_map存放 std::map<std::string, int> words_frequency_map; bool count_word_frequency_result = CountWordFrequency(words, &words_frequency_map); if (!count_word_frequency_result) { printf("Count word frequency error!\n"); return false; } // 将统计结果写入指定文本文件中 std::string result_file = "result_liang.txt"; bool output_to_file_result = OutputResultToFile(words_frequency_map, result_file); if (!output_to_file_result) { printf("Output result to file %s error!\n", result_file.c_str()); return false; } return 0;}bool GetFileContent(const std::string& file_path, std::string* content) { // 要检查传入参数 if (file_path.empty()) return false; if (!content) return false; FILE* fp = fopen(file_path.c_str(), "r"); if (!fp) return false; std::string inner_content; inner_content.reserve(4096); // 预留空间,提升效率 const int buffer_size = 4096; char buffer[buffer_size]; size_t read_size = 0; bool ok = true; while (1) { // 每读一次写一次 read_size = fread(buffer, 1, sizeof(buffer), fp); if (read_size >= sizeof(buffer)) { inner_content.append(buffer, read_size); continue; } // 读到文件尾部,将剩余内容写入inner_content并跳出循环 if (feof(fp)) { inner_content.append(buffer, read_size); break; } // on error ok = false; break; } if (ok) content->swap(inner_content); // 交换空间给传出参数 // *content = std::move(inner_content); fclose(fp); return ok;}bool SplitWords(const std::string& content, std::vector<std::string>* words) { if (!words) return false; if (content.empty()) // ? return true; std::string word; std::vector<std::string> wordlist; // 循环读取内容并划分单词存入vector容器中 int chr = 0; for (int i = 0; i < (int)content.size(); ++i) { //++i占用的空间比i++要小,注意运算符重载中i++有一个亚元 chr = content[i]; if (!std::isalpha(chr)) { // 不是英文字母 if (!word.empty()) // 不为空,即word中存入了单词,将该单词放入wordlist容器中 wordlist.emplace_back(std::move(word)); // 相比于push_back,emplace_back可以避免额外类的复制和移动操作 // 用move顺便清空word continue; } word.append(1, chr); } if (!word.empty()) // 将最后一个单词放入wordlist中 wordlist.emplace_back(std::move(word)); words->swap(wordlist); // 交换空间给传出参数 return true;}bool CountWordFrequency(const std::vector<std::string>& words, std::map<std::string, int>* word_2_count) { if (!word_2_count) return false; if (words.empty()) // ? return true; std::map<std::string, int> inner_word_counts; std::string lower_word; auto it = inner_word_counts.begin(); std::pair<std::map<std::string, int>::iterator, bool> insert_result; for (const auto& word : words) { // for auto // 将单词转化为小写 lower_word.resize(word.size()); std::transform(word.cbegin(), word.cend(), lower_word.begin(), ::tolower); it = inner_word_counts.find(lower_word); if (it == inner_word_counts.end()) { // 若是新的单词 insert_result = inner_word_counts.insert(std::make_pair(lower_word, 0)); it = insert_result.first; } it->second++; } word_2_count->swap(inner_word_counts); return true;}bool OutputResult(const std::map<std::string, int>& word_2_count, FILE* fp) { if (!fp) return false; if (word_2_count.empty()) return true; int size = 0; bool ok = true; for (const auto& pair : word_2_count) { size = fprintf(fp, "%-20s\t%d\n", pair.first.c_str(), pair.second); if (size < 0) { ok = false; break; } } return ok;}bool OutputResultToFile(const std::map<std::string, int>& word_2_count, const std::string& file_path) { if (file_path.empty()) return false; if (word_2_count.empty()) return true; FILE* fp = fopen(file_path.c_str(), "w"); if (!fp) return false; bool ok = OutputResult(word_2_count, fp); if (!ok) { fclose(fp); return false; } fclose(fp); return ok;}
未完待续...