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