├── .gitattributes ├── .gitignore ├── README.md ├── get_similar_words.py ├── most_common_ip.py ├── n_largest_num.py ├── unique_int.py └── url_in_two_file.py /.gitattributes: -------------------------------------------------------------------------------- 1 | 2 | # Auto detect text files and perform LF normalization 3 | * text=auto 4 | 5 | # Custom for Visual Studio 6 | *.cs diff=csharp 7 | *.sln merge=union 8 | *.csproj merge=union 9 | *.vbproj merge=union 10 | *.fsproj merge=union 11 | *.dbproj merge=union 12 | 13 | # Standard to msysgit 14 | *.doc diff=astextplain 15 | *.DOC diff=astextplain 16 | *.docx diff=astextplain 17 | *.DOCX diff=astextplain 18 | *.dot diff=astextplain 19 | *.DOT diff=astextplain 20 | *.pdf diff=astextplain 21 | *.PDF diff=astextplain 22 | *.rtf diff=astextplain 23 | *.RTF diff=astextplain 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # -python-BAT- 2 | 现在BAT笔试和面试非常容易出现大数据的问题。例如:对5亿个整数进行排序,并输出到文件中。有5亿个ip,找出出现次数最多的ip等等,这些问题的共同特点是数据量较大,并且常规的读入内存在处理行不通,或者即使内存足够大,采取暴力的方法行不通。 3 | 针对常见的BAT公司中的大数据面试和笔试问题,列出解决思路,并使用python来实现。 4 | 例子中的代码都是基于python2.7.10 5 | 6 | ##问题1:海量日志数据,提取出某日访问百度次数最多的那个IP。 7 | ##问题2:寻找热门查询,300万个查询字符串中统计最热门的10个查询。 8 | ##问题3:有一个1G大小的一个文件,里面每一行是一个词,词的大小不超过16字节,内存限制大小是1M。返回频数最高的100个词。 9 | ##问题4:海量数据分布在100台电脑中,想个办法高效统计出这批数据的TOP10。 10 | ##问题5:有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排##序。 11 | ##问题6:给定a、b两个文件,各存放50亿个url,每个url各占64字节,内存限制是4G,让你找出a、b文件共同的url? 12 | ##问题7:在2.5亿个整数中找出不重复的整数,注,内存不足以容纳这2.5亿个整数。 13 | ##问题8: 100w个数中找出最大的100个数。 14 | -------------------------------------------------------------------------------- /get_similar_words.py: -------------------------------------------------------------------------------- 1 | # 问题描述:对于一个文本集合,把相似的单词进行归类。这里这样定义相似单词:两个单词只有一个字母不一样! 2 | 方法一:对于这个问题,我们最开始使用的是暴力穷举办法。遍历文本中的每个单词,找出在文本中与其相似的单词, 3 | 算法的时间复杂度是o(n2), 4 | 对于常见的英文词典,差不多有将近20000万个单词,那么需要经过4亿次运算,时间惊人,在实际中不可能行得通。 5 | 当然这里还是有一个trick,因为相似单词总是长度一样的,所以你也许可以少许多计算。(当然这不能从根本上改变大局) 6 | 7 | 方法二: 从相似单词的特点入手。‘son’和‘sun’都可以用正则表达式中的‘s.n’来表示,其中.在正则表达式中可以代表任意的符号 8 | 我们使用一个一个字典结构:key是正则字符串 value:是相似单词的集合。举个例子:对于单词'son',那么符合它的正则匹配有 9 | ‘.on’,'s.n','so.',那么字典中,分别是:key:'.on',value:'son',key:'s.n',value:'son',key:'so.',value:'son',对于单词‘sun’, 10 | 进行同样的计算,同时字典开始更新:key:'.un',value:'sun',key:'s.n',value:['son','sun'],key:'so.',value:'son' 11 | key:'su.',value:'sun'。这样遍历文本,最后的时间复杂度是o(n): 12 | 13 | 代码: 14 | from collections import defaultdict #使用了默认字典 15 | words_dict = defaultdict(set) #词典的value值默认为set(非重复的相似单词集合,例如‘son’和‘sun’) 16 | def cal_similar_words(word): 17 | if len(word)!=0: 18 | for item in word: 19 | pattern = word.replace(item,'.') 20 | words_dict[pattern].add(word) 21 | words_list = [] #文本单词集合 22 | with open(r'D:\programesoftware\NLTK\corpora\abc\science.txt') as file: #为了方便,我们读入一个txt文件,可以认为包含了 23 | words_list = re.findall(r'\w+',file.read()) #所有常见的单词 24 | for item in set(words_list): 25 | cal_similar_words(item.lower()) 26 | 27 | -------------------------------------------------------------------------------- /most_common_ip.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 海量日志数据,提取出某日访问百度次数最多的那个IP 4 | 5 | 6 | """问题一:海量日志数据,提取出某日访问百度次数最多的那个IP 7 | 解决思路:因为问题中提到了是海量数据,所以我们想把所有的日志数据读入内存,再去排序,找到出现次数最多的,显然行不通了。这里 8 | 我们假设内存足够,我们可以仅仅只用几行代码,就可以求出最终的结果""": 9 | 代码如下: 10 | #python2.7 11 | from collections import Counter 12 | if __name__ == '__main__': 13 | ip_list = read_log() #读取日志到列表中,这里为了简化,我们用一个小的列表来代替。 14 | ip_list = ["192.168.1.2","192.168.1.3","192.168.1.3","192.168.1.4","192.168.1.2"] 15 | ip_counter = Counter(ip_list) #使用python内置的计数函数,进行统计 16 | #print ip_counter.most_common() Out:[('192.168.1.3', 2), ('192.168.1.2', 2), ('192.168.1.4', 1)] 17 | print ip_counter.most_common()[0][0] #out:192.168.1.3 18 | 在内存足够的情况下,我们可以看到仅仅使用了5、6行代码就解决了这个问题 19 | """ 20 | 下面才是我们的重点,加入内存有限,不足以装得下所有的日志数据,应该怎么办? 21 | 既然内存都不能装得下所有数据,那么我们后面的使用排序算法都将无从谈起,这里我们采取大而化小的做法。 22 | 假设海量的数据的大小是100G,我们的可用内存是1G.我们可以把数据分成1000份(这里只要大于100都是可以的),每次内存读入100M 23 | 再去处理。但是问题的关键是怎么将这100G数据分成1000分呢。这里我们以前学过的hash函数就派上用场了。 24 | Hash函数的定义:对于输入的字符串,返回一个固定长度的整数,hash函数的巧妙之处在于对于相同的字符串,那么经过hash计算, 25 | 得出来的结果肯定是相同的,不同的值,经过hash,结果可能相同(这种可能性一般都很小)或者不同。那么有了hash函数, 26 | 那么这道题就豁然开朗了,思路如下: 27 | 1.对于海量数据中的每一个ip,使用hash函数计算hash(ip)%1000,输出到1000个文件中 28 | 2.对于这1000个文件,分别找出出现最多的ip。这里就可以用上面提到的Counter类的most_common()方法了(这里方法很多,不一一列举) 29 | 3.使用外部排序,对找出来的1000个ip在进行排序。(这里数据量小,神马排序方法都行,影响不大) 30 | 代码如下:可以直接运行 31 | 32 | import os 33 | import heapq 34 | import operator 35 | from collections import Counter 36 | source_file = 'C:/Users/Administrator/Desktop/most_ip/bigdata.txt' #原始的海量数据ip 37 | temp_files = 'C:/Users/Administrator/Desktop/most_ip/temp/' #把经过hash映射过后的数据存到相应的文件中 38 | top_1000ip = [] #存放1000个文件的出现频率最高的ip和出现的次数 39 | def hash_file(): 40 | """ 41 | this function is map a query to a new file 42 | """ 43 | temp_path_list = [] 44 | if not os.path.exists(temp_files): 45 | os.makedirs(temp_files) 46 | for i in range(0,1000): 47 | temp_path_list.append(open(temp_files+str(i)+'.txt',mode='w')) 48 | with open(source_file) as f: 49 | for line in f: 50 | temp_path_list[hash(str(line))%1000].write(line) 51 | #print hash(line)%1000 52 | print line 53 | for i in range(1000): 54 | temp_path_list[i].close() 55 | def cal_query_frequency(): 56 | for root,dirs,files in os.walk(temp_files): 57 | for file in files: 58 | real_path = os.path.join(root,file) 59 | ip_list = [] 60 | with open(real_path) as f: 61 | for line in f: 62 | ip_list.append(line.replace('\n','')) 63 | try: 64 | top_1000ip.append(Counter(ip_list).most_common()[0]) 65 | except: 66 | pass 67 | print top_1000ip 68 | def get_ip(): 69 | return (sorted(top_1000ip,key = lambda a:a[1],reverse=True)[0])[0] 70 | if __name__ == '__main__': 71 | hash_file() 72 | cal_query_frequency() 73 | print(get_ip()) 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /n_largest_num.py: -------------------------------------------------------------------------------- 1 | """ 2 | 问题8: 100w个数中找出最大的100个数。时间复杂度尽可能的小 3 | 方案1:采用局部淘汰法。选取前100个元素,并排序,记为序列L。然后一次扫描剩余的元素x,与排好序的100个元素中最小的元素比, 4 | 如果比这个最小的要大,那么把这个最小的元素删除,并把x利用插入排序的思想,插入到序列L中。 5 | 依次循环,知道扫描了所有的元素。复杂度为O(100w*100)。 6 | 方案2:采用快速排序的思想,每次分割之后只考虑比轴大的一部分,知道比轴大的一部分在比100多的时候,采用传统排序算法排序, 7 | 取前100个。复杂度为O(100w*100)。 8 | 方案3:在前面的题中,我们已经提到了,用一个含100个元素的最小堆完成。复杂度为O(100w*lg100)。 9 | """ 10 | 代码实现如下: 11 | import heapq #引入堆模块 12 | import random #产生随机数 13 | test_list = [] #测试列表 14 | for i in range(1000000): #产生100w个数,每个数在【0,1000w】之间 15 | test_list.append(random.random()*100000000) 16 | heapq.nlagest(10,test_list) #求100w个数最大的10个数 17 | heapq.nsmallest(10,test_list) #求100w个数最小的10个数 18 | -------------------------------------------------------------------------------- /unique_int.py: -------------------------------------------------------------------------------- 1 | 问题7:在2.5亿个整数中找出不重复的整数,注,内存不足以容纳这2.5亿个整数。 2 | 3 | 首先我们考虑在内存充足的情况下,我们可以使用python中的字典结构。对2.5亿个数中的每一个数,出现一次,字典对应的值+1. 4 | 最后遍历字典,找出value为1的所有key。代码很简单,10行都不到。 5 | 6 | 内存不充足的话,我们可以有两种解决方案。 7 | (1):假设内存有(2.5*(10**8)*2)/8*(10**9) = 0.06G。那么我们可以使用bit数组,下面我详细解释一下上面内存的计算过程: 8 | 因为数据可能存在的方式只有三种:没出现过,出现了一次,出现了很多次。所以我们用2bit表示这三种状态。另外数据有2.5亿条, 9 | 总计需要内存2.5亿*2 bit,约等于0.6G。算法的过程大致如下: 10 | """ 11 | 1: 初始化一个(2.5*10^8) * 2 bool数组,并且初始化为False,对于每一个整数,使用 12 | 2个bit来表示它出现的次数: 0 0:出现0次 0 1:出现一次 1 1:出现多次 13 | 2: 遍历文件,当数据是第一次出现的时候,,更改属于它的两个bit状态:从 00变成01 14 | 3: 最后遍历文件找出bit为01的数字,那么这个数字只出现了一次 15 | """ 16 | 17 | from collections import defaultdict 18 | import numpy as np 19 | mark =np.zeros((2.5*(10**8),2),dtype=np.bool) #初始化一个(2.5*10^8) * 2 bool数组,并且初始化为False,对于每一个整数,使用 20 | 两个bit来表示它出现的次数: 0 0:出现0次 0 1:出现一次 1 1:出现多次 21 | def get_unique_int(): 22 | with open('bigdata') as file: #bigdata:原始的2.5亿个整数大文件 23 | for num in file: 24 | if mark[num][0] == False and mark[num][1] == False: #这个数第一次出现。那么更改属于它的2个bit 25 | mark[num][0] = True 26 | mark[num][1] = False 27 | else: 28 | mark[num][0] = True #出现了不止一次的数据,那么同意赋值 1 1 29 | mark[num][1] = True 30 | with open('bigdata') as file: #bigdata:原始的2.5亿个整数大文件 31 | for num in file: 32 | if mark[num][0] == True and mark[num][1] == False: 33 | yield num 34 | 35 | 36 | if __name__ == '__main__': 37 | unique_list = get_unique_int() #返回一个不重复整数的迭代器 38 | 39 | -------------------------------------------------------------------------------- /url_in_two_file.py: -------------------------------------------------------------------------------- 1 | """ 2 | 问题描述: 3 | 给你A,B两个文件,各存放50亿条URL,每条URL占用64字节,内存限制是16G,让你找出A,B文件共同的URL。如果是三个乃至n个文件呢? 4 | """ 5 | 1.常规的解决办法,也是最容易想到的,就是对于文件A,读入内存,对于文件B中的每一个元素,判断是否在A中出现过。 6 | 我们来分析一下这样做的空间和时间复杂度:第一步,读入文件到内存,需要的内存是(50*(10**8)*64)= 320G内存,显然 7 | 我们在实际中没有那么大的内存。另外通过遍历A文件和B文件中的每一个元素,需要的时间复杂度是o(M*N),M,N是两个 8 | 文件元素的大小,时间复杂度是(50亿*50亿)。。。。。。这是一个悲伤的算法 9 | 10 | 2.使用bloom过滤器。关于bloom过滤器,介绍它的文章太多了,稍微有点数学基础,都应该可以明白它的大致意思。 11 | 用一句话总结bloom过滤器就是:在需要查找,或者查重的场合,我们使用bloom过滤器能够使我们的搜索时间维持在o(1)的水平, 12 | 而不用去考虑文件的规模,另外它的空间复杂度也维持在一个可观的水平,但是它的缺陷是存在误报的情况,具体来说就是, 13 | 假如你要验证文件中是否存在某个元素,经过bloom过滤器,告诉你的结果是元素已经存在,那么真实的结果可能元素在文件中并不存在, 14 | 但是如果bloom过滤器告诉你的结果是不存在,那么文件中肯定不存在这个元素。下面具体分析问题: 15 | """ 16 | 对于A中50亿个文件,我们使用一个误报率为1%的bloom过滤器,那么经过计算(可以参考bloom的分析过程,里面有结论),每个元素 17 | 需要使用9.6bits,总计需要(50*(10**8)*9.6)bits = 6G,在内存的使用上,是符合我们要求的,然后对于使用A文件建立的bloom 18 | 过滤器,我们遍历B中的每一个元素,判断是否在A中出现过。 19 | 我使用了python的 pybloom模块,帮我们实现了bloom的功能。 20 | 代码在python2.7.10下测试通过 21 | 只用了9行代码 22 | """ 23 | from pybloom import BloomFilter #pip install pybloom 24 | bloom_A_file = BloomFilter(capacity = 5000000000, error_rate=0.01) #生成一个容量为50亿个元素,错误率为1%的bloom过滤器, 25 | #这里需要估摸一下自己电脑的可用内存,至少保持电脑的可用内存在8G以上, 26 | #否则死机不要找我。哈哈 27 | with open(file_A) as f1: #遍历A文件中的每一个元素,加入到bloom过滤器中 28 | for sel in f1: 29 | bloom_A_file.add(sel) 30 | with open(file_B) as f2: #遍历B文件,找出在A文件中出现的元素,并打印出来 31 | for sel in f2: 32 | if sel in bloom_A_file: 33 | print sel 34 | 35 | 36 | --------------------------------------------------------------------------------