本文用于自己学习
部分内容来自网络
数据分析面试题解析
问题描述:对于一个文本集合,把相似的单词进行归类。这里这样定义相似单词:两个单词只有一个字母不一样!
解析:找到两个单词之中只有一个字母不一样的单词
1.暴力读取
方法一:对于这个问题,我们最开始使用的是暴力穷举办法。遍历文本中的每个单词,找出在文本中与其相似的单词,算法的时间复杂度是o(n**2),对于常见的英文词典,差不多有将近20000个单词,那么需要经过4亿次运算,时间惊人,在实际中不可能行得通。当然这里还是有一个trick,因为相似单词总是长度一样的,所以你也许可以少许多计算。
2.使用正则的方式找到只有一个字母不同的单词(使用正则的思想进行查找)
方法二: 从相似单词的特点入手。‘son’和‘sun’都可以用正则表达式中的‘s.n’来表示,其中.在正则表达式中可以代表任意的符号
我们使用一个一个字典结构:key是正则字符串 value:是相似单词的集合。举个例子:对于单词’son’,那么符合它的正则匹配有‘.on’,‘s.n’,‘so.’,那么字典中,分别是:key:’.on’,value:‘son’,key:‘s.n’,value:‘son’,key:‘so.’,value:‘son’,对于单词‘sun’,进行同样的计算,同时字典开始更新:key:’.un’,value:‘sun’,key:‘s.n’,value:[‘son’,‘sun’],key:‘so.’,value:‘son’
key:‘su.’,value:‘sun’。这样遍历文本,最后的时间复杂度是o(n):
```python
import re
from collections import defaultdict #使用了默认字典
words_dict = defaultdict(set) #词典的value值默认为set(非重复的相似单词集合,例如‘son’和‘sun’)
def cal_similar_words(word):
if len(word)!=0:
for item in word:
# 通过每一次的循环替换 获得各个相似单词的组
pattern = word.replace(item,'.')
# 将各个相似组中的元素 添加到字典中
words_dict[pattern].add(word)
words_list = [] #文本单词集合
with open(r'D:\1.txt') as file: #为了方便,我们读入一个txt文件,可以认为包含了
words_list = re.findall(r'\w+',file.read()) #所有常见的单词
for item in set(words_list):
cal_similar_words(item.lower())
print(item)
海量日志数据,提取出某日访问百度次数最多的那个IP
1.暴力读取将所有的数据都进行读取 排序次数
解决思路:因为问题中提到了是海量数据,所以我们想把所有的日志数据读入内存,再去排序,找到出现次数最多的,显然行不通了。这里我们假设内存足够,我们可以仅仅只用几行代码,就可以求出最终的结果
```python
from collections import Counter
if __name__ == '__main__':
ip_list = ["192.168.1.3","192.168.1.2","192.168.1.3","192.168.1.3","192.168.1.4","192.168.1.2"]
ip_counter = Counter(ip_list) #使用python内置的计数函数,进行统计
# 按照出现次数的顺序进行排列
print(ip_counter.most_common())
# 出现最多的次数
print(ip_counter.most_common()[0][0])
在内存足够的情况下,我们可以看到仅仅使用了5、6行代码就解决了这个问题
大文件一定要进行分块处理
下面才是我们的重点,假如内存有限,不足以装得下所有的日志数据,应该怎么办?
既然内存都不能装得下所有数据,那么我们后面的使用排序算法都将无从谈起,这里我们采取大而化小的做法。
假设海量的数据的大小是100G,我们的可用内存是1G.我们可以把数据分成1000份(这里只要大于100都是可以的),每次内存读入100M再去处理。但是问题的关键是怎么将这100G数据分成1000分呢。这里我们以前学过的hash函数就派上用场了。
Hash函数的定义:对于输入的字符串,返回一个固定长度的整数,hash函数的巧妙之处在于对于相同的字符串,那么经过hash计算,得出来的结果肯定是相同的,不同的值,经过hash,结果可能相同(这种可能性一般都很小)或者不同。那么有了hash函数
思路如下:
1.对于海量数据中的每一个ip,使用hash函数计算hash(ip)%1000,输出到1000个文件中
2.对于这1000个文件,分别找出出现最多的ip。这里就可以用上面提到的Counter类的most_common()方法了
3.使用外部排序,对找出来的1000个ip在进行排序。(这里数据量小,神马排序方法都行,影响不大)
代码如下:可以直接运行
import os
import heapq
import operator
from collections import Counter
source_file = 'D:/1.txt' #原始的海量数据ip
temp_files = 'D:/TEX' #把经过hash映射过后的数据存到相应的文件中
top_1000ip = [] #存放1000个文件的出现频率最高的ip和出现的次数
#hash函数hash出的值是唯一的 不会重复 每一个唯一的字符 hash出的值也是唯一的
def hash_file():
"""
this function is map a query to a new file
"""
temp_path_list = []
# 如果不存在这个文件夹就创建
if not os.path.exists(temp_files):
os.makedirs(temp_files)
#
for i in range(0,1000):
# 将文件夹下面的所有文件读出添加到列表中
temp_path_list.append(open(temp_files+str(i)+'.txt',mode='w'))
with open(source_file) as f:
# 读出海量的数据
# 相当于调用readline 就是每一次读一行
for line in f:
# 将这海量的数据分成1000分存入列表中
temp_path_list[hash(str(line))%1000].write(line)
# print(hash(line)%1000)
print(line)
for i in range(1000):
temp_path_list[i].close()
def cal_query_frequency():
for root,dirs,files in os.walk(temp_files):
for file in files:
real_path = os.path.join(root,file)
ip_list = []
with open(real_path) as f:
for line in f:
ip_list.append(line.replace('\n',''))
try:
top_1000ip.append(Counter(ip_list).most_common()[0])
except:
pass
print(top_1000ip)
def get_ip():
return (sorted(top_1000ip,key = lambda a:a[1],reverse=True)[0])[0]
if __name__ == '__main__':
hash_file()
cal_query_frequency()
print(get_ip())
问题8: 100w个数中找出最大的100个数。时间复杂度尽可能的小
方案1:采用局部淘汰法。选取前100个元素,并排序,记为序列L。然后每次取出剩余的元素集合中的一个元素,与排好序的100个元素中最小的元素比,
如果比这个最小的要大,那么把这个最小的元素删除,并把x利用插入排序的思想,插入到序列L中。
依次循环,直到扫描了所有的元素。复杂度为O(100w*100)。
方案2:采用快速排序的思想,每次分割之后只考虑比轴大的一部分,直到比轴大的一部分在比100多的时候,采用传统排序算法排序,
取前100个。复杂度为O(100w*100)。
方案3:在前面的题中,我们已经提到了,用一个含100个元素的最小堆完成。复杂度为O(100w*lg100)。
代码实现如下:
使用堆排序
堆排序 稳定的排序算法 使用数组进行存储 不需要其他的储存空间 时间复杂度 log2n 比如16个数 最坏的情况需要比较三次 2**3 适合用于 数据量比较大的数据
import heapq #引入堆模块
import random #产生随机数
test_list = [] #测试列表
for i in range(1000000): #产生100w个数,每个数在【0,1000w】之间
test_list.append(random.random()*100000000)
print(heapq.nlargest(100,test_list)) #求100w个数最大的100个数
print(heapq.nsmallest(100,test_list)) #求100w个数最小的100个数
在2.5亿个整数中找出不重复的整数
1.暴力查找
首先我们考虑在内存充足的情况下,我们可以使用python中的字典结构。对2.5亿个数中的每一个数,出现一次,字典对应的值+1.
最后遍历字典,找出value为1的所有key。代码很简单,10行都不到。
内存不充足的话使用二进制的方式
(1):假设内存有(2.5(108)2)/8*(109) = 0.06G。那么我们可以使用bit数组,下面我详细解释一下上面内存的计算过程:
因为数据可能存在的方式只有三种:没出现过,出现了一次,出现了很多次。所以我们用2bit表示这三种状态。另外数据有2.5亿条,
总计需要内存2.5亿*2 bit,约等于0.6G。算法的过程大致如下:
1: 初始化一个(2.5*10^8) * 2 bool数组,并且初始化为False,对于每一个整数,使用
2个bit来表示它出现的次数: 0 0:出现0次 0 1:出现一次 1 1:出现多次
2: 遍历文件,当数据是第一次出现的时候,,更改属于它的两个bit状态:从 00变成01
3: 最后遍历文件找出bit为01的数字,那么这个数字只出现了一次
from collections import defaultdict
import numpy as np
mark =np.zeros((2.5*(10**8),2),dtype=np.bool) #初始化一个(2.5*10^8) * 2 bool数组,并且初始化为False,对于每一个整数,使用
#两个bit来表示它出现的次数: 0 0:出现0次 0 1:出现一次 1 1:出现多次
def get_unique_int():
with open('bigdata') as file: #bigdata:原始的2.5亿个整数大文件
for num in file:
if mark[num][0] == False and mark[num][1] == False: #这个数第一次出现。那么更改属于它的2个bit
mark[num][0] = True
mark[num][1] = False
else:
mark[num][0] = True #出现了不止一次的数据,那么同意赋值 1 1
mark[num][1] = True
with open('bigdata') as file: #bigdata:原始的2.5亿个整数大文件
for num in file:
if mark[num][0] == True and mark[num][1] == False:
# 在需要的时候在读出
yield num
问题描述:
给你A,B两个文件,各存放50亿条URL,每条URL占用64字节,内存限制是16G,让你找出A,B文件共同的URL。如果是三个乃至n个文件呢?
1.常规的解决办法,也是最容易想到的,就是对于文件A,读入内存,对于文件B中的每一个元素,判断是否在A中出现过。
我们来分析一下这样做的空间和时间复杂度:第一步,读入文件到内存,需要的内存是(50(10**8)64)= 320G内存,显然
我们在实际中没有那么大的内存。另外通过遍历A文件和B文件中的每一个元素,需要的时间复杂度是o(M*N),M,N是两个
文件元素的大小,时间复杂度是(50亿*50亿
2.使用bloom过滤器。关于bloom过滤器,介绍它的文章太多了,稍微有点数学基础,都应该可以明白它的大致意思。
用一句话总结bloom过滤器就是:在需要查找,或者查重的场合,我们使用bloom过滤器能够使我们的搜索时间维持在o(1)的水平,
而不用去考虑文件的规模,另外它的空间复杂度也维持在一个可观的水平,但是它的缺陷是存在误报的情况,具体来说就是,
假如你要验证文件中是否存在某个元素,经过bloom过滤器,告诉你的结果是元素已经存在,那么真实的结果可能元素在文件中并不存在,
但是如果bloom过滤器告诉你的结果是不存在,那么文件中肯定不存在这个元素。下面具体分析问题:
from pybloom_live import BloomFilter #pip install pybloom
bloom_A_file = BloomFilter(capacity = 500, error_rate=0.01) #生成一个容量为50亿个元素,错误率为1%的bloom过滤器,
#这里需要估摸一下自己电脑的可用内存,至少保持电脑的可用内存在8G以上,
#否则死机不要找我。哈哈
with open(file_A) as f1: #遍历A文件中的每一个元素,加入到bloom过滤器中
for sel in f1:
bloom_A_file.add(sel)
with open(file_B) as f2: #遍历B文件,找出在A文件中出现的元素,并打印出来
for sel in f2:
if sel in bloom_A_file:
print(sel)
布隆过滤器的用处 大数据 网址去重 使用多个 hash算法 将一个网址进行hash 将这些hash的值 放入到一个类似数组中 值为1 当进行对这个网址查找的时候 如果 数组中所有关于这个网址的都为1 那么这个网址 就已经出现过了 但是布隆过滤器 会出现误判率 原因:当将网址进行hash的时候 会将hash值均匀的放入到 数组中 当数组的容量大 而 hash的值 比较少的时候 就不容易进行误判 但是 当hash的值 数量大到一定程度的时候 就会发生误判的情况
来源:CSDN
作者:鱼天天
链接:https://blog.csdn.net/qq_40433141/article/details/104268811