PythonChallenge_2
一、实验说明
1. 环境登录
无需密码自动登录,系统用户名shiyanlou,密码shiyanlou
2. 环境介绍
本实验环境采用带桌面的Ubuntu Linux环境,实验中会用到桌面上的程序:
1. LX终端(LXTerminal): Linux命令行终端,打开后会进入Bash环境,可以使用Linux命令
2. Firefox:浏览器,可以用在需要前端界面的课程里,只需要打开环境里写的HTML/JS页面即可
3. GVim:非常好用的编辑器,最简单的用法可以参考课程[Vim编辑器]
3. 环境使用
使用GVim编辑器输入实验所需的代码及文件,使用LX终端(LXTerminal)运行所需命令进行操作。
完成实验后可以点击桌面上方的“实验截图”保存并分享实验结果到微博,向好友展示自己的学习进度。实验楼提供后台系统截图,可以真实有效证明您已经完成了实验。
实验记录页面可以在“我的主页”中查看,其中含有每次实验的截图及笔记,以及每次实验的有效学习时间(指的是在实验桌面内操作的时间,如果没有操作,系统会记录为发呆时间)。这些都是您学习的真实性证明。
二、课程介绍
本次`PythonChallenge`系列一共有11个项目,持续更新中~~~,每个项目课程里面会详细讲解3个`pythonchallenge`通关题目以及不同解决方案,课后习题里面布置一个任务题,该题目会在下一个项目中被揭开面纱。
本系列题目属于在线闯关题,由于实验楼暂时不提供访问外网,因此请各位验证答案的时候烦劳点击自己的浏览器访问网页以验证答案。
所有题目和参考的解决方案版权归[PythonChallenge官网],课程编写属于实验楼原创,欢迎在问答区积极提问,小编会积极解答,也欢迎在评论区吐槽~
三、实验回顾
在上一个[项目课]中,我们学到了很多知识,比如:`string`模块中的一些函数,字典的使用以及函数`lambda`。记性不算太差的你们估计还记得在上一次的课程中小编有留给大家一个课后作业,不知道你们完成的怎样呢?
不管是否得到答案,让小编带着你一起解决这个问题吧!
四、作业题解析
问题:找出满足以下条件的字母:字母是小写的,在该字母两边各有3个大写字母做保镖。
**小编脑洞:**
第一眼看到该图片的感觉是,确实图片中的几根蜡烛生动形象的描述了大小写字母的关系,那么是哪些字母,图片并没有明确的表示,看来只有看源代码了。
依旧是一大堆乱七八糟的字符,与上一个问题的字符内容不同的时候,这里面的字符是由大小写英文字母组成。
这里有一个误区,通往下一个网页的关键单词的组成字母存在于这样的字符串`XXXxXXX`中,并且那些字母就是被那3个大写字母包围的小写字母,重新审核他给的原问题:
`One small letter, surrounded by EXACTLY three big bodyguards on each of its sides.`
注意里面的单词`EXACTLY`,也就是说刚好是`3`个大写字母而不是`4`个大写字母,因此为了确保,旁边三个大写字母两边必须分别是一个小写字母!所以答案就很明显了,该单词的字母存在于由9个字母组成的字符串中,排列形式是`xXXXxXXXx`。也就是说从网页源码的一大堆杂乱的字符串中找出符合`xXXXxXXXx`这样排列的子字符串就可以得出需要的小写字母,然后将那些满足条件的小写字母连接好就是通往答案的单词。
首先,那一堆乱七八糟的字符已经上传到实验楼的服务器上了,大家输入以下命令行下载文档:
1 wget http://labfile.oss.aliyuncs.com/courses/409/character.txt
废话有点多,看看大神们的解决方案:
**方法一:正则匹配,最简单粗暴的方式**
1 import re 2 3 text = open(‘character.txt‘).read() 4 5 "".join(re.findall(‘[^A-Z][A-Z]{3}([a-z])[A-Z]{3}[^A-Z]‘,tetx))
这里用到了正则表达式`re`模块,关于正则表达式的内容,推荐学习[30分钟入门正则表达式],里面的正则表达式测试工具也是相当好用,以下介绍正则语句`[^A-Z][A-Z]{3}([a-z])[A-Z]{3}[^A-Z]`:
1. 首先,函数`re.fidall(regex,str)`是返回所有`str`内容中被正则语句`regex`匹配的所有字符串;
2. `[^A-Z]`是匹配一个非大写字母;
3. `[A-Z]{3}`指的是匹配3个连续的大写字母;
4. `[a-z]`匹配一个小写字母。
**方法二:使用`string`模块,最常规的方法**
1 import string 2 3 4 5 s = open(‘character.txt‘,‘r‘).read() 6 7 lwr=string.lowercase 8 9 upr=string.uppercase 10 11 12 13 for n in range(1,len(s)-7): 14 15 if (s[n-1] in lwr) and (s[n] in upr) and (s[n+1] in upr)16 17 and (s[n+2] in upr) and (s[n+3] in lwr)18 19 and (s[n+4] in upr) and (s[n+5] in upr)20 21 and (s[n+6] in upr) and (s[n+7] in lwr): 22 23 print s[n+3]
该方法没有讲究什么技巧,直接判断是否存在满足条件`xXXXxXXXx`的字符,然后把最中间的那个小写字母`x`给打印出来。
**方法三:使用`reduce`函数调用子函数处理字符串**
创建一个`test_0.py`文件,编写代码:
# coding:utf-8 def level_3(state, c): if not c.isalpha(): # 先判断字符是否为是字母 return state if state: # 如果元组不为空 chars_found, state_lower, upper_count = state else: state_lower = "" upper_count = 0 chars_found = "" if c.islower(): # 如果字符是小写字母 if upper_count == 3: # 如果大写字母的数量为3 if state_lower: # 如果找到符合条件的小写字母 chars_found += state_lower state_lower = c else: state_lower = "" upper_count = 0 else: upper_count += 1 return chars_found, state_lower, upper_count if __name__ == "__main__": with open(‘character.txt‘) as f: s = f.read() s += "x" # 最右边添加一个小写字母 print reduce(level_3, str, ())[0]
关于`reduce`函数,可以使用`help`命令查阅该函数的英文文档。用法如下:
- `reduce(function, sequence[,initial])`函数中有两个参数和一个可选参数;`function`参数是一个函数,`sequence`参数是一个序列,使用`reduce`函数可以给函数`function`提供参数进行累加,参数来源于序列`sequence`的元素;
- - 比如:`reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])`就是计算`((((1+2)+3)+4)+5)`;也就是`function`的结果和``sequence`中下一个序列元素作为`function`的参数;
- - `initial`参数如果被给出,那么它将作为初始化结果,缺省情况下是空的。
那么应用在`test_0.py`文件中的`reduce`函数的作用就是,使用`level_3`函数对字符串`s`中的每一个字母进行处理,且函数初始返回结果`state`为空元祖`()`。
五、第4个挑战题
**小编脑洞**
这一次的题目没有给任何提示,只是上面的一张图片,图片的名字为`chainsaw`,意思是`肢解,用链锯隔开`,那么是不是意味着接下来的东西是一个`链`?
经过这几次闯关,我们知道一般答案都隐藏在网页源代码中,审查元素,发现:
首先,有提示说,使用`urllib`或许能帮助我们,但是不要一直追踪链接,因为一直点击链接是没有终止循环的那一天,因为链接数量超过400个。
那么答案应该就是隐藏在链接中,刚好在图片的下面找到一个`href`链接语句:`a href="linkedlist.php?nothing=12345"`那么点击这个[链接]查看结果:
提示下一个`nothing`的值为44837,那么把链接地址:http://www.pythonchallenge.com/pc/def/linkedlist.php?nothing=12345中的`nothing`值改为`44837`就是了,其实这个时候不用查看链接的结果也知道,下一个页面也是这样提示`nothing`的值,根据源代码中的提示,这个链接不超过400个,因此如果手动点击400次会非常浪费时间而且无聊,那么就使用`Python`的`urllib`模块玩一玩吧~
关于这个题目,因为要访问外网才能遍历链接,因此在这里只介绍一种方法,希望你们在自己的电脑上进行编程验证代码,并用自己的方式解决这个问题:
1 import urllib 2 3 import urllib2 4 5 6 7 data= {} 8 9 number = ‘12345‘ 10 11 12 13 # 循环400次 14 15 for i in range(400): 16 17 # 给字典`data`中的`nothing`赋初始值 18 19 data[‘nothing‘] = number 20 21 url_values = urllib.urlencode(data) 22 23 url = ‘http://www.pythonchallenge.com/pc/def/linkedlist.php‘ 24 25 full_url = url + ‘?‘ + url_values 26 27 28 29 foo = urllib2.urlopen(full_url) 30 31 foo = foo.read() 32 33 print foo # 打印网页内容 34 35 foo = foo.split(" ") # 使用空格作为分隔符分开单词 36 37 38 39 number = [i for i in foo if i.isdigit()][0]
然后就会循环400次打印捕捉到的网页内容
然后你根据这些内容跳链接,实际上这种方式有障碍,那就是遇到网页内容中没有数字的内容时会报错,例如在上面的脚本运行一段时间后会打印以下内容:
注意看最后一句,`Yes. Divide by two and keep going.`该句中没有任何数字而是提示,需要将前一个数字除以2才能继续前进。
遇到这种情况,通常是把初始的`number`改为`92118/2`,但是这样显得不怎么`pythonic`,而且,一般答案应该是给一个链接比如内容应该是以`.html`结尾的,因此需要把以上情况都考虑到原始脚本的条件中,修改以上脚本:
1 import re 2 3 import urllib 4 5 6 7 url="http://www.pythonchallenge.com/pc/def/linkedlist.php?nothing=" 8 9 10 11 nothing = "12345" 12 13 # 匹配以数字结尾的字符串以继续搜索 14 15 search = re.compile(" (\d*)$") 16 17 匹配最终的网页类型字符串以跳出循环 18 19 search_html = re.compile("\.html$") # 20 21 22 23 for i in xrange(400): 24 25 print "%s: " % nothing, 26 27 28 29 line = urllib.urlopen( "%s%s" % (url,nothing) ).read() 30 31 print line 32 33 34 35 # 如果找到最终的网页链接则终止查询 36 37 if search_html.findall (line): 38 39 break 40 41 42 43 match = search.findall (line) 44 45 if match: 46 47 # 下一个nothing值 48 49 nothing = match [0] 50 51 else: 52 53 # 上一个nothing除以二 54 55 nothing = str(int(nothing) / 2)
其实这个题目的原理很简单,一直搜索链接,遇到数字就进入下一次搜索,遇到非数字就考虑是否是`.html`结尾,不是就给上一个数字除2继续搜索。
六、第5个挑战题
问题:读出来
**小编脑洞**
依旧源代码:
注意语句:`<peakhell scr="banner.p">`以及`peak hell sound familiar?`
了解到应该是一个跟`peak hell`读音非常接近的`Python`用法,由于`banner.p`是一个文件,那么文件处理中跟`peak hell`发音相似的只有`pickle`模块,那么问题就很明显了,应该就是让我们使用`pickle`模块对`banner.p`文件进行处理,答案应该就隐藏在经过处理后的字符串中。
那么小编已经把文件`banner.p`放在个人的`git`仓库中了,请输入以下命令下载文件并查看:
1 git clone http://git.shiyanlou.com/wing1995/shiyanlou_cs409 2 3 cd shiyanlou_cd409 4 5 ls
`pickle`模块介绍:
- > `python`的`pickle`模块实现了基本的数据序列和反序列化。通过`pickle`模块的序列化操作我们能够将程序中运行的对象信息保存到文件中去,永久存储;通过`pickle`模块的反序列化操作,我们能够从文件中创建上一次程序保存的对象。
由于数据文件有了,那么要做的就是使用`pikle`模块中`load()`函数从文件中重构`python`对象:
1 import pickle 2 3 4 5 # 以二进制形式读取文件内容 6 7 data = pickle.load(open(‘banner.p‘, ‘rb‘)) 8 9 for each in data: 10 11 print each
观察数据打印结果:
结果是一个列表,列表里面的数据非常有规律,列表里面包含有多个子列表,子列表的内容是由一到多个元组组成,那些元祖里面都是只有2个元素,第一个元素是字符型,要么是`‘ ‘`,要么是`‘#‘`,第二个元素是数字,仔细分析这些子列表,发现:
很有意思,都是有规律的,猜测:子列表中每一个元祖代表字符(第一个元素)出现的次数(第二个元素)。
尝试打印:
1 for each in data: 2 3 "".join([i[1] * i[0] for i in each])
见证奇迹的时刻到来了:
额,好吧,实验楼虚拟环境显示图不全,再来一张比较明显的:
这明明就是单词`channel`!
看来答案非常明显,把`url`链接中单词`peak`换成`channel`就好。所有大家看着这个字符图片是不是觉得很有意思,关于这个字符图画的拓展课程,实验楼也已经写好,在[Python图片转字符画]。
鉴于实验楼的命令行窗口显示不清楚,我们先把输出的字符写入到图片文件中查看字符画:
1 import pickle, pprint 2 3 import Image, ImageDraw 4 5 6 7 im = Image.new("1", (95, 24)) 8 9 draw = ImageDraw.Draw(im) 10 11 data = pickle.loads(s) 12 13 line = 0 14 15 16 17 for i in data: 18 19 xpos = 0 20 21 for j in i: 22 23 if j[0] == " ": 24 25 draw.line([(xpos,line), (xpos+j[1],line)], 255) 26 27 xpos += j[1] 28 29 line += 1 30 31 im.save("banner.bmp")
以上代码需要安装`Image`模块后才能实现,关于该模块的安装和学习在[课程Python图片转字符画]和[课程Python破解验证码]里面有详细介绍,欢迎各位学完那些课程再来查看以上代码是否更加熟悉。
当然,不一定要直接转成图片文件,也可以直接把字符串的输出结果写入到文件中。
七、作业
请完成以下作业,并写入实验报告,不一定要做出结果,但是一定要写出思路以及对这门课程的总结:
### 问题:请访问[挑战题链接]查看源代码,找到通往下一个网址的链接。(提示:与`zip`有关)