用Python来实现2024年春晚刘谦魔术

简介

这是新春的第一篇,今天早上睡到了自然醒,打开手机刷视频就被刘谦的魔术所吸引,忍不住用编程去模拟一下这个过程。

首先,声明的一点,大年初一不学习,所以这其中涉及的数学原理约瑟夫环大家可以找找其他的教程看看,我这块只是复现它魔术里面的每个步骤。

魔术的步骤

总而言之,可以分为以下8个步骤:

Step 1: 将四张4张牌撕成两半,直接将两堆叠放;

Step 2: 假设姓名为n个字,重复n次,将堆在最上的牌放到最下面;

Step 3: 将牌堆最上的3张拿出,不改变顺序,并随机插入牌堆中间;

Step 4: 将牌堆最上方的牌拿走,放在一旁;

Step 5: 按照南/北/不知道是南或者北方地区,判断自己属于哪一地区,并分别将牌堆最上的1/2/3,不改变顺序,并随机插入牌堆中间;

Step 6: 按性别男/女,从牌堆最上方拿走1/2张牌,一边念口诀:"见证奇迹的时刻",每念一个字,将牌堆最上方的牌放到牌堆最下;

Step 7: 念口诀"好运留下米"时,将牌堆最上的牌放到牌堆最下;念"烦恼扔出去"时,将牌堆最上方的牌移除。重复这两句口诀,直到手中只有一张牌;

Step 8: 最后留下的牌和Step 4拿走的牌是一样的。

过程拆开分来其实就是对列表进行一个简单的操作了

用python实现其中的过程

0. 模拟扑克牌打乱并抽取的过程;

python 复制代码
import random
import itertools
import copy
# 定义扑克牌
suits = ['红桃', '方块', '梅花', '黑桃']
ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
jokers = ['小王', '大王']
deck_of_cards = list(itertools.product(suits, ranks)) + jokers
random.shuffle(deck_of_cards)       # 模拟打乱的操作
print(f"随机生成的{len(deck_of_cards)}扑克牌:", deck_of_cards)
selected_cards = random.sample(deck_of_cards, 4)
print("随机抽取其中的四张牌:", selected_cards)

随机抽取其中的四张牌: [('红桃', '9'), ('黑桃', '8'), ('黑桃', 'A'), ('黑桃', 'K')]

1. 将四张4张牌撕成两半,直接将两堆叠放;

python 复制代码
def split_and_stack(cards):
    cards_copy = copy.copy(cards)
    merged_cards = cards + cards_copy
    return merged_cards

split_cards = split_and_stack(selected_cards)
print("撕成两半后堆叠:", split_cards)

撕成两半后堆叠: [('红桃', '9'), ('黑桃', '8'), ('黑桃', 'A'), ('黑桃', 'K'), ('红桃', '9'), ('黑桃', '8'), ('黑桃', 'A'), ('黑桃', 'K')]

2. 假设姓名为n个字,重复n次,将堆在最上的牌放到最下面;

python 复制代码
def repeat_name(cards, name):
    name_length = len(name)
    for _ in range(name_length):
        # 取出堆在最上的牌,放到最下面
        top_card = cards.pop(0)  
        cards.append(top_card) 
    return cards

split_cards_repeated = repeat_name(split_cards, name)
print(f"{name} 重复姓名字数次后的牌堆:", split_cards_repeated)

夏天是冰红茶 重复姓名字数次后的牌堆: [('黑桃', 'A'), ('黑桃', 'K'), ('红桃', '9'), ('黑桃', '8'), ('黑桃', 'A'), ('黑桃', 'K'), ('红桃', '9'), ('黑桃', '8')]

3. 将牌堆最上的3张拿出,不改变顺序,并随机插入牌堆中间;

python 复制代码
def take_top_and_insert(cards):
    top_three_cards = cards[:3]  # 取出最上面的3张牌
    remaining_cards = cards[3:]  # 剩下的牌
    insert_index = random.randint(1, len(remaining_cards))
    shuffled_cards = remaining_cards[:insert_index] + top_three_cards + remaining_cards[insert_index:]
    return shuffled_cards

shuffled_cards = take_top_and_insert(split_cards_repeated)
print("牌堆最上的3张拿出,随机插入后的牌堆:", shuffled_cards)

牌堆最上的3张拿出,随机插入后的牌堆: [('黑桃', '8'), ('黑桃', 'A'), ('黑桃', 'A'), ('黑桃', 'K'), ('红桃', '9'), ('黑桃', 'K'), ('红桃', '9'), ('黑桃', '8')]

4. 将牌堆最上方的牌拿走,放在一旁;

python 复制代码
def take_top_card(cards):
    top_card = cards.pop(0)  # 取出最上方的牌
    return top_card

top_card = take_top_card(shuffled_cards)
print("拿走的牌:", top_card)
print("剩余的牌:", shuffled_cards)

拿走的牌: ('黑桃', '8')

剩余的牌: [('黑桃', 'A'), ('黑桃', 'A'), ('黑桃', 'K'), ('红桃', '9'), ('黑桃', 'K'), ('红桃', '9'), ('黑桃', '8')]

5. 按照南/北/不知道是南或者北方地区,判断自己属于哪一地区,并分别将牌堆最上的1/2/3,不改变顺序,并随机插入牌堆中间;

python 复制代码
def insert_cards_based_on_region(cards, region):
    if region == "南":
        insert_count = 1
    elif region == "北":
        insert_count = 2
    else:
        insert_count = 3

    top = cards[:insert_count]
    remaining_cards = cards[insert_count:]
    insert_index = random.randint(0, len(remaining_cards)-1)
    shuffled_cards = remaining_cards[:insert_index] + top + remaining_cards[insert_index:]

    return shuffled_cards


shuffled_cards_region = insert_cards_based_on_region(shuffled_cards, region)
print(f"{region}方地区插入后的牌堆:", shuffled_cards_region)

南方地区插入后的牌堆: [('黑桃', 'A'), ('黑桃', 'K'), ('红桃', '9'), ('黑桃', 'K'), ('黑桃', 'A'), ('红桃', '9'), ('黑桃', '8')]

6. 按性别男/女,从牌堆最上方拿走1/2张牌,一边念口诀:"见证奇迹的时刻",每念一个字,将牌堆最上方的牌放到牌堆最下;

python 复制代码
def take_and_chant(cards, gender, chant="见证奇迹的时刻"):
    take_count = 0
    if gender == "男":
        take_count = 1
    elif gender == "女":
        take_count = 2
    else:
        print("未知性别")

    remaining_cards = cards[take_count:]  # 剩下的牌
    print(remaining_cards)
    # 念口诀过程
    for c in chant:
        remaining_cards.append(remaining_cards.pop(0))  # 将最上方的牌放到牌堆最下

    return remaining_cards

remaining_cards= take_and_chant(shuffled_cards_region, gender, chant)
print(f"剩余的牌堆:", remaining_cards)

('黑桃', 'K'), ('红桃', '9'), ('黑桃', 'K'), ('黑桃', 'A'), ('红桃', '9'), ('黑桃', '8')

剩余的牌堆: [('红桃', '9'), ('黑桃', 'K'), ('黑桃', 'A'), ('红桃', '9'), ('黑桃', '8'), ('黑桃', 'K')]

7/8. 念口诀"好运留下米"时,将牌堆最上的牌放到牌堆最下;念"烦恼扔出去"时,将牌堆最上方的牌移除。重复这两句口诀,直到手中只有一张牌;最后留下的牌和Step 4拿走的牌是一样的。

python 复制代码
def chant_and_modify(cards):
    iter = 1
    while len(cards) > 1:
        chant_good_luck = "好运留下米"
        chant_throw_away = "烦恼扔出去"
        print(f"\n第{iter}轮口诀开始:")
        cards.append(cards.pop(0))
        print(f"口诀{chant_good_luck}结束后手上的牌:", cards)
        cards.pop(0)
        print(f"口诀{chant_throw_away}结束后手上的牌:", cards)
        iter += 1

    return cards[0]

final_card = chant_and_modify(remaining_cards)
print(f"\n最终留下的牌:{final_card}, Step 4:{top_card}")

第1轮口诀开始:

口诀好运留下米结束后手上的牌: [('黑桃', 'K'), ('黑桃', 'A'), ('红桃', '9'), ('黑桃', '8'), ('黑桃', 'K'), ('红桃', '9')]

口诀烦恼扔出去结束后手上的牌: [('黑桃', 'A'), ('红桃', '9'), ('黑桃', '8'), ('黑桃', 'K'), ('红桃', '9')]

第2轮口诀开始:

口诀好运留下米结束后手上的牌: [('红桃', '9'), ('黑桃', '8'), ('黑桃', 'K'), ('红桃', '9'), ('黑桃', 'A')]

口诀烦恼扔出去结束后手上的牌: [('黑桃', '8'), ('黑桃', 'K'), ('红桃', '9'), ('黑桃', 'A')]

第3轮口诀开始:

口诀好运留下米结束后手上的牌: [('黑桃', 'K'), ('红桃', '9'), ('黑桃', 'A'), ('黑桃', '8')]

口诀烦恼扔出去结束后手上的牌: [('红桃', '9'), ('黑桃', 'A'), ('黑桃', '8')]

第4轮口诀开始:

口诀好运留下米结束后手上的牌: [('黑桃', 'A'), ('黑桃', '8'), ('红桃', '9')]

口诀烦恼扔出去结束后手上的牌: [('黑桃', '8'), ('红桃', '9')]

第5轮口诀开始:

口诀好运留下米结束后手上的牌: [('红桃', '9'), ('黑桃', '8')]

口诀烦恼扔出去结束后手上的牌: [('黑桃', '8')]

最终留下的牌:('黑桃', '8'), Step 4:('黑桃', '8')

完整的代码

python 复制代码
import random
import itertools
import copy
# 定义扑克牌
suits = ['红桃', '方块', '梅花', '黑桃']
ranks = ['A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K']
jokers = ['小王', '大王']
deck_of_cards = list(itertools.product(suits, ranks)) + jokers
random.shuffle(deck_of_cards)       # 模拟打乱的操作
print(f"随机生成的{len(deck_of_cards)}扑克牌:", deck_of_cards)
selected_cards = random.sample(deck_of_cards, 4)
print("随机抽取其中的四张牌:", selected_cards)


# 模拟性别为男的情况
name = "夏天是冰红茶"
gender = "男"
chant = "见证奇迹的时刻"
region = "南"

# step 1: 将四张4张牌撕成两半,直接将两堆叠放;
def split_and_stack(cards):
    cards_copy = copy.copy(cards)
    merged_cards = cards + cards_copy
    return merged_cards

split_cards = split_and_stack(selected_cards)
print("撕成两半后堆叠:", split_cards)

# Step 2: 设你的姓名为n个字,重复n次,将堆在最上的牌放到最下面;
def repeat_name(cards, name):
    name_length = len(name)
    for _ in range(name_length):
        # 取出堆在最上的牌,放到最下面
        top_card = cards.pop(0)
        cards.append(top_card)
    return cards

split_cards_repeated = repeat_name(split_cards, name)
print(f"{name} 重复姓名字数次后的牌堆:", split_cards_repeated)

# Step 3: 将牌堆最上的3张拿出,不改变顺序,并随机插入牌堆中间
def take_top_and_insert(cards):
    top_three_cards = cards[:3]  # 取出最上面的3张牌
    remaining_cards = cards[3:]  # 剩下的牌
    insert_index = random.randint(1, len(remaining_cards))
    shuffled_cards = remaining_cards[:insert_index] + top_three_cards + remaining_cards[insert_index:]
    return shuffled_cards

shuffled_cards = take_top_and_insert(split_cards_repeated)
print("牌堆最上的3张拿出,随机插入后的牌堆:", shuffled_cards)

# Step 4: 将牌堆最上方的牌拿走,放在一旁
def take_top_card(cards):
    top_card = cards.pop(0)  # 取出最上方的牌
    return top_card

top_card = take_top_card(shuffled_cards)
print("拿走的牌:", top_card)
print("剩余的牌:", shuffled_cards)

# Step 5: 按照南/北/不知道是南或者北方地区,判断自己属于哪一地区,并分别将牌堆最上的1/2/3,不改变顺序,并随机插入牌堆中间
def insert_cards_based_on_region(cards, region):
    if region == "南":
        insert_count = 1
    elif region == "北":
        insert_count = 2
    else:
        insert_count = 3

    top = cards[:insert_count]
    remaining_cards = cards[insert_count:]
    insert_index = random.randint(0, len(remaining_cards)-1)
    shuffled_cards = remaining_cards[:insert_index] + top + remaining_cards[insert_index:]

    return shuffled_cards


shuffled_cards_region = insert_cards_based_on_region(shuffled_cards, region)
print(f"{region}方地区插入后的牌堆:", shuffled_cards_region)


# Step 6: 按性别男/女,从牌堆最上方拿走1/2张牌,一边念口诀:"见证奇迹的时刻",每念一个字,将牌堆最上方的牌放到牌堆最下。
def take_and_chant(cards, gender, chant="见证奇迹的时刻"):
    take_count = 0
    if gender == "男":
        take_count = 1
    elif gender == "女":
        take_count = 2
    else:
        print("未知性别")

    remaining_cards = cards[take_count:]  # 剩下的牌
    print(remaining_cards)
    # 念口诀过程
    for c in chant:
        remaining_cards.append(remaining_cards.pop(0))  # 将最上方的牌放到牌堆最下

    return remaining_cards


remaining_cards= take_and_chant(shuffled_cards_region, gender, chant)
print(f"剩余的牌堆:", remaining_cards)

# Step 7: 念口诀"好运留下米"时,将牌堆最上的牌放到牌堆最下;念"烦恼扔出去"时,将牌堆最上方的牌移除。重复这两句口诀,直到手中只有一张牌;
def chant_and_modify(cards):
    iter = 1
    while len(cards) > 1:
        chant_good_luck = "好运留下米"
        chant_throw_away = "烦恼扔出去"
        print(f"\n第{iter}轮口诀开始:")
        cards.append(cards.pop(0))
        print(f"口诀{chant_good_luck}结束后手上的牌:", cards)
        cards.pop(0)
        print(f"口诀{chant_throw_away}结束后手上的牌:", cards)
        iter += 1

    return cards[0]

# Step 8: 最后留下的牌和Step 4拿走的牌是一样的。
final_card = chant_and_modify(remaining_cards)
print(f"\n最终留下的牌:{final_card}, Step 4:{top_card}")

大家可以自己去试一试,在步骤6后男生拿走的牌总是会在对应的第5位,女生拿走的牌总是会在对应的第3位。

结语

其实说实话,这种数学魔术在我小时候买的书里就曾经看到过许多。虽然现在了解了其中的数学原理,但当时的惊奇与欢乐感觉依然难以忘怀。刘谦老师在表演中展现了非凡的技艺,不仅仅是数学的巧妙运用,更是他善于抓住观众的好奇心,创造出让人难以置信的奇迹。

相关推荐
databook16 小时前
Manim实现闪光轨迹特效
后端·python·动效
Juchecar18 小时前
解惑:NumPy 中 ndarray.ndim 到底是什么?
python
用户83562907805118 小时前
Python 删除 Excel 工作表中的空白行列
后端·python
Json_18 小时前
使用python-fastApi框架开发一个学校宿舍管理系统-前后端分离项目
后端·python·fastapi
数据智能老司机1 天前
精通 Python 设计模式——分布式系统模式
python·设计模式·架构
数据智能老司机1 天前
精通 Python 设计模式——并发与异步模式
python·设计模式·编程语言
数据智能老司机1 天前
精通 Python 设计模式——测试模式
python·设计模式·架构
数据智能老司机1 天前
精通 Python 设计模式——性能模式
python·设计模式·架构
c8i1 天前
drf初步梳理
python·django
每日AI新事件1 天前
python的异步函数
python