经常在直播中刷到对对碰游戏,好奇概率是怎么分布的,故用python做了个模拟实验。
实验假设:有一大箱含有type_num种颜色的球,现在要随机拿出initial_draws个,每当拿出的球中出现1对相同颜色的2个球就把这2个球放到一边并重新从箱子里拿出1个球(如果出现2对相同颜色的2个球则放一边并重新拿2个球,以此类推),若出现3个一样颜色的球则随机放2个球到一边并重新拿1个球,与此同时可以指定lucky_color_num个幸运颜色,当拿出指定的幸运颜色时,可以再拿1个球,最后所有拿出的球中没有重复的球时过程结束,进行100000次实验模拟最后总共拿出球的可能个数及其对应概率。
以下为实现代码:
可自行设定type_num、initial_draws、lucky_color_num参数得到拿球平均数、众数、中位数及可视化分布图;也可设定ball_cost、transport_cost、other_cost参数计算最终成本。
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
@File : ball_experiment.py
@Author : ggb
@Date : 2024/9/26 14:08
@Desc : 拿球模拟实验
"""
import random
from collections import Counter
import tqdm
import matplotlib.pyplot as plt
def calculate_median(data):
""" 计算中位数 """
data_sorted = sorted(data) # 将数据按大小顺序排列
n = len(data)
if n % 2 == 1: # 如果数据个数为奇数
median = data_sorted[n // 2]
else: # 如果数据个数为偶数
median = (data_sorted[n // 2 - 1] + data_sorted[n // 2]) / 2
return median
def plot_results(result_dict, simulation_trials):
""" 画出柱状图 """
# 设置中文字
plt.rcParams['font.sans-serif'] = ['SimHei'] # SimHei是黑体
plt.rcParams['axes.unicode_minus'] = False # 解决负号'-'显示为方块的问题
labels, counts = zip(*result_dict.items())
plt.figure(figsize=(10, 6))
plt.bar(labels, counts, width=0.8, color='skyblue')
plt.xlabel('拿球次数')
plt.ylabel('频率')
plt.title('次数分布')
plt.xticks(range(min(labels), max(labels) + 1, max(len(labels) // 10, 1))) # 设置横坐标的显示间隔
plt.show()
def with_lucky_color(type_num, initial_draws, simulation_trials, lucky_color_num):
""" 有幸运颜色 """
def process_balls(current_balls, colors):
""" 碰球 """
counts = Counter(current_balls)
new_balls = []
total_draws = 0
for color, count in counts.items():
if count == 1: # 将不重复的球加入新球列表
new_balls.append(color)
elif count >= 2: # 每找出一对相同颜色的球,加入一次新球
pairs = count // 2
new_balls.extend([color] * (count % 2)) # 保留多余的一个球,也可以用new_balls.append(color)
total_draws += pairs
for _ in range(pairs): # 对于每对相同颜色的球,加入新的球
new_balls.append(random.choice(colors))
return new_balls, total_draws
def simulate_draws_with_lucky(colors, initial_draws, lucky_colors):
total_draws = 0
current_balls = random.choices(colors, k=initial_draws)
total_draws += len(current_balls)
# 继续直到所有球颜色不重复
while len(set(current_balls)) != len(current_balls):
lucky_count = sum(color in lucky_colors for color in current_balls) # 检查当前球中有多少个幸运色
if lucky_count > 0:
for _ in range(lucky_count): # 根据幸运色数量增加相应数量的新球
new_ball = random.choice(colors)
current_balls.append(new_ball)
total_draws += 1
# 处理碰球逻辑
current_balls, new_draws = process_balls(current_balls, colors)
total_draws += new_draws
return total_draws
colors = list(f'col{i + 1}' for i in range(type_num))
lucky_colors = random.sample(colors, lucky_color_num) # 随机选择幸运颜色
results = []
for _ in tqdm.tqdm(range(simulation_trials)):
results.append(simulate_draws_with_lucky(colors, initial_draws, lucky_colors))
average_draws = sum(results) / simulation_trials
print(f"幸运颜色: {lucky_colors}")
print(f"平均拿球数量: {average_draws}")
result_dict = Counter(results)
for key in result_dict: # 替换次数为比例
result_dict[key] = round((result_dict[key] / simulation_trials) * 100, 2)
print(f'拿球情况:{result_dict}')
# 找出最大、最小拿球数
max_key = max(result_dict.keys())
min_key = min(result_dict.keys())
max_value = result_dict[max_key]
min_value = result_dict[min_key]
print(f"最大拿球数是: {max_key}, 占比是: {max_value}")
print(f"最小拿球数是: {min_key}, 占比是: {min_value}")
# 计算众数和中位数
mode = [k for k, v in result_dict.items() if v == max(result_dict.values())]
median = calculate_median(results)
print(f'众数: {mode}')
print(f'中位数: {median}')
# 画图
# plot_results(result_dict, simulation_trials)
return average_draws
if __name__ == "__main__":
simulation_trials = 100000 # 模拟实验次数
type_num = 8 # 颜色种类数
initial_draws = 8 # 拿出数量
lucky_color_num = 2 # 幸运色数量
ball_cost = 3 # 每件成本
transport_cost = 4 # 运输成本
other_cost = 2 # 其他成本
average_draws = with_lucky_color(type_num, initial_draws, simulation_trials, lucky_color_num) # 有幸运色
total_cost = average_draws * ball_cost + transport_cost + other_cost # 全部成本
print(f'全部成本为:{total_cost}')