统计单词出现频率及排序 从单机到多机合作 图文示例
本文是学习 多线程服务端编程的练习
书籍作者陈硕的博客也有提到这个题目
http://blog.csdn.net/solstice/article/details/8497475
第一个层次很简单 单机 一个小文件 读进来进行处理 然后对每个单词进行统计排序 记录每个单词出现频率
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | // WordFrequent.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <algorithm> #include <iostream> #include <unordered_map> #include <vector> #include <sstream> #include <fstream> using namespace std; std::unordered_map<std::string, int > mapStringFrequent; void SplitStrig(std::string& line, std::vector<std::string>& vecString) { typedef std::string::size_type strSize; strSize i = 0; while (i != line.size()) { while (i != line.size() && isspace (line[i])) ++i; strSize j = i; while (j != line.size() && ! isspace (line[j])) ++j; if (i != j) { vecString.push_back(line.substr(i, j - i)); i = j; } } } bool CheckWord(std::string s) { typedef std::string::size_type strSize; strSize i = 0; while (i != s.size()) { if ( isalpha (s[i])) { i++; } else { return false ; } } return true ; } void VecInputMap(std::vector<std::string>& vecString, std::unordered_map<std::string, int >& mapStringFrequent) { for ( auto it : vecString) { if (CheckWord(it)) { transform(it.begin(), it.end(), it.begin(), :: tolower ); mapStringFrequent[it]++; } } } bool ReadFile( const string& fileName) { bool ret = false ; std::ifstream in(fileName); if (in.bad() || !in) { return ret; } std::string line; std::vector<std::string> vecString; try { while (getline(in, line)) { //std::cout << line << std::endl; SplitStrig(line, vecString); } VecInputMap(vecString, mapStringFrequent); } catch (std::exception& e) { std::cout << line << std::endl; in.close(); std::cerr << e.what() << std::endl; return ret; } in.close(); ret = true ; return ret; } void WriteToFile( const string& fileName, std::vector<std::pair< int , std::string>>& vecWordFreq) { std::ofstream out(fileName); if (out.bad() || !out) { return ; } for ( auto & it : vecWordFreq) { out << it.second << '\t' << it.first << std::endl; } out.close(); } //============================================================ int main() { bool ret = ReadFile( "1.txt" ); if (!ret) return -1; //ret = ReadFile("2.txt"); //if (!ret) // return -1; //ret = ReadFile("3.txt"); //if (!ret) // return -1; //ret = ReadFile("4.txt"); //if (!ret) // return -1; //ret = ReadFile("5.txt"); //if (!ret) // return -1; //ret = ReadFile("6.txt"); //if (!ret) // return -1; //ret = ReadFile("7.txt"); //if (!ret) // return -1; //ret = ReadFile("8.txt"); //if (!ret) // return -1; //ret = ReadFile("9.txt"); //if (!ret) // return -1; //ret = ReadFile("10.txt"); //if (!ret) // return -1; std::vector<std::pair< int , std::string>> freq; freq.reserve(mapStringFrequent.size()); for ( auto & it : mapStringFrequent){ freq.push_back(make_pair(it.second, it.first)); } std::sort(freq.begin(), freq.end(), []( const std::pair< int , std::string>& lhs, // const auto& lhs in C++14 const std::pair< int , std::string>& rhs) { return lhs.first > rhs.first; }); //for (auto it : freq) { // std::cout << it.first << '\t' << it.second << '\n'; //} WriteToFile( "freqResult.txt" , freq); return 0; } |
第二个层次 就是文件较大 单词量较多 如果一次性读入并使用vector容器存放 以及使用hash容器进行统计频率 会出现内存等资源不足现象
那么就依次读取进内存 将解析的单词存放进vector中,当vector中的容量到达一个阈值 将vector中的单词放进map容器中统计单词出现频率 vector容器清空
但是map容器有多个 各个单词存放进那个map容器根据hash函数决定
当map容器的容量达到阈值后 写入文件 map容器清空
这样我们就得到多个记录单词出现频率的文本 供后继步骤分析合并 但是一个单词的出现频率肯定只出现在一个记录文本中(因为相同的单词具有相同的hash值 对应同一个记录文本)
如图
代码如下(记录合并代码后继完成)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | // WordFrequentLargeFile.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <algorithm> #include <iostream> #include <unordered_map> #include <vector> #include <sstream> #include <fstream> using namespace std; #define BUCKET_NUM 10 std::unordered_map<std::string, int > mapStringFrequent[BUCKET_NUM]; std::ofstream out[BUCKET_NUM]; #define MAX_WORD_SIZE 1024*1024 void SplitStrig(std::string& line, std::vector<std::string>& vecString) { typedef std::string::size_type strSize; strSize i = 0; while (i != line.size()) { while (i != line.size() && isspace (line[i])) ++i; strSize j = i; while (j != line.size() && ! isspace (line[j])) ++j; if (i != j) { vecString.push_back(line.substr(i, j - i)); i = j; } } } bool CheckWord(std::string s) { typedef std::string::size_type strSize; strSize i = 0; while (i != s.size()) { if ( isalpha (s[i])) { i++; } else { return false ; } } return true ; } void VecInputMap(std::vector<std::string>& vecString, std::unordered_map<std::string, int > mapStringFrequent[]) { for ( auto it : vecString) { if (CheckWord(it)) { transform(it.begin(), it.end(), it.begin(), :: tolower ); int idx = std::hash<string>()(it) % BUCKET_NUM; mapStringFrequent[idx][it]++; if (mapStringFrequent[idx].size() >= MAX_WORD_SIZE) { for ( auto & it : mapStringFrequent[idx]) { out[idx] << it.second << '\t' << it.first << std::endl; } mapStringFrequent[idx].clear(); } } } } bool ReadFile( const string& fileName) { bool ret = false ; std::ifstream in(fileName); if (in.bad() || !in) { return ret; } std::string line; std::vector<std::string> vecString; try { while (getline(in, line)) { SplitStrig(line, vecString); //std::cout << line << std::endl; if (vecString.size() >= MAX_WORD_SIZE) { //std::cout << "too many words" << std::endl; VecInputMap(vecString, mapStringFrequent); vecString.clear(); } } for ( int i = 0; i < BUCKET_NUM; i++) { for ( auto & it : mapStringFrequent[i]) { out[i] << it.second << '\t' << it.first << std::endl; } mapStringFrequent[i].clear(); } } catch (std::exception& e) { in.close(); std::cerr << e.what() << std::endl; return ret; } std::cout << vecString.size() << std::endl; ret = true ; return ret; } int main() { std::string fileName = "0bucket.txt" ; for ( int i = 0; i < BUCKET_NUM; i++) { out[i].close(); out[i].open(fileName); fileName[0]++; } ReadFile( "LargeFile.txt" ); return 0; } |
运行效果如图
运行前
运行后
合并代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | // WordFrequentMerge.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <algorithm> #include <iostream> #include <unordered_map> #include <deque> #include <vector> #include <sstream> #include <fstream> using namespace std; #define DEFAULT_FILE_NAME "0bucket.txt" #define BUCKET_NUM 10 std::ifstream in[BUCKET_NUM]; std::deque<std::pair< int , std::string>> deqFreqRecord[BUCKET_NUM]; void OpenFile() { std::string fileName = "0bucket.txt" ; for ( int i = 0; i < BUCKET_NUM; i++) { in[i].close(); in[i].open(fileName); if (in[i].bad() || !in[i]) { std::cerr << "open file error.exit!!" << std::endl; return ; } fileName[0]++; } } void ReadSortEle_(std::ifstream& in, std::deque<std::pair< int , std::string>>& d) { std::string line,word; int freq; try { while (getline(in, line)) { std::string::size_type pos = line.find( '\t' ); if (pos != std::string::npos) { freq = std::stoi(line.substr(0, pos)); word = line.substr(pos + 1); //std::cout << word << "\t" << freq << std::endl; d.push_back(std::make_pair(freq, word)); } } } catch (std::exception& e) { std::cout << e.what() << std::endl; return ; } std::sort(d.begin(),d.end(), []( const std::pair< int , std::string>& lhs, const std::pair< int , std::string>& rhs) { return lhs.first > rhs.first; }); } void ReadSortEle(std::ifstream in[], std::deque<std::pair< int , std::string>> d[]) { for ( int i = 0; i < BUCKET_NUM; i++) { ReadSortEle_(in[i],d[i]); } } bool HeapCompareFunc(std::pair< int , std::string>& l, std::pair< int , std::string>& r) { return l.first < r.first; } void MergeFrequent(std::deque<std::pair< int , std::string>> d[]) { vector<std::pair< int , std::string>> vecHeap; for ( int i = 0; i < BUCKET_NUM; i++) { if (!d[i].empty()) { vecHeap.push_back(d[i].front()); d[i].pop_front(); } } std::make_heap(vecHeap.begin(), vecHeap.end(), HeapCompareFunc); while (!vecHeap.empty()) { std::pop_heap(vecHeap.begin(), vecHeap.end(), HeapCompareFunc); std::pair< int , std::string> p = vecHeap.back(); vecHeap.pop_back(); std::cout << p.second << "\t" << p.first << std::endl; int idx = std::hash<string>()(p.second) % BUCKET_NUM; if (!d[idx].empty()) { vecHeap.push_back(d[idx].front()); std::push_heap(vecHeap.begin(), vecHeap.end(), HeapCompareFunc); d[idx].pop_front(); } } } int main() { OpenFile(); ReadSortEle(in, deqFreqRecord); MergeFrequent(deqFreqRecord); return 0; } |
合并过程如图
代码如下

// WordFrequentMerge.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <algorithm> #include <iostream> #include <unordered_map> #include <deque> #include <vector> #include <sstream> #include <fstream> using namespace std; #define DEFAULT_FILE_NAME "0bucket.txt" #define BUCKET_NUM 10 std::ifstream in[BUCKET_NUM]; std::deque<std::pair<int, std::string>> deqFreqRecord[BUCKET_NUM]; void OpenFile() { std::string fileName = "0bucket.txt"; for (int i = 0; i < BUCKET_NUM; i++) { in[i].close(); in[i].open(fileName); if (in[i].bad() || !in[i]) { std::cerr << "open file error.exit!!" << std::endl; return; } fileName[0]++; } } void ReadSortEle_(std::ifstream& in, std::deque<std::pair<int, std::string>>& d) { std::string line,word; int freq; try { while (getline(in, line)) { std::string::size_type pos = line.find('\t'); if (pos != std::string::npos) { freq = std::stoi(line.substr(0, pos)); word = line.substr(pos + 1); //std::cout << word << "\t" << freq << std::endl; d.push_back(std::make_pair(freq, word)); } } } catch (std::exception& e) { std::cout << e.what() << std::endl; return; } std::sort(d.begin(),d.end(), [](const std::pair<int, std::string>& lhs, const std::pair<int, std::string>& rhs) { return lhs.first > rhs.first; }); } void ReadSortEle(std::ifstream in[], std::deque<std::pair<int, std::string>> d[]) { for (int i = 0; i < BUCKET_NUM; i++) { ReadSortEle_(in[i],d[i]); } } bool HeapCompareFunc(std::pair<int, std::string>& l, std::pair<int, std::string>& r) { return l.first < r.first; } void MergeFrequent(std::deque<std::pair<int, std::string>> d[]) { vector<std::pair<int, std::string>> vecHeap; for (int i = 0; i < BUCKET_NUM; i++) { if (!d[i].empty()) { vecHeap.push_back(d[i].front()); d[i].pop_front(); } } std::make_heap(vecHeap.begin(), vecHeap.end(), HeapCompareFunc); while (!vecHeap.empty()) { std::pop_heap(vecHeap.begin(), vecHeap.end(), HeapCompareFunc); std::pair<int, std::string> p = vecHeap.back(); vecHeap.pop_back(); std::cout << p.second << "\t" << p.first << std::endl; int idx = std::hash<string>()(p.second) % BUCKET_NUM; if (!d[idx].empty()) { vecHeap.push_back(d[idx].front()); std::push_heap(vecHeap.begin(), vecHeap.end(), HeapCompareFunc); d[idx].pop_front(); } } } int main() { OpenFile(); ReadSortEle(in, deqFreqRecord); MergeFrequent(deqFreqRecord); return 0; }
代码还有诸多待优化地方
比如读入单词后 另开线程处理而不是等待处理完毕后再读取单词
比如数据的传递 考虑传递指针和起始位置 而不是传递拷贝
作 者: itdef
欢迎转帖 请保持文本完整并注明出处
技术博客 http://www.cnblogs.com/itdef/
B站算法视频题解
https://space.bilibili.com/18508846
qq 151435887
gitee https://gitee.com/def/
欢迎c c++ 算法爱好者 windows驱动爱好者 服务器程序员沟通交流
如果觉得不错,欢迎点赞,你的鼓励就是我的动力
欢迎转帖 请保持文本完整并注明出处
技术博客 http://www.cnblogs.com/itdef/
B站算法视频题解
https://space.bilibili.com/18508846
qq 151435887
gitee https://gitee.com/def/
欢迎c c++ 算法爱好者 windows驱动爱好者 服务器程序员沟通交流
如果觉得不错,欢迎点赞,你的鼓励就是我的动力


【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
2015-02-20 监控系统 内存占用率并记录于本地文件中