乐趣区

CC-统计词频

分别用 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;
}

未完待续 …

退出移动版