数据分析面试题解答

与世无争的帅哥 提交于 2020-02-12 00:27:46

本文用于自己学习
部分内容来自网络

数据分析面试题解析

问题描述:对于一个文本集合,把相似的单词进行归类。这里这样定义相似单词:两个单词只有一个字母不一样!

解析:找到两个单词之中只有一个字母不一样的单词

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的值  数量大到一定程度的时候 就会发生误判的情况
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!