编程之法之海量数据处理:寻找Top K的热词
题目:
有100万个关键字,长度小于50个字节。用有效的算法找出Top 10的热词,要求对内存的占用不超过1 MB。
分析:
这是大家面试中都被问道的问题,可以直接使用map-reducer直接解决这个问题。如果不能使用这个架构,我们手动实现,也是采用的这个思想,只是这个用文件代替节点。
因为要求最多1MB内存,我们需要划分成50个小文件。划分的时候我们需要保证所有相同的字符串在同一个文件中,这样我们就可以单独统计每一个小文件,最后合并结果即可。当然这个文件大家都已经理解了,这里主要是从C++的代码进行实现。
首先我们明确需要几个函数:
-
划分函数,将相同的字符串划分到相同小文件中, 这里我们需要有一个hash函数,将每个字符串映射成整数,再对小文件个数(50)取余。
-
统计每个小文件中的热词,这里需要实现一个统计函数,我们可以基于最小堆进行实现。
-
合并所有小文件的结果的函数,合并函数。
-
由于我们没有数据,需要写一个随机函数,生出数据,生出数据的函数。
-
随机产生数据的函数
/** 随机产生200W个长度为10的字符串,写入文件中,字符[0-5] + 'a'*/
void randCreateData(const char* outputfile, const int cnt = 1000000) {
FILE *fp = NULL;
fp = fopen(outputfile, "w+");
if(fp == NULL) {
printf("File error\n");
return;
}
srand((unsigned)time(0));
for(int i = 0; i < cnt; i++) {
char tmp[12];
int j;
for(j = 0; j < 5; j++) {
tmp[j] = char(rand()%5 + 'a');
}
tmp[j] = '\0';
fprintf(fp, "%s\n", tmp);
}
fclose(fp);
}
- hash key函数
/** 得到hash(str) % MOD **/
int hash_key(const char* str) {
int value = 0;
while(*str != '\0') {
value = value * 31 + *str;
value = value % MOD;
str ++;
}
return value;;
}
- 分割函数
/** 将200w * 5B的文件,根据key划分成50个文件,每个文件100B,大约1M **/
void partation(const char* inputfile, int filecnt = 50) {
FILE * fps[filecnt];
for(int i = 0; i < filecnt; i++) {
char tmpfile[20] = "./data/tmpfile";
char s[3];
sprintf(s, "%d", i);
char *file = strcat(tmpfile, s);
// printf("%s\n", file);
fps[i] = fopen(file, "w+");
assert(fps[i] != NULL);
}
FILE *fp_read = fopen(inputfile, "r");
assert(fp_read != NULL);
int i = 0;
char buffer[10];
while(! feof(fp_read)) {
// 这样读取包含换行符
fgets(buffer, sizeof(buffer)-1 ,fp_read);
// 去掉换行符号
buffer[strlen(buffer)-1] = '\0';
// 得到字符串对应的key,写入对应的文件
int key = hash_key(buffer) % filecnt;
fprintf(fps[key], "%s\n", buffer);
}
// close 所有文件指针
for(int i = 0; i < filecnt; i++) {
fclose(fps[i]);
}
fclose(fp_read);
}
- 处理一个文件的函数
/** 统计单个文件中的出现的频率 **/
int preocessedOneFile(const char *inputfile, const int topK=100) {
map<string, int> s2cnt;
FILE *fp_read = fopen(inputfile, "r");
assert(fp_read != NULL);
int i = 0;
char buffer[10];
while(! feof(fp_read)) {
// 这样读取包含换行符
fgets(buffer, sizeof(buffer)-1 ,fp_read);
// 去掉换行符号
buffer[strlen(buffer)-1] = '\0';
string s = buffer;
s2cnt[s] += 1;
}
// cout<<s2cnt.size()<<endl;
for(auto it=s2cnt.begin(); it!=s2cnt.end(); it++) {
q.push(make_pair(it->first, it->second));
if(q.size() > topK) { // 保存最大的100个
q.pop();
}
}
/*
int k = 0;
while(q.size()) {
pair<string, int> t = q.top();
cout<<t.first<<" "<<t.second<<endl;
q.pop();
k ++;
if(k > 10) break;
}
*/
return s2cnt.size();
}
全部源码:github 源码