现在这份工作入职后,由于一些部门内的人事调动,一段时间内整个组里只有我和我老大两个员工,剩下的就是无穷无尽的面试了。由于技术面试至少2轮才能通过,所以第一面毫无疑问的落到了我的头上。
作为公司里级别最低的员工(仅仅高于实习生),我对面试别人这个事情是非常发怵的。问的太简单了,招进来的人不能干活;问的太难了,自己都不知道答案,更没法评论别人是否能够胜任了(就不说如果被别人问个问题自己不知道之后有多尴尬了)。后来,我找到了一个很讨巧的办法。
鉴于我们找人进来都是来干活的,我就把平时工作中遇到的问题拿出来抽象一下,然后找张纸给面试者。一面不需要关心你的离职原因,做人理念;也不关心你原来手底下有多少小弟,自己做的系统能支持多少并发;我只要过滤掉不能干活的人,剩下的交给我老大去评估好了。能干活的标准很简单:能解决问题,编码风格良好,有参数检查(诡异的是,90%的人都不会干这事);干活速度可接受。
这个系列里我会列出来一系列工作中遇到的“小问题”,基本都谈不到“算法”二字,最多算是“方法”。
首先介绍一个处理日志的小问题:
如果熟悉bash,一句话就可以了:
Cat access.log | awk '{print $2}' | uniq -c | sort -n -r | head -n N #N 就是你想要的个数了
如果你用php写,也是很轻松的(伪代码,没有参数检查等等):
<?php
$rst = array();
$file_handler = fopen('access.log', 'r');
if ($file_handler)
{
$ip_cnt = array();
$ln = fgets($file_handler);
while ($ln !== false)
{
$parts = explode(' ', $ln, 3);
$ip = $parts[2];
if (isset($ip_cnt[$ip]))
{
$ip_cnt[$ip] = $ip_cnt[$ip] + 1;
}
else
{
$ip_cnt[$ip] = 0;
}
$ln = fgets($file_handler);
}
$rst = arsort($ip_cnt);
}
$rst = array_slice($rst, 0, $n);
这里面有比较多的问题,比如排序的时候如果有n个ip访问次数是一样的会如何处理等等。去除这些不谈,最大的问题是它会把全部的ip访问次数排序,这样做其实是没有必要的,因为我们只需要前面n个,不需要全部按顺序排好。所以扣除那些用来迷惑人的日志格式处理,这个问题变成了:
也就等同于
看过《算法导论》前100页的人应该能看到(也就是看过算法导论的绝大部份人,惭愧),这就是一个快排的问题,完全没有必要把整个数组排序。
后面是一个使用python但是一点都不pythonic的实现,考虑了有次数相等的情况(为了简化,文件里一行就是一个ip地址):
#!/usr/bin/python
# -*- encoding=utf-8 -*-
""" """
import random
def get_top_n(items, cnt, cmp_func, pre_val):
lst_len = len(items)
if lst_len <= cnt:
pre_val = pre_val or []
pre_val.extend(items)
return pre_val
# get a pivot in qsort
pivot_idx = random.randrange(0, lst_len)
big_lst = [item for item in items
if (cmp_func(item, items[pivot_idx]) > 0)]
big_lst_len = len(big_lst)
if big_lst_len >= cnt:
return get_top_n(big_lst, cnt, cmp_func, pre_val)
else:
pre_val = pre_val or []
pre_val.extend(big_lst)
#equal cases
equal_lst = [item for item in items
if (cmp_func(item, items[pivot_idx]) == 0)]
pre_val.extend(equal_lst)
equal_lst_len = len(equal_lst)
if (big_lst_len + equal_lst_len) >= cnt:
return pre_val
sml_lst = [item for item in items
if (cmp_func(item, items[pivot_idx]) < 0)]
return get_top_n(sml_lst, (cnt - big_lst_len - equal_lst_len),
cmp_func, pre_val)
def ip_cmp_func(x, y):
if x[1] == y[1]:
return 0
return x[1] > y[1] and 1 or -1
if __name__ == '__main__':
f = open('ip_only.log', 'r')
ip_cnt_dict = {}
for line in f.xreadlines():
if not line:
continue
line = line[:-1]
cnt = ip_cnt_dict.get(line, 0)
#make a {ip: cnt} dict
ip_cnt_dict[line] = cnt + 1
f.close()
# make a (ip, cnt) list
ip_list = [(ip_addr, ip_cnt) for ip_addr, ip_cnt in ip_cnt_dict.iteritems()]
# get top 100 ips
rst = get_top_n(ip_list, 100, ip_cmp_func, None)
# sort the result
srt_rst = sorted(rst, key=lambda x: -x[1])
for ip_addr, pv in srt_rst:
print 'IP: %s\tPV: %s' %(ip_addr, pv)
还有几行测试文件
#!/usr/bin/python
# -*- encoding=utf-8 -*-
""" """
import random
if __name__ == '__main__':
for k in xrange(100000):
ip = '%s.%s.%s.%s' % (random.randrange(0, 255),
random.randrange(0, 255),
random.randrange(0, 255),
random.randrange(0, 255))
print ip
if (k % 100) == 0:
for k in range(k % 199):
print ip
可以用下面的命令生成一堆日志,然后测试下处理时间
chmod +x *.py
for cnt in $(seq 5); do ./mklog.py > ip_only.log; ./get_top_ip.py | nl;echo '\n'; done