继续挑战,开始变得美了
第31题地址grandpa.html
- 网页标题是
Where am I?
,题目内容为空,源码里面有一行隐藏内容:<!– short break, this REALLY has nothing to do with Python –>
标题问在哪,图片有个链接grandpa.html,提示要密码,密码提示是island : country
。很明显了,要找出图片是哪个国家的哪个岛,我们用Google
的搜图:
from io import BytesIO
import requests
with requests.Session() as sess:
sess.auth = ('repeat', 'switch')
response = sess.get('http://www.pythonchallenge.com/pc/ring/grandpa.jpg').content
data = requests.urllib3.encode_multipart_formdata({
'encoded_image': (None, response)
})
header = {'Content-Type': data[1]}
search_image = sess.post('https://www.google.com/searchbyimage/upload', data=data[0],
headers=header, allow_redirects=False)
print(search_image.headers)
{'Location': 'https://www.google.com/search?tbs=sbi:AMhZZisp2a0LMhliMh47qE2wSFpJ3IbHE3Sm5U8XmVp6FOBfKy0ZymDGthiIjhCsCRRHuNwF2fLyA1Zj6psMCCLNJaCUDCeDI-lBJwZAtPlNJxK89uYEIM3C0fUshJBkMd5hCuCplOVImxTnCsxctt4A1e-PDLY1JlxN4uPhuqCuhUplUHZlAKh7AbJh0vhCg4C0LHxPwWIX754PVr6J9iv0u94CSG2DGHmhHRb4yx1M0w8SBkLb9fj4HJ4YmZzQZInctinkikuk54qqF1xiyFjJ8fRZgU6YgAnXz1DUzdHWtNKoC5DEHCXuKrIyJ3Im9h2EaVxvV6Si', 'Cache-Control': 'public, max-age=21600', 'Date': 'Mon, 03 Feb 2020 15:36:15 GMT', 'Expires': 'Mon, 03 Feb 2020 21:36:15 GMT', 'Content-Type': 'text/html; charset=UTF-8', 'X-Content-Type-Options': 'nosniff', 'Server': 'quimby_frontend', 'Content-Length': '567', 'X-XSS-Protection': '0', 'X-Frame-Options': 'SAMEORIGIN', 'Alt-Svc': 'quic=":443"; ma=2592000; v="46,43",h3-Q050=":443"; ma=2592000,h3-Q049=":443"; ma=2592000,h3-Q048=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000'}
打开搜索结果可以看到图片所在地为Koh Samui
,位于泰国:
阁沙梅岛(泰语:เกาะสมุย),一译苏美岛、苏梅岛,是泰国的一个岛屿,在行政区划上属于素叻他尼府阁沙梅县。
该岛是泰国第三大岛屿,仅次布吉岛和阁昌岛(象岛)。位于首都曼谷南方约560公里处,长21公里,宽25公里,总面积228.7平方公里,人口约50,000人(2008年)。当地人民以务农为主,其中椰子的生产是重要经济来源,岛上的椰树随处可见,所以此岛又被称为“椰子岛”;近年亦发展为泰国受欢迎的旅游点之一。From wikipedia.org
打开链接grandpa.html,输入用户名kohsamui
,密码thailand
,来到了新的页面:
- 网页标题是
UFOs ?
,题目内容为That was too easy. You are still on 31...
,源码里面没有隐藏内容,但在<img>标签里面放了两个奇怪的标签:<window left=”0.34” top=”0.57” width=”0.036” height=”0.027”/>
<option iterations=”128”/>
看来这里才是这题的重头戏。
图片长得挺好看的,一看图片名叫mandelbrot.gif
,原来是一种著名的分形结构:
曼德博集合(Mandelbrot set,或译为曼德布洛特复数集合)是一种在复平面上组成分形的点的集合,以数学家本华·曼德博的名字命名。曼德博集合与朱利亚集合有些相似的地方,例如使用相同的复二次多项式来进行迭代。
曼德博集合可以用复二次多项式来定义:
\(f_{c}(z)=z^{2}+c\)
其中c
是一个复数参数。
从 \(z = 0\) 开始对 \(f_c(z)\) 进行迭代:
\(z_{n+1} = z_n^2 + c, n=0,1,2,...\)
\(z_0 = 0\)
\(z_1 = z_0^2 + c = c\)
\(z_2 = z_1^2 + c = c^2 + c\)
每次迭代的值依序如以下序列所示:
\((0, f_c(0), f_c(f_c(0)), f_c(f_c(f_c(0))), \ldots)\)
不同的参数c
可能使序列的绝对值逐渐发散到无限大,也可能收敛在有限的区域内。
曼德博集合M
就是使序列不延伸至无限大的所有复数c
的集合。曼德博集合一般用计算机程序计算。对于大多数的分形软件,例如Ultra fractal,内部已经有了比较成熟的例子。下面的程序是一段伪代码,表达了曼德博集合的计算思路。
For Each c in Complex repeats = 0 z = 0 Do z = z^2 + c repeats = repeats + 1 Loop until abs(z) > EscapeRadius or repeats > MaxRepeats '根据定理三,EscapeRadius可设置为2。 If repeats > MaxRepeats Then Draw c,Black '如果迭代次数超过MaxRepeats,就将c认定为属于曼德博集合,并设置为黑色。 Else Draw c,color(z,c,repeats) 'colo函数用来决定颜色。 End If Next
From wikipedia.org
根据伪代码,对于每一个c
都要画上不同的颜色,这个c
应该是通过像素坐标来生成的复数,x
y
坐标分别对应实部和虚部;而画的这个颜色就是根据迭代使得模长超过2的次数决定的。
显然源码中的奇怪标签就是生成这张曼德博图的参数。window
里的left
top
width
height
是将x
y
坐标转化成复数c
的,而iterations
是指求颜色时最大的迭代次数。
现在让我们来生成一个:
from itertools import product
from PIL import Image
def mandelbrot(size, left, top, width, height, max_iter) -> Image:
img = Image.new('L', size)
img_data = img.load()
for x, y in product(range(img.width), range(img.height)):
c = complex(left + x * width / img.width, top + y * height / img.height)
z = 0
for i in range(max_iter):
z = z * z + c
if abs(z) > 2:
break
img_data[x, y] = i
return img
mandelbrot((640, 480), 0.34, 0.57, 0.036, 0.027, 128)
啊,跟题目原图长得非常得像,需要翻转一下。同时我们将图像差别记录下来:
from io import BytesIO
import requests
from PIL import Image
with requests.Session() as sess:
sess.auth = ('kohsamui', 'thailand')
response = sess.get('http://www.pythonchallenge.com/pc/rock/mandelbrot.gif').content
img = Image.open(BytesIO(response))
left, top, width, height = 0.34, 0.57, 0.036, 0.027
iterations = 128
img_new = mandelbrot(img.size, left, top, width, height, iterations).transpose(Image.FLIP_TOP_BOTTOM)
diff = [(a, b) for a, b in zip(img.getdata(), img_new.getdata()) if a != b]
print('count:', len(diff))
print(diff[:20])
count: 1679
[(43, 59), (48, 64), (44, 60), (40, 56), (45, 61), (36, 52), (67, 51), (39, 55), (84, 68), (70, 86), (83, 67), (37, 53), (71, 55), (40, 56), (49, 65), (51, 67), (36, 52), (48, 64), (69, 85), (36, 52)]
看上去也很有规律,都是相差16。
先用上一题的质因数分解看看:
def prime_factor(n: int) -> tuple:
factors = []
while n > 1:
for i in range(2, n // 2 + 1):
if n % i == 0:
factors.append(i)
n //= i
break
else:
factors.append(n)
break
return tuple(factors)
print(prime_factor(len(diff)))
(23, 73)
也是唯一的分解方式:\(1679=23\times73\),所以图像大小为(23, 73)
,差值用二值化表示出来:
from PIL import Image
img_diff = Image.new('L', prime_factor(len(diff)))
img_diff.putdata([(print(f'abs({a} - {b}) != 16!!!') if abs(a - b) != 16
else bool(a > b)) for a, b in diff], 256)
img_diff
图片非常小,不过我们隐约看到有一个小人和一些象形图案,联系标题UFOs ?
可以知道,这是人类向宇宙发射的代表人类文明的著名信息:
阿雷西博信息(Arecibo Message),是于1974年11月16日以距离地球25,000光年的球状星团M13为目标,发送的无线电信息。该信息共有1,679个二进制数字(ASCII),而且1,679这个数字只能由两个质数相乘,因此只能把信息拆成73条横列及23条直行,这是假设该信息的读者会先将它排成一个长方形。如果把它排成23条横列,它会变成白色噪声,相反如果把它排成73条横列,便可排出图中的一幅信息。 From wikipedia.org
把地址改为arecibo.html,来到了下一题。