"Giventwo log files, each with a billion usernames (each username appendedto the log file), find the usernames existing in both documents inthe most efficient manner. Use c/c++ code. If your code callspre-existing library functions, create each library function fromscratch. "


Requirement:C/C++.


Submission:Source code and a document to describe your solution.


上面是之前应聘一家公司的研发岗位时,遇到的一道题目,不知道自己做的对不对,觉得挺有趣的,在这里总结一下思路跟代码。

当时看到这个题目的第一个反应是使用哈希表解决,

决定使用哈希表后,便开始构思如何创建一个简单而高效的哈希表,思路是这样的,因为每个文件的用户名大约是一亿个,为每个用户申请空间来存放ta对应的字符串是不可能的,但是我们可以这样,先申请一块连续的空间,然后将该空间上的每一位都置0。比如我们申请有1000位的连续空间,上面的每一位都置0,然后1000位大约也就125字节,每个字节有8位啊。就是这样的,因为有大约一亿个用户,我们可以申请1×10位的空间,也就差不多12.5M的空间,将该空间作为哈希表的存储空间(源文件中是hashMap)。

接下来,我们将该空间上的每一位清零(源文件中对应的函数是clr()),然后读取第一个文件,将每个用户名映射成一个具体的数字作为哈希码,接着把哈希表上跟该数字对应的位上的值置1(源文件中对应的函数是set()),就这样,读取完第一个文件的同时也将该文件上所有用户名对应的哈希空间位置1(无论是否重复,我们都置1)。

然后,我们读取第二个文件,同样的,先根据用户名生成相应的哈希码,然后呢,我们根据哈希码查询哈希表上对应的位是否是1,如果是的话,就说明该用户名在第一个文件出现过,将ta打印到屏幕,然后继续读取用户名,直至读取完文件。

然后呢,然后就没有然后啦,哈哈。这就是整个源文件的总体框架。

刚才说完总体框架,接下来就说一下具体的细节,其实比较难的是如何根据用户名生成相应的哈希码。

在这里,我假设文件中的用户名是包括了字母和数字的字符串,然后我所要做的就是将用户名对应的字符串映射到一个确切的数字上,那么应该怎么做呢?

我想了很久,方案也是挺多的,考虑过将用户名中的每个字符跟A’偏移量相乘,然后生成相应的哈希码,不过这样子可能会造成不同的用户名生成相同的哈希码,所以抛弃了这个想法,也考虑过将每个字符跟A’的偏移量的平方相加,可是也还是可能出现之前的那种错误,想了许久之后决定使用这样的方案:

将用户名放在一个字符数组中,然后读取每个字符还有ta的下标,然后,如果字符是字母的话,就算出该字符跟A’的偏移量,然后跟其下标+1相乘,如果该字符是数字0-9的话,就算出该数字,然后用该数字跟其下标+1相乘,最后将每个字符所得的乘积相加,就形成了该用户名的哈希码。在源文件中对应的函数是hash_code()。


好了,整个方案的大概思路就是这样,框架跟细节都写出来了,源文件的编译没有问题,但是没有用数据进行进行测试

#include "stdio.h"
#include "string.h"
#include "stdlib.h"
#define BITSPERWORD 32  //数组元素对应的位数
#define N 100000000     //要申请空间的位数
                                                                                   
int hashMap[1 + N/BITSPERWORD];//哈希码数组
                                                                                   
//set 设置i对应的bit位为1 
//clr 初始化所有的bit位为0 
//test 测试i对应的bit为是否为1 
void set(int i) {        hashMap[i/32] |=  (1<<(i % 32)); } 
void clr() {memset(hashMap,0,sizeof(hashMap) * (1 + N/BITSPERWORD));}
int  test(int i){ return hashMap[i/32] &   (1<<(i %32 )); }
//hahs_code 用于生成用户名的哈希码
//mapFile用于将第一个文件中的用户名映射到hash数组
//findUser用于查找同时存在于两个文件的用户名,并显示到屏幕
void hash_code(char *src, int *hash);
void mapFile(char *fileName);
void findUser(char*fileName);
void main()
{
    char filename1[40];                                                 //用于输入第一个文件名
    char filename2[40];                                                 //用于输入第二个文件名
    printf("请输入第一个文件名:");
    gets(filename1);
    mapFile(filename1);                                                 //将第一个文件的用户名映射到哈希表
    printf("请输入第二个文件名:");
    gets(filename2);
    findUser(filename2);                                                //查找并打印相同的用户名
    getcgar();
    return ;
}
void mapFile(char *fileName){
    FILE *fp;
    char str_user[80];                                                      //  用于存放临时用户名
    int hash = 0;                                                           //存放哈希码
    if((fp = fopen(fileName,"r")) == NULL){                                 //打开指定文件,如果文件不存在,退出
        printf("文件不存在!!!\n");                                  
        exit(0);
    }
    while((fscanf(fp,"%s", str_user) )!= EOF){                              //将用户名读入str_user
        hash_code(str_user, &hash);                                         //生成相应的哈希码
        set(hash);                                                          //将哈希码对应的位置1
        memset(str_user, '/0', sizeof(str_user));                           //清空str_user,进入下一个循环
                                                                                     
    }
    fclose(fp);                                                             //关闭文件
}
void findUser(char*fileName){
    FILE *fp;
    char str_user[80];                                                      //  用于存放临时用户名
    int hash = 0;                                                           //用于存放哈希码
    if((fp = fopen(fileName,"r")) == NULL){                                 //打开文件,如果文件不存在,退出
        printf("文件不存在!!!\n");
        exit(0);
    }
    while((fscanf(fp,"%s", str_user) )!= EOF){                              //将用户名读入str_user
        hash_code(str_user, &hash);                                         //生成相应的哈希码
        if(1 == test(hash) ){                                               //如果哈希表中哈希码对应的位是1,则说明该用
            printf("该用户名同时存在于两个文件:%s\n", str_user);         //户名存在于之前的文件,将该用户名打印到屏幕
        }
        memset(str_user, '/0', sizeof(str_user));                           //清空str_user,进入下一个循环
                                                                                         
    }
    fclose(fp);
}
void hash_code(char *src, int *hash){                                       //src是用户名对应的字符数组,hash用来返回生成的哈希码
    char *p = src;
    int index1 = 0;                                                         //index1用来存放字符在数组中的位置
    int index2 = 0;                                                         //index2用来存放字符跟'A'的相对距离,或者跟'0'的相对距离,具体请看代码或参照文档
    int sum = 0;
    while(!p){
        index1 += 1;
        if((*p >= 'A'&& *p<= 'Z') || (*p >= 'a' && *p <= 'z')){
            index2  = *p - 'A';
            sum += index1 * index2;
        }
        else if(*p >= '0' && *p <= '9'){
            index2 = *p -'0';
            sum += index1 * index2;
        }
        *(++p);
    }
    *hash = sum;
    return ;
}



在完成这道题之后,把自己的解决方案发给了对方,很快就收到对方的邀请,讨论到具体待遇的时候觉得对方给出的条件没有达到自己的预期,最后拒绝了。自己觉得挺失望的,有时候觉得做开发真的有点廉价,是自己的期望太高吗?后来有点开始怀疑自己,呵呵