1 前言
随着2024 年春晚的完美落幕,刘谦的魔术表演再度成为了全国人们热议的焦点。作为一名程序员我们尝试从编程的角度来揭秘刘谦的魔术,通过代码穷举所有情况验证是否剩下的两张牌都一致。
2 魔术步骤
1、准备4张扑克牌并平均撕成两份,叠放在一起,形成8个半张牌的形式:ABCD-ABCD。
2、将牌堆顶的数量为名字字数的牌移至牌堆底。这一步实际上并不改变牌的本质顺序,只是营造神秘感。
3、将前三张牌放在牌堆中间,并取出牌堆顶的牌放置在一旁。此操作确保头尾两张牌相同。
4、取出牌堆顶的若干张牌插入牌堆中间。具体取牌数依据地域差异,如南方人取1张,北方人取2张,若不确定则取3张。但这一步在魔术中并不起决定性作用。
5、男生扔掉牌堆顶1张,女生扔掉牌堆顶2张。这一步是为了调整牌的数量,以适应后续的操作。
6、执行"见证奇迹的时刻"循环。每说一个字,就取出牌堆顶的牌放置在牌堆底。这一步骤通过多次循环操作,改变了牌的顺序。
7、执行关键操作:从牌堆顶开始,每次先将牌堆顶的一张牌放在牌堆底,再扔掉牌堆顶的一张牌。重复此操作直到只剩一张牌。检查这张牌和最初放置在一旁的牌是否吻合。若吻合,则魔术成功。
需要注意的是,这个魔术的关键在于通过特定的操作步骤和牌的数量变化,最终找到和最初放置在一旁的牌相同的牌。而地域、性别等因素在魔术中并不起决定性作用,更多的是为了增加魔术的趣味性和神秘感。
3 代码实现
代码如下:
ini
package com.mrxu.admin.test;
public class MoShuTest {
/**
* 计算某种情况下 最后剩余的两张牌
* @param nameLength 名字长度
* @param pre3ToMiddle 前三张插入的中间位置
* @param district 根据地区获得的数据 1、2、3
* @param districtToMiddle 地区获得的数据插入的中间位置
* @param sex 性别变量
*/
private static void run(int nameLength,int pre3ToMiddle,int district,int districtToMiddle,int sex) {
char firstChar,lastChar;
// 1 准备四张扑克牌,将它们平均撕成两份,并叠放在一起,形成八张半张牌的形式,即"ABCD-ABCD"的形式。
char[] orgArray = {'A','B','C','D','A','B','C','D'};
// 2 将牌堆顶的牌数量为名字字数的牌移至牌堆底。这个步骤其实并不影响牌的顺序,只是为了营造神秘感
orgArray = moveToBottom(orgArray,nameLength);
// 3 从牌堆顶取出三张牌,随意地插入到牌堆的中间位置。这一步是为了确保头尾两张牌是一样的。
orgArray = moveToMiddle(orgArray,3,pre3ToMiddle);
// 4 将牌堆顶的一张牌取出,放置在一旁,这张牌将作为后续验证的关键牌。
firstChar = orgArray[0];
orgArray = removeTop(orgArray,1);
// 5 根据南北地区不同,从牌堆顶取出一定数量的牌插入到牌堆中间。这一步骤中的牌数选择可以根据实际情况进行调整,南方人取1张,北方人取2张,如果不确定可以取3张。但实际上这个步骤并不会影响魔术的成功与否。
orgArray = moveToMiddle(orgArray,district,districtToMiddle);
// 5 如果是男生,就扔掉牌堆顶的一张牌;如果是女生,就扔掉牌堆顶的两张牌。这一步是为了让男女生的牌堆数量有所不同,增加魔术的趣味性。
orgArray = removeTop(orgArray,sex);
// 6 执行"见证奇迹的时刻"循环,即每说一个字就从牌堆顶取出一张牌放置在牌堆底。这个步骤会改变牌的顺序,但不会影响头尾两张牌的一致性。
for(int i = 0;i < "见证奇迹的时刻".length();i++) {
orgArray = moveToBottom(orgArray,1);
}
// 7 最后,从牌堆顶开始,每次先将牌堆顶的一张牌放在牌堆底,再扔掉牌堆顶的一张牌。重复以上操作直到只剩一张牌。检查这张牌和之前放置在一旁的牌是否吻合。如果吻合,则魔术成功。
while(orgArray.length != 1) {
orgArray = moveToBottom(orgArray,1);
orgArray = removeTop(orgArray,1);
}
lastChar = orgArray[0];
System.out.println(firstChar+":"+lastChar);
}
/**
* 移动前n张牌到最下面
* @param orgArray 牌数据
* @param count 移动数量
* @return 移动后的牌数据
*/
private static char[] moveToBottom(char[] orgArray,int count) {
char[] rsArray = new char[orgArray.length];
for(int i = 0;i < orgArray.length;i ++) {
if(i < count) {
rsArray[orgArray.length - count + i] = orgArray[i];
} else {
rsArray[i - count] = orgArray[i];
}
}
return rsArray;
}
/**
* 移动上面moveCount张牌到下面中间位置
* @param orgArray 牌数据
* @param moveCount 移动数量
* @param middlePoint 插入中间位置
* @return 插入后的牌数据
*/
private static char[] moveToMiddle(char[] orgArray,int moveCount,int middlePoint) {
char[] rsArray = new char[orgArray.length];
for(int i = 0;i < orgArray.length;i++) {
if(i < moveCount) {
rsArray[middlePoint+i] = orgArray[i];
}
else {
if(i < moveCount+middlePoint) {
rsArray[i-moveCount] = orgArray[i];
}
else {
rsArray[i] = orgArray[i];
}
}
}
return rsArray;
}
/**
* 删除上面n张牌
* @param orgArray 牌数据
* @param count 删除数量
* @return 删除后的牌数据
*/
private static char[] removeTop(char[] orgArray,int count) {
char[] rsArray = new char[orgArray.length - count];
for(int i = count;i < orgArray.length;i++) {
rsArray[i-count] = orgArray[i];
}
return rsArray;
}
public static void main(String[] args) {
int allCount = 0;
for(int nameLength = 1;nameLength <= 8;nameLength ++) {
for(int pre3ToMiddle = 1;pre3ToMiddle <= 4;pre3ToMiddle ++) {
for(int district = 1;district <= 3;district ++) {
for(int districtToMiddle = 1;districtToMiddle <= 6-district;districtToMiddle ++) {
for(int sex = 1;sex <= 2;sex ++) {
run(nameLength,pre3ToMiddle,district,districtToMiddle,sex);
allCount++;
}
}
}
}
}
System.out.println("总次数:"+allCount);
}
}
3.1 run 给特定参数输出最终剩余的两张牌
3.2 moveToBottom方法,移动前n张牌到最下面
3.3 moveToMiddle方法,移动上面n张牌到下面中间位置
3.4 removeTop方法,删除上面n张牌
3.5 main方法,穷举所有情况输出结果
最终输出有768种情况,没中情况下留下的两张数字都一样。
4 总结
刘谦老师魔术本质其实就是约瑟夫环的问题。作为一名程序员应有把现实问题通过程序解决的能力,对编程感兴趣的可以关注我,我会定期分享编程技术。