Python Euler 240项目:掷骰子的方法数

Python Euler 240项目:掷骰子的方法数,python,puzzle,itertools,dice,Python,Puzzle,Itertools,Dice,我试图解决: 20个12面骰子(面编号为1到12)可以通过多少种方式滚动,使前10个骰子的总数达到70? 我已经想出了解决这个问题的代码。但计算起来确实要花很多时间。我知道这种方法很糟糕。有人能建议我如何修复此代码以提高性能吗 import itertools def check(a,b): # check all the elements in a list a, are lesser than or equal to value b chk=0 for x in a:

我试图解决:

20个12面骰子(面编号为1到12)可以通过多少种方式滚动,使前10个骰子的总数达到70?

我已经想出了解决这个问题的代码。但计算起来确实要花很多时间。我知道这种方法很糟糕。有人能建议我如何修复此代码以提高性能吗

import itertools
def check(a,b):   # check all the elements in a list a, are lesser than or equal to value b
    chk=0
    for x in a:
        if x<=b:
            chk=1
    return chk

lst=[]
count=0
for x in itertools.product(range(1,13),repeat=20):
    a=sorted([x[y] for y in range(20)])
    if sum(a[-10:])==70 and check(a[:10],min(a[-10:])):
        count+=1

此解决方案应该可以工作-不确定在您的系统上需要多长时间

from itertools import product

lg = (p for p in product(xrange(1,13,1),repeat=10) if sum(p) == 70)

results = {}
for l in lg:
    results[l] = [p for p in product(xrange(1,min(l),1),repeat=10)]
它所做的是首先创建“前十名”。然后向每个“前十名”添加一个可能的“下十名”项目列表,其中最大值限制在“前十名”中的最小项目

结果是一个dict,其中
键是“前十名”,值是可能的“下十名”列表

解决方案(符合要求的组合数量)是计算所有结果dict中的列表数量,如下所示:

count = 0
for k, v in results.items():    
    count += len(v)
然后,
count
将是结果

更新

好吧,我想了一个更好的方法

from itertools import product
import math

def calc_ways(dice, sides, top, total):
    top_dice = (p for p in product(xrange(1,sides+1,1),repeat=top) if sum(p) == total)
    n_count = dict((n, math.pow(n, dice-top)) for n in xrange(1,sides+1,1))

    count = 0
    for l in top_dice:
        count += n_count[min(l)]

    return count
因为我只计算“下一个十”的长度,我想我只需要预先计算“前十”中每个“最低”数字的选项数量,所以我创建了一个字典来实现这一点。上面的代码将运行得更加流畅,因为它只由一个小字典、一个计数器和一个生成器组成。正如你所能想象的,这可能还需要很多时间。。。。但我在不到一分钟的时间里运行了第一个100万个结果。所以我确信它在可行的范围内

祝你好运:)

更新2

在你的另一次评论之后,我明白我做错了什么,并试图纠正它

from itertools import product, combinations_with_replacement, permutations
import math

def calc_ways(dice, sides, top, total):
    top_dice = (p for p in product(xrange(1,sides+1,1),repeat=top) if sum(p) == total)
    n_dice = dice-top
    n_sets = len(set([p for p in permutations(range(n_dice)+['x']*top)]))
    n_count = dict((n, n_sets*len([p for p in combinations_with_replacement(range(1,n+1,1),n_dice)])) for n in xrange(1,sides+1,1))

    count = 0
    for l in top_dice:
        count += n_count[min(l)]

    return count
你可以想象这是一场灾难,甚至没有给出正确的答案。我想我会把这个留给数学家。因为我解决这个问题的方法就是:

def calc_ways1(dice, sides, top, total):
    return len([p for p in product(xrange(1,sides+1,1),repeat=dice) if sum(sorted(p)[-top:]) == total])
这是一个优雅的单线解决方案,为
计算方式1(5,6,3,15)
提供了正确的答案,但对于
计算方式1(20,12,10,70)
问题,它需要永远的时间


不管怎么说,数学似乎是解决这个问题的方法,而不是我愚蠢的想法。

重复所有的可能性是没有用的,因为有1220=3833759992447475122176种方法可以掷出20个十二面骰子,比如说,每秒一百万个骰子,这需要数百万年才能完成

解决这类问题的方法是使用。找到一些方法把你的问题分解成几个小问题的总和,并建立一个这些子问题的答案表,直到你可以计算出你需要的结果

例如,设T(n,d,k,T)为滚动n个d边骰子的方法数,使其顶部的k和为T。我们如何将其分解为子问题?嗯,我们可以考虑骰子的数量。有nCi的方法来选择这些i骰子和T(n− i、 d− 1、…)选择n的方法− 我剩余的骰子最多只能掷d− 1.(对于k和t的一些合适的参数选择,我已经省略了。)

取这些的乘积,求出i的所有合适值,就完成了。(嗯,还没有完全完成:您必须指定基本情况,但这应该很容易。)

您需要计算的子问题数量最多为(n+1)(d+1)(k+1)(t+1),在项目Euler情况下(n=20,d=12,k=10,t=70),最多为213213。(在实践中,它远小于此,因为树的许多分支很快到达基本情况:在我的实现中,结果表明,仅791个子问题的答案就足以计算答案。)

要编写动态程序,通常最简单的方法是递归地表达它,并使用它来避免重新计算子问题的答案。在Python中,您可以使用

因此,您的程序的框架可以如下所示。我用
替换了关键的细节,这样就不会剥夺你自己解决问题的乐趣。在尝试更大的案例之前,先用小的例子(例如“两个6面骰子,上面的1和6”)检查你的逻辑是否正确

def combinations(n, k):
    """Return C(n, k), the number of combinations of k out of n."""
    c = 1
    k = min(k, n - k)
    for i in range(1, k + 1):
        c *= (n - k + i)
        c //= i
    return c

@lru_cache(maxsize=None)
def T(n, d, k, t):
    """Return the number of ways n distinguishable d-sided dice can be
    rolled so that the top k dice sum to t.

    """
    # Base cases
    if ???: return 1
    if ???: return 0

    # Divide and conquer. Let N be the maximum number of dice that
    # can roll exactly d.
    N = ???
    return sum(combinations(n, i)
               * T(n - i, d - 1, ???)
               for i in range(N + 1))
通过对所有
进行适当的选择,可以在几毫秒内解决Project Euler问题:

>>> from timeit import timeit
>>> timeit(lambda:T(20, 12, 10, 70), number=1)
0.008017531014047563
>>> T.cache_info()
CacheInfo(hits=1844, misses=791, maxsize=None, currsize=791)

你应该重新思考这个问题。您有20个插槽,您需要用1-12之间的数字填充它们,这样10个插槽的最大数字加起来就是70。我们已经可以减少这个,你有10个插槽,1-12=70。然后,对于每个解决方案,您还有10个插槽,这些插槽的排列方式可以使其值都不大于解决方案插槽中的任何值。请记住,问题是有多少种方法可以做到这一点-因此,每个解决方案10,有许多其他插槽,可以安排在许多不同的方式,。。最初,我测试了这段代码的相似谜题,其中要求5个骰子,前三个的总和应该是15,有多少这样的组合?该代码立即给出了完全正确的解决方案,即1111。现在复杂性已经增加到20个骰子和12张脸。。花了这么多时间。。我需要一种替代的方法或者不用暴力就能解决的方法。感谢Inbar的快速响应,但我尝试了这种分而治之的方法。。但是这需要很长时间…在你的系统中,你在哪里可以得到计数?我尝试了这个,它给出了错误的答案(82)。Gareth中较小的例子。。。你能试试我在主要描述中写的代码吗。它应该给你1111。导入itertools def check(a,b):a中x的chk=1:if x>b:chk=0中断返回chk lst=[]在itertools.product中x的计数=0(范围(1,6),重复=5):a=sorted([x[y]表示范围(5)],如果和(a[-3:])=70,则检查(a[:2],min(a[-10:]):计数+=1@Inbar:对于掷5个6面骰子的方法的数量,您修改后的代码仍然给出了错误的答案(82),因此前3个骰子
>>> from timeit import timeit
>>> timeit(lambda:T(20, 12, 10, 70), number=1)
0.008017531014047563
>>> T.cache_info()
CacheInfo(hits=1844, misses=791, maxsize=None, currsize=791)