# 【Python CheckiO 题解】Probably Dice

CheckiO 是面向初学者和高级程序员的编码游戏，使用 Python 和 JavaScript 解决棘手的挑战和有趣的任务，从而提高你的编码技能，本博客主要记录自己用 Python 在闯关时的做题思路和实现代码，同时也学习学习其他大神写的代码。

CheckiO 官网：https://checkio.org/

CheckiO 题解系列专栏：https://itrhx.blog.csdn.net/category_9536424.html

CheckiO 所有题解源代码：https://github.com/TRHX/Python-CheckiO-Exercise

## 题目描述

【Probably Dice】：计算掷骰子命中点数的概率， 给定三个参数：骰子数，每个骰子的面数，要计算概率的目标数，掷 n 个骰子，将每个骰子的点数加起来，若点数和与目标数相同，则表示命中，计算所有的情况中，命中的概率，结果的精度应为四位数，即 ±0.0001，例如：掷出 2 个 6 面的骰子，则点数和为 3 的概率为 2/36 或 5.56％，您应该返回 0.0556。

【链接】https://py.checkio.org/mission/probably-dice/

【输入】：三个参数：骰子数，每个骰子的面数，要计算概率的目标数，均为整数

【输出】：命中的概率，浮点数

【前提】：1 ≤ dice_number ≤ 10；2 ≤ sides ≤ 20；0 ≤ target < 1000

【范例】

probability(2, 6, 3) == 0.0556  # 2 six-sided dice have a 5.56% chance of totalling 3
probability(2, 6, 4) == 0.0833
probability(2, 6, 7) == 0.1667
probability(2, 3, 5) == 0.2222  # 2 three-sided dice have a 22.22% chance of totalling 5
probability(2, 3, 7) == 0       # The maximum you can roll on 2 three-sided dice is 6
probability(3, 6, 7) == 0.0694
probability(10, 10, 50) == 0.0375


## 代码实现

def probability(dice_number, sides, target):
dic = {}
def calculation(dice_number, sides, target):
if dice_number > target or dice_number * sides < target:
return 0
if dice_number == 1:
return 1
if (dice_number, sides, target) in dic:
return dic[(dice_number, sides, target)]
else:
dic[(dice_number, sides, target)] = sum(calculation(dice_number - 1, sides, target - i) for i in range(1, sides + 1))
return dic[(dice_number, sides, target)]
return calculation(dice_number, sides, target) / sides ** dice_number

if __name__ == '__main__':
#These are only used for self-checking and are not necessary for auto-testing
def almost_equal(checked, correct, significant_digits=4):
precision = 0.1 ** significant_digits
return correct - precision < checked < correct + precision

assert(almost_equal(probability(2, 6, 3), 0.0556)), "Basic example"
assert(almost_equal(probability(2, 6, 4), 0.0833)), "More points"
assert(almost_equal(probability(2, 6, 7), 0.1667)), "Maximum for two 6-sided dice"
assert(almost_equal(probability(2, 3, 5), 0.2222)), "Small dice"
assert(almost_equal(probability(2, 3, 7), 0.0000)), "Never!"
assert(almost_equal(probability(3, 6, 7), 0.0694)), "Three dice"
assert(almost_equal(probability(10, 10, 50), 0.0375)), "Many dice, many sides"


## 大神解答

#### 大神解答 NO.1

from numpy.polynomial.polynomial import polypow

def probability(dice_number, sides, target):
""" The number of ways to obtain x as a sum of n s-sided dice
is given by the coefficients of the polynomial:

f(x) = (x + x^2 + ... + x^s)^n
"""

# power series (note that the power series starts from x^1, therefore
# the first coefficient is zero)
powers = [0] + [1] * sides
# f(x) polynomial, computed used polypow in numpy
poly = polypow(powers, dice_number)

# check if the target is in valid range
# if an IndexError is raised, it means that the target cannot be reached,
# therefore the probability is 0
try:
return poly[target] / sides ** dice_number
except IndexError:
return 0


#### 大神解答 NO.2

from functools import lru_cache

@lru_cache(maxsize=None)
def probability(dice_number, sides, target):
if dice_number == 1:
return (1 <= target <= sides**dice_number)/sides
return sum([probability(dice_number-1, sides, target-x)
for x in range(1, sides+1)])/sides


#### 大神解答 NO.3

from scipy.special import binom as b
probability=lambda n,s,p:sum((-1)**x*b(n, x)*\
b(p-s*x-1,n-1)for x in range((p-n)//s+1))/s**n


#### 大神解答 NO.4

probability,C=lambda n,s,t:sum((-1)**k*C(n,k)*C(t-k*s-1,n-1)for k
in range(1+(t-n)//s))/s**n,lambda n,k:n*C(n-1,k-1)//k if k else 1


©️2019 CSDN 皮肤主题: 数字20 设计师: CSDN官方博客