最近几天正值诺兰的新片《信条》上映,朋友圈已经被“看不懂”刷屏了。我虽然不是诺兰迷,但细细想来,也看过不少他的影片,都二刷,三刷,留下了很深的映像。这里《信条》就不剧透了,我来说说大家耳熟能详的几部诺兰的老片。
我们知道有很多很难懂的优秀影片。而作为理工科的同学,看这些电影最忌讳的就是试图在逻辑上寻求一种完备的解释,而忽视了电影本身所尝试传达的情感。比如大卫林奇,近一点的,比如国内的毕赣,他的《路边野餐》以及评分不太高的《地球最后的夜晚》等。这样的影片,你是无论如何都不能自圆其说的,如果不是去感受,而是试图去从逻辑上分析,永远都不会懂。
但诺兰不同,他的所谓“硬科幻”,在一个大家能够也必须接受的假设下,能够通过逻辑推理完整合理的解读整部影片,让人信服。最近我也乘热重温了一下非常喜欢的几部诺兰的电影。既然我说能够通过逻辑推理来解读,那身为一名软件工程师,能否通过手中的代码来解读呢?
当然,作为一名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
....
....
结尾彩蛋
在慕课网,我收到最多的问题就是,老师什么时候出新课。由于我本人工作繁忙,工作之余也在赛车运动上寻求突破,所以做新课的事情一直耽搁了很久。不过这次不同,带来的不仅是彩蛋,而是一个大惊喜。
– 我的新课上线了!
这次我将不再是讲解基础知识,而是实干,带领大家做一款汽车分时租赁的小程序。全栈开发,采用ts+go黄金组合,课程总时长120小时,我讲解部分60小时。
扫码体验
课程链接
- 金职位 --> Go开发工程师 https://class.imooc.com/sale/go 💪😜💯
共同学习,写下你的评论
评论加载中...
作者其他优质文章