为了账号安全,请及时绑定邮箱和手机立即绑定

今天我写了三段代码,用来解读诺兰的魔术

最近几天正值诺兰的新片《信条》上映,朋友圈已经被“看不懂”刷屏了。我虽然不是诺兰迷,但细细想来,也看过不少他的影片,都二刷,三刷,留下了很深的映像。这里《信条》就不剧透了,我来说说大家耳熟能详的几部诺兰的老片。
我们知道有很多很难懂的优秀影片。而作为理工科的同学,看这些电影最忌讳的就是试图在逻辑上寻求一种完备的解释,而忽视了电影本身所尝试传达的情感。比如大卫林奇,近一点的,比如国内的毕赣,他的《路边野餐》以及评分不太高的《地球最后的夜晚》等。这样的影片,你是无论如何都不能自圆其说的,如果不是去感受,而是试图去从逻辑上分析,永远都不会懂。
但诺兰不同,他的所谓“硬科幻”,在一个大家能够也必须接受的假设下,能够通过逻辑推理完整合理的解读整部影片,让人信服。最近我也乘热重温了一下非常喜欢的几部诺兰的电影。既然我说能够通过逻辑推理来解读,那身为一名软件工程师,能否通过手中的代码来解读呢?
当然,作为一名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小时。
图片描述

扫码体验

图片描述

课程链接

点击查看更多内容
17人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
全栈工程师
手记
粉丝
1.3万
获赞与收藏
487

关注作者,订阅最新文章

阅读免费教程

感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消