最近几天正值诺兰的新片《信条》上映,朋友圈已经被“看不懂”刷屏了。我虽然不是诺兰迷,但细细想来,也看过不少他的影片,都二刷,三刷,留下了很深的映像。这里《信条》就不剧透了,我来说说大家耳熟能详的几部诺兰的老片。
我们知道有很多很难懂的优秀影片。而作为理工科的同学,看这些电影最忌讳的就是试图在逻辑上寻求一种完备的解释,而忽视了电影本身所尝试传达的情感。比如大卫林奇,近一点的,比如国内的毕赣,他的《路边野餐》以及评分不太高的《地球最后的夜晚》等。这样的影片,你是无论如何都不能自圆其说的,如果不是去感受,而是试图去从逻辑上分析,永远都不会懂。
但诺兰不同,他的所谓“硬科幻”,在一个大家能够也必须接受的假设下,能够通过逻辑推理完整合理的解读整部影片,让人信服。最近我也乘热重温了一下非常喜欢的几部诺兰的电影。既然我说能够通过逻辑推理来解读,那身为一名软件工程师,能否通过手中的代码来解读呢?
当然,作为一名Go语言的推崇者,我还是使用Go语言来进行解读。其中也不乏使用了goroutine和channel的特性,不理解的同学,可能需要先看一下《搭建并行处理管道,感受GO语言魅力》这门免费课,一样精彩。
当然,如果你有购买《Google资深工程师深度讲解Go语言》那自然是更好啦😎
========= 剧透分割线 ==========
《记忆碎片》是一部结构特别精妙的作品。正如大家在代码中看到的那样,整个故事非常简单,甚至有些可笑,但经过时间线的加工,采用彩色片段代表倒叙,黑白片段代表正序的方式,把短期失忆的经历让观众跟随主人公体验了一番,非常有意思。最后黑白片段和彩色片段相遇,解释真相,达到高潮。
这部片子并不难懂,很快就能够跟上导演的倒叙正序交错的框架,唯一需要的就是记忆。所以这里的代码解读也比较简单,首先列出了整个故事,然后通过双指针扫描,生成交错时间线,生成场景“脚本”。大家看,本身一个平平无奇的故事,交错之后是不是充满悬念,越来越紧张呢?
package main import "fmt" type Scene struct { Camera string StoryIndex int } func generateTimeline(len int) []Scene { var timeline []Scene bwIndex := 0 colorIndex := len - 1 for i := 0; i < len; i++ { if i%2 == 0 { timeline = append(timeline, Scene{ Camera: "彩色", StoryIndex: colorIndex, }) colorIndex-- } else { timeline = append(timeline, Scene{ Camera: "黑白", StoryIndex: bwIndex, }) bwIndex++ } } return timeline } func main() { storyLine := []string{ "我的妻子被John G.强奸杀害,而我也受到伤害,患有短期失忆,只记得几分钟的事。我要杀了John G.来报仇", "我在电话里讲述Sammy的故事:我曾是一个保险公司调查员,调查Sammy车祸后短期失忆的案子。我戳穿Sammy,保险拒赔", "Sammy因短期失忆,给妻子注射了过量胰岛素,导致妻子死亡。电话完,电话对面是Teddy", "Teddy让我制服Jimmy,因为Jimmy就是John G. 我开枪射杀了Jimmy,但觉得杀错了", "质问Teddy,才得知真相:真正的John G.早就被杀,而注射过量胰岛素至妻子死亡的是我自己,我只是选择遗忘。而Teddy利用我干掉了Jimmy,他自己真名也是John G. 我写下“不要相信他”,把他的车牌号纹在身上。把Teddy当作我下一个人生目标", "我去找Natalie,Natalie决定利用我帮她除掉Dodd", "我把Dodd打了一顿并赶走", "Natalie得知我想要杀掉John G. 决定利用我除掉真名是John G.的Teddy", "Natalie查到Teddy的车牌号,和我的纹身一致,而Teddy真名叫John G. 证据确凿,我杀了Teddy", } scenes := generateTimeline(len(storyLine)) for _, s := range scenes { fmt.Printf("%s: %s\n", s.Camera, storyLine[s.StoryIndex]) } }
输出:
彩色: Natalie查到Teddy的车牌号,和我的纹身一致,而Teddy真名叫John G. 证据确凿,我杀了Teddy 黑白: 我的妻子被John G.强奸杀害,而我也受到伤害,患有短期失忆,只记得几分钟的事。我要杀了John G.来报仇 彩色: Natalie得知我想要杀掉John G. 决定利用我除掉真名是John G.的Teddy 黑白: 我在电话里讲述Sammy的故事:我曾是一个保险公司调查员,调查Sammy车祸后短期失忆的案子。我戳穿Sammy,保险拒赔 彩色: 我把Dodd打了一顿并赶走 黑白: Sammy因短期失忆,给妻子注射了过量胰岛素,导致妻子死亡。电话完,电话对面是Teddy 彩色: 我去找Natalie,Natalie决定利用我帮她除掉Dodd 黑白: Teddy让我制服Jimmy,因为Jimmy就是John G. 我开枪射杀了Jimmy,但觉得杀错了 彩色: 质问Teddy,才得知真相:真正的John G.早就被杀,而注射过量胰岛素至妻子死亡的是我自己,我只是选择遗忘。而Teddy利用我干掉了Jimmy,他自己真名也是John G. 我写下“不要相信他”,把他的车牌号纹在身上。把Teddy当作我下一个人生目标
《致命魔术》可以说是诺兰最难理解的片子。非线性叙事,加上魔术本身,还有超自然的道具,使得这部影片魔幻无比。而其所表达的却是非常残酷的魔术表演背后的故事,讲述了为了成功所付出的代价。而这些表达非常隐藏,越看懂,越黑暗。反而如果害怕黑暗的话,会阻碍对此片的理解。
片头说到魔术,把一个东西变没很简单,观众也不会鼓掌,只是惊讶。真正的成功在于让消失的东西重新出现。然而往往,这个重新出现的只是替身,也就是片名prestige。正如片头的魔术,魔术师让鸟消失时,鸟已经在机关里被杀死,重新出现的只是别的鸟而已。
那么魔术师的瞬移呢?主角的对手也是一名魔术师,他让自己兄弟作为替身。但这样非常痛苦,片中说到魔术师的手指断了,为了保证替身不被识破,竟然把替身的手指也砍断。而对于魔术师自己来说,由于他需要在魔术开始前烘托气氛,所以他只能扮演消失的那位,掉入舞台的陷阱,只能在后台想象观众欢呼的场景;而替身则是真正出现接受观众鼓掌欢呼的人。
我们的主角选了另一种方法,他远渡重洋,找到了传奇人物特斯拉,收获了一个可以克隆人的机器。这里的克隆人不仅能克隆肉身,而且能克隆所有的记忆。-- 这立刻就让我想到了fork函数。
主角的瞬移魔术是这样变的:表演开始,主角触发克隆,掉入陷阱下的水缸淹死。而克隆人在台上出现,接受观众欢呼,然后周而复始,进行下一次表演。由于克隆人也保留了记忆,所以主角认为接受台下观众欢呼的也是他自己。那么到底哪个才是真正的“我自己”呢?这个问题不重要。我更倾向于认为“我”的意识每次都在水缸中淹死。
好了,说了这么多,下面就是代码:
package main import ( "fmt" "time" ) func performShow(cnt int) { fmt.Printf("\n第%d场表演开始\n", cnt) go cloneMagician(cnt + 1) fmt.Printf(" --我在第%d场表演后死去\n", cnt) } func cloneMagician(cnt int) { fmt.Println("演出成功,接受观众欢呼") time.Sleep(time.Second) performShow(cnt) } func main() { go performShow(1) var forever chan bool <-forever }
输出:
第1场表演开始 --我在第1场表演后死去 演出成功,接受观众欢呼 第2场表演开始 演出成功,接受观众欢呼 --我在第2场表演后死去 第3场表演开始 演出成功,接受观众欢呼 --我在第3场表演后死去 第4场表演开始 --我在第4场表演后死去 演出成功,接受观众欢呼 第5场表演开始 演出成功,接受观众欢呼 --我在第5场表演后死去 ...
观众看到的,是一次次的表演成功,而同时在后台,是“我”的一次次死去。
最后,我们来说说《盗梦空间》吧。这部作品大家都非常熟悉了,我就不多复述。大家一定都想到了递归,但到底怎么递归呢?我在这里尝试模拟了n层梦境的时间模型。我们知道,下一层梦境的时间相比上一层梦境要快很多,所以片中第一层梦境中车辆坠崖的几秒钟里,在第四层梦境中则是发生了跌宕起伏的情节。
那么这n层梦境如何在时间轴上进行同步呢?这里就要用到go语言中的channel:
package main import ( "fmt" "time" ) const ( limbo = 4 secondsMul = 5 ) func padAndFormat(seconds int, level int) string { pad := "" for i := 0; i < level; i++ { pad += " " } return fmt.Sprintf("%s%d seconds passed in level %d", pad, seconds, level) } func dream(level int) chan time.Time { var ticker <-chan time.Time if level == limbo { ticker = time.Tick(time.Second) } else { ticker = dream(level + 1) } out := make(chan time.Time) go func() { seconds := 0 for { tm := <-ticker seconds++ fmt.Println(padAndFormat(seconds, level)) if seconds%secondsMul == 0 { out <- tm } } }() return out } func main() { ticker := dream(1) for { <-ticker } }
输出:
1 seconds passed in level 4 2 seconds passed in level 4 3 seconds passed in level 4 4 seconds passed in level 4 5 seconds passed in level 4 1 seconds passed in level 3 6 seconds passed in level 4 7 seconds passed in level 4 .... .... 1998 seconds passed in level 4 1999 seconds passed in level 4 2000 seconds passed in level 4 400 seconds passed in level 3 80 seconds passed in level 2 16 seconds passed in level 1 2001 seconds passed in level 4 2002 seconds passed in level 4 2003 seconds passed in level 4 2004 seconds passed in level 4 2005 seconds passed in level 4 401 seconds passed in level 3 2006 seconds passed in level 4 .... ....
在慕课网,我收到最多的问题就是,老师什么时候出新课。由于我本人工作繁忙,工作之余也在赛车运动上寻求突破,所以做新课的事情一直耽搁了很久。不过这次不同,带来的不仅是彩蛋,而是一个大惊喜。
– 我在准备新课了!
这次我将不再是讲解基础知识,而是实干,带领大家做一款汽车分时租赁的小程序。这里我也不顾慕课网小姐姐的阻拦,偷偷放出一些设计稿给大家先睹为快。
敬请期待 💪😜💯