简体   繁体   English

如何优化数字排列以提高效率

[英]How to refine number permutations for efficiency

I am working on a dice probability program and have been running into some efficiency issues in the permutation section when the numbers get big. 我正在开发一个骰子概率程序,当数字变大时,在置换部分遇到了一些效率问题。 For example, the perimeters I am required to run are 10 dice, with 10 sides, with an outcome of 50. 例如,我需要运行的周长是10个骰子,带有10个边,结果为50。

I require a total number of permutations to calculate the probability of the specified outcome given the number of dice and number of sides. 给定骰子数和边数,我需要排列的总数来计算指定结果的概率。 The final_count(total, dice, faces) function lets the least number of combinations pass from the generator before moving into the perms(x) function. final_count(total, dice, faces)函数使生成器传递最少数量的组合,然后再进入perms(x)函数。

The following code works, but for the previously mentioned perimeters it takes an extremely long time. 以下代码有效,但是对于前面提到的边界,它需要花费很长时间。

The perms(x) was posted by @Ashish Datta from this thread: permutations with unique values Which is where I believe I need help. perms(x)是@Ashish Datta在此线程发布的: 具有唯一值的排列,这是我认为需要帮助的地方。

import itertools as it

total = 50
dice = 10
faces = 10

#-------------functions---------------------

# Checks for lists of ALL the same items
def same(lst):
   return lst[1:] == lst[:-1]

# Generates the number of original permutations (10 digits takes 1.65s)
def perms(x):
    uniq_set = set()
    for out in it.permutations(x, len(x)):
        if out not in uniq_set:
            uniq_set.update([out])
    return len(uniq_set)


# Finds total original dice rolls.  "combinations" = (10d, 10f, 50t, takes 0.42s)
def final_count(total, dice, faces):
    combinations = (it.combinations_with_replacement(range(1, faces+1), dice))
    count = 0
    for i in combinations:
        if sum(i) == total and same(i) == True:
            count += 1
        elif sum(i) == total and same(i) != True:
            count += perms(i)
        else:
            pass
    return count

# --------------functions-------------------

answer = final_count(total, dice, faces) / float(faces**dice)

print(round(answer,4))

I have read the thread How to improve permutation algorithm efficiency with python . 我已经阅读了如何使用python提高排列算法效率的线程。 I believe my question is different, though a smarter algorithm is my end goal. 我相信我的问题有所不同,尽管更智能的算法是我的最终目标。

I originally posted my first draft of this program in CodeReview. 我最初在CodeReview中发布了该程序的初稿。 https://codereview.stackexchange.com/questions/212930/calculate-probability-of-dice-total . https://codereview.stackexchange.com/questions/212930/calculate-probability-of-dice-total I realize I am walking a fine line between a question and a code review, but I think in this case, I am more on the question side of things :) 我意识到我在问题和代码审查之间走了一条很好的界限,但是我认为在这种情况下,我更多地是在问题方面:)

You can use a function that deducts the current dice rolls from the totals for the recursive calls, and short-circuit the search if the total is less than 1 or greater than the number of dices times the number of faces. 您可以使用一个从递归调用总数中减去当前骰子掷骰的函数,如果总数小于1或大于骰子数乘以面数,则可以缩短搜索距离。 Use a cache to avoid redundant calculations of the same parameters: 使用高速缓存来避免对相同参数的重复计算:

from functools import lru_cache
@lru_cache(maxsize=None)
def final_count(total, dice, faces):
    if total < 1 or total > dice * faces:
        return 0
    if dice == 1:
        return 1
    return sum(final_count(total - n, dice - 1, faces) for n in range(1, faces + 1))

so that: 以便:

final_count(50, 10, 10)

returns within a second: 374894389 一秒钟内返回: 374894389

I had a similar solution to blhsing but he beat me to it and, to be honest I didn't think of using lru_cache (nice! +1 for that). 我有类似的解决方法,但他击败了我,老实说,我没想到要使用lru_cache(不错!+1)。 I'm posting it anyhow if only to illustrate how storage of previously computed counts cuts down on the recursion. 无论如何,我都将其发布,只是为了说明存储先前计算的计数如何减少递归。

def permutationsTo(target, dices, faces, computed=dict()):
    if target > dices*faces or target < 1: return 0 
    if dices == 1 :                        return 1
    if (target,dices) in computed: return computed[(target,dices)]
    result = 0 
    for face in range(1,min(target,faces+1)):
         result += permutationsTo(target-face,dices-1,faces,computed)
    computed[(target,dices)] = result
    return result  

One way to greatly reduce the time is to mathematically count how many combinations there are for each unique group of numbers in combinations , and increment count by that amount. 大大减少时间的一种方法是,以数学方式计算组合中每个唯一数字组的combinations数量,然后以该数量递增count If you have a list of n objects where x1 of them are all alike, x2 of them are all alike, etc., then the total number of ways to arrange them is n!/(x1! x2! x3! ...). 如果您有n个对象的列表,其中x1都相似,x2都相似,依此类推,那么布置它们的方式总数为n!/(x1!x2!x3!...) 。 For example, the number of different ways to arrange the letters of "Tennessee" is 9!/(1! 4! 2! 2!). 例如,排列“田纳西”字母的不同方式的数目是9!/(1!4!2!2!)。 So you can make a separate function for this: 因此,您可以为此设置一个单独的函数:

import math
import itertools as it
import time

# Count the number of ways to arrange a list of items where
# some of the items may be identical.
def indiv_combos(thelist):
    prod = math.factorial(len(thelist))
    for i in set(thelist):
        icount = thelist.count(i)
        prod /= math.factorial(icount)
    return prod

def final_count2(total, dice, faces):
    combinations = it.combinations_with_replacement(range(1, faces + 1), dice)
    count = 0
    for i in combinations:
        if sum(i) == total:
            count += indiv_combos(i)
    return count

I don't know off-hand if there's already some built-in function that does the job of what I wrote as indiv_combos2 , but you could also use Counter to do the counting and mul to take the product of a list: 我不知道离手,如果已经有一些内置的功能,做的是我写的作业indiv_combos2 ,但你也可以使用Counter做计数和mul采取列表的产品:

from operator import mul
from collections import Counter

def indiv_combos(thelist):
    return math.factorial(len(thelist)) / reduce(mul, [math.factorial(i) for i in Counter(thelist).values()],1)

I get mixed results on the times when I try both methods with (25, 10, 10) as the input, but both give me the answer in less than 0.038 seconds every time. 当我尝试使用(25, 10, 10)作为输入的两种方法时,我得到的结果是混合的,但是每次都在不到0.038秒的时间内给了我答案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM