去年大年初二,我写了一篇文章「用 Go 语言实现刘谦 2024 春晚魔术,还原尼格买提汗流浃背的尴尬瞬间!」,里面揭秘了小尼魔术失败的原因,这也是我公众号的第一篇文章。
今天刚好也是大年初二,我再带大家用 Go 语言还原一下刘谦在蛇年春晚上的魔术。
先吐个槽,相比去年的魔术,今年的魔术是不是有点「降本增效」了 :)。我看有人提到今年的魔术类似冒泡排序…这个属实有亿点🤏夸张了 😅。
没什么数学原理,也什么算法公式,咱们就最简单直接的使用暴力求解法,来穷举一下所有可能情况,这也正是程序代码的强项所在。
排列组合
只有筷子🥢、杯子🍺、勺子🥄三样东西,所有排列组合也仅仅只有 6 种情况。
给定这三种物品,使用 Go 代码求出所有排列组合如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| package main
import "fmt"
func permute(items []string, start int) { if start == len(items) { fmt.Println(items) return }
for i := start; i < len(items); i++ { items[start], items[i] = items[i], items[start] permute(items, start+1) items[start], items[i] = items[i], items[start] } }
func main() { items := []string{"筷子🥢", "杯子🍺", "勺子🥄"} permute(items, 0) }
|
执行示例代码,得到输出如下:
1 2 3 4 5 6 7
| $ go run main.go [筷子🥢 杯子🍺 勺子🥄] [筷子🥢 勺子🥄 杯子🍺] [杯子🍺 筷子🥢 勺子🥄] [杯子🍺 勺子🥄 筷子🥢] [勺子🥄 杯子🍺 筷子🥢] [勺子🥄 筷子🥢 杯子🍺]
|
这几种组合其实心算也能很快求出来。
魔术实现
接着,咱们捋一下刘谦这个魔术的三个步骤:
筷子跟它左边的物品互换,如果筷子已经在最左边,则无需移动。
杯子跟它右边的物品互换,如果杯子已经在最右边,则无需移动。
勺子跟它左边的物品互换,如果勺子已经在最左边,则无需移动。
那么,我们要做的,就是把这三个步骤封装成一个小函数,然后让每一种排列组合都交给这个魔术执行一遍,最终看看得到的结果即可。
魔术步骤实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| func magic(items []string) { old := make([]string, len(items)) copy(old, items)
for i := 1; i < len(items); i++ { if items[i] == "筷子🥢" { items[i], items[0] = items[0], items[i] break } }
for i := len(items) - 2; i >= 0; i-- { if items[i] == "杯子🍺" { items[i], items[len(items)-1] = items[len(items)-1], items[i] break } }
for i := 1; i < len(items); i++ { if items[i] == "勺子🥄" { items[i], items[0] = items[0], items[i] break } }
fmt.Println("当前排列:", old, " => ", "魔术操作后:", items) }
|
这里逻辑非常简单,就是按照魔术步骤交换物品。
现在我们再修改下 permute
函数打印排列顺序的代码 fmt.Println(items)
,改为直接调用魔术函数 magic(items)
。
最终实现代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| package main
import "fmt"
func permute(items []string, start int) { if start == len(items) { magic(items) return }
for i := start; i < len(items); i++ { items[start], items[i] = items[i], items[start] permute(items, start+1) items[start], items[i] = items[i], items[start] } }
func magic(items []string) { old := make([]string, len(items)) copy(old, items)
for i := 1; i < len(items); i++ { if items[i] == "筷子🥢" { items[i], items[0] = items[0], items[i] break } }
for i := len(items) - 2; i >= 0; i-- { if items[i] == "杯子🍺" { items[i], items[len(items)-1] = items[len(items)-1], items[i] break } }
for i := 1; i < len(items); i++ { if items[i] == "勺子🥄" { items[i], items[0] = items[0], items[i] break } }
fmt.Println("当前排列:", old, " => ", "魔术操作后:", items) }
func main() { items := []string{"筷子🥢", "杯子🍺", "勺子🥄"} permute(items, 0) }
|
执行示例代码,得到输出如下:
1 2 3 4 5 6 7
| $ go run main.go 当前排列: [筷子🥢 杯子🍺 勺子🥄] => 魔术操作后: [勺子🥄 筷子🥢 杯子🍺] 当前排列: [勺子🥄 杯子🍺 筷子🥢] => 魔术操作后: [勺子🥄 筷子🥢 杯子🍺] 当前排列: [杯子🍺 勺子🥄 筷子🥢] => 魔术操作后: [勺子🥄 筷子🥢 杯子🍺] 当前排列: [勺子🥄 杯子🍺 筷子🥢] => 魔术操作后: [勺子🥄 筷子🥢 杯子🍺] 当前排列: [筷子🥢 勺子🥄 杯子🍺] => 魔术操作后: [勺子🥄 筷子🥢 杯子🍺] 当前排列: [勺子🥄 杯子🍺 筷子🥢] => 魔术操作后: [勺子🥄 筷子🥢 杯子🍺]
|
可以发现,无论哪一种排列顺序,经过魔术的三个步骤以后,最终结果都是一致的 [勺子🥄 筷子🥢 杯子🍺]
。所以这个魔术一定会成功,小尼笑而不语😄。
总结
没啥好总结的,看个乐子,今年的魔术属实有点简陋。
嗯,又水了一篇文章 :)
延伸阅读
联系我