• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
geniushuai
博客园    首页    新随笔    联系   管理    订阅  订阅

海量数据处理:找出现次数最多的那些(一点记录)

海量数据处理:找出现次数最多的那些...

默认分类 2010-08-24 16:56:07 阅读0 评论0  字号:大中小 订阅

回忆一下tx的二面,有一道题是这样的:

假设有1kw个身份证号,以及他们对应的数据。身份证号可能重复,要求找出出现次数最多的身份证号。
一个很显然的做法是,hash之,O(n)搞定。这前提是内存中可以存下。
如果是中国的13亿人口,内存中存不下呢?借用磁盘,多次扫描?磁盘IO的速度慢得能让你疯掉。
这时候你终于感受到,中国人口真TMD多阿。。。

--

然后再看看今天的astar复赛第一题: 

百 度每天都会接受数亿的查询请求, 如何在这么多的查询(Query)中找出高频的Query是一个不小的挑战. 而你的任务则更加艰巨, 你需要在极其有限的资源下来找出这些高频的Query.(使用内存不得多于1MB) 。输入文件是一行一个Query, 以文件结束符结尾。每个Query字节数L(一个汉字两个字节)满足:0<L<=16. 输入大小不超过1GB(包括换行符)。 输出你认为最高频的100个query. 每行一个, 不能有重复, 不能多输出, 但可以少输出(见样例). 

用1M的内存来统计1GB的东西,而且还只能读取一遍?也许你会以为出题人疯了,或者你自己直接疯了。别激动,想想nike的广告。

然后再仔细看看题目的要求。

引用
输出你认为最高频的100个query


注意!有三个字“你认为”。然后再看看 百度之星吧 里面astar2006的答疑,给了一个明确的信息:

这题要求的不是准确的答案,而是近似答案。

回头想想,其实当时在tx面试的时候就应该问清楚,需要的是精确的还是近似的答案。现在终于豁然开朗了。

---- 传说中的分割线,下面就不是废话了 ----

要求使用1MB以内的空间,在只有一遍扫描的情况下,尽可能找出出现次数在TOP100里面的Query。

于是很显然,hash是必须的了。

由于内存限制相当严格,所以hash表只能开到很小,比如,103(因为103是个素数)。

在扫描的过程中,为了限制内存的使用,你需要按照某种规则不断地替换掉一些entry。

在遍历完以后需要归并,因为要求TOP100,所以每个hash值对应的list应该要有100个元素(因为只要近似,所以并不需要很严格)。

然后你就发现,这个东西怎么那么像计算机组成原理里面的多路组相联Cache阿。。。

那替换的规则几乎就不用想了,找出出现次数最少的那个,直接掐掉。

于是整个算法框架就完整了。

------

测试:

随机生成长度为1~16的1,000,000个字符串,字符串仅包含小写字母。数据文件为8.6MB。

显然出现次数最多的应该是单字母串。如果你的结果不是这样,那属于RPWT。

程序平均运行时间: 1.04s (Ubuntu 9.04, g++ 4.3.3, T2410[2.0G/533MHz],开着n个进程,包括一个跑着QQ的虚拟的XP) 

程序内存占用:568KB (Gnome系统监视器数据)

程序统计结果:

1. 前26个结果为字母,顺序不定。

2. 看了下a~f的出现频率统计,仅有a和e的统计次数比出现次数少1,其他都完全一致。

这个结果比我想像中的还要NB。我被震撼了。

OVER。


代码(主程序 以及 数据生成代码):

p.s. 代码写得很烂,有些地方为了效率和内存占用妥协了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

const int hashm = 103;
inline int hash(char *p){
    int t = 0;
    while (*p){
        t = (*p++) + (t << 6) + (t << 16) - t;
    }
    return ((t & 0x7fffffff) % hashm);
}

struct node_t{
    char s[17];
    int count;
    node_t& operator =(const char *t){
        strcpy(s, t);
        count = 1;
        return (*this);
    }
};

struct great_node_t{
    char *s;
    int count;
    bool operator <(const great_node_t &a)const {
        return (count > a.count);
    }
    great_node_t(const char *_s, int &_count){
        s = (char *)_s;
        count = _count;
    }
};

node_t tbl[hashm][100];
int tblc[hashm];
vector<great_node_t> ans;

int main(){
    char t[20];
    int i, j;
    int v;
    //freopen("a.in", "r", stdin);
    for (i = 0; i < hashm; ++i){
        tblc[i] = 0;
    }
    while (scanf ("%s", t) == 1){
        v = hash(t);
        for (i = 0, j = 0; i < tblc[v]; ++i){
            if (strcmp(tbl[v][i].s, t) == 0){
                tbl[v][i].count++;
                break ;
            }
            if (tbl[v][i].count < tbl[v][j].count){
                j = i;
            }
        }
        if (i == tblc[v]){
            if (i < 99){
                tblc[v]++;
                tbl[v][i] = t;
            }else {
                tbl[v][j] = t;
            }
        }
    }
    for (i = 0; i < hashm; ++i){
        for (j = 0; j < tblc[i]; ++j){
            ans.push_back(great_node_t(tbl[i][j].s, tbl[i][j].count));
        }
    }
    sort(ans.begin(), ans.end());
    for (int i = 0; i < 100 && i < (int )ans.size(); ++i){
        printf ("(%d) %s\n", ans[i].count, ans[i].s);
    }
    return 0;
}
//数据生成代码
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

int main(){
    int i, j;
    srand(time(NULL ));
    const int maxline = 1000000;
    freopen("a.in", "w", stdout);
    for (i = 0; i < maxline; ++i){
        int t = rand() % 15 + 1;
        for (j = 0; j < t; ++j){
            printf ("%c", rand() % 26 + 'a');
        }
        printf ("\n");
    }
    return 0;
}
posted @ 2010-10-22 18:22  geniushuai  阅读(1310)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3