Python 用模式划分整数

Python 用模式划分整数,python,list,algorithm,performance,Python,List,Algorithm,Performance,我知道划分一个整数的问题已经很老了,这里有很多关于它的问题和答案,但是在广泛搜索之后,我还没有找到我想要的东西。公平地说,我的解决方案并不太糟糕,但我想知道是否有一种更快/更好的方法可以做到以下几点: 我需要将一个整数划分为一个固定长度的分区,该分区可能包含值0,其中分区中的每个“位置”都有一个最大可能值。例如: 列表(分区(编号=5,最大值=(1,0,3,4))) [(1, 0, 3, 1), (1, 0, 2, 2), (1, 0, 0, 4), (1, 0, 1, 3), (0, 0, 1

我知道划分一个整数的问题已经很老了,这里有很多关于它的问题和答案,但是在广泛搜索之后,我还没有找到我想要的东西。公平地说,我的解决方案并不太糟糕,但我想知道是否有一种更快/更好的方法可以做到以下几点:

我需要将一个整数划分为一个固定长度的分区,该分区可能包含值0,其中分区中的每个“位置”都有一个最大可能值。例如:

列表(分区(编号=5,最大值=(1,0,3,4))) [(1, 0, 3, 1), (1, 0, 2, 2), (1, 0, 0, 4), (1, 0, 1, 3), (0, 0, 1, 4), (0, 0, 2, 3), (0, 0, 3, 2)] 我的解决方案如下:

从集合导入计数器
从itertools导入组合
def分区(编号:int,最大值:tuple):
S=集合(组合((k代表i,枚举中的val(max_vals)代表k代表[i]*val),数字))
对于s中的s:
c=计数器
产量元组([c[n]表示范围内的n(len(max_vals))]))
基本上,我首先为每个插槽创建“令牌”,然后组合正确数量的令牌,最后计算每个插槽有多少令牌


我并不特别喜欢为每个分区实例化一个
计数器
,但我最不喜欢的是
组合
生成的元组比需要的要多得多,然后我使用
set()
丢弃所有重复的元组,这似乎效率很低。有更好的方法吗?

尽管必须有更好的算法,但一个相对简单和更快的解决方案将是:

性能:

>>> %timeit list(partition(number = 15, max_vals = (1,0,3,4)*3))
155 ms ± 681 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

>>> %timeit list(partition_2(number = 15, max_vals = (1,0,3,4)*3))
14.7 ms ± 763 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
################################################################################
>>> %timeit list(partition(number = 5, max_vals = (10,20,30,10,10)))
1.17 s ± 26.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

>>> %timeit list(partition_2(number = 5, max_vals = (10,20,30,10,10)))
1.21 ms ± 28.6 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
#################################################################################
>>> %timeit list(partition_2(number = 35, max_vals = (8,9,10,11,12)))
23.2 ms ± 697 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

>>> %timeit list(partition(number = 35, max_vals = (8,9,10,11,12)))
# Will update when/if it finishes :)

递归函数通常是处理此类问题的一种优雅方式:

def partition(N,slots):
    if len(slots)==1:
        if slots[0]>=N: yield [N]
        return
    for s in range(min(N,slots[0])+1):
        yield from ([s]+p for p in partition(N-s,slots[1:]))

                    
for part in partition(5,[1,0,3,4]): print(part)
[0, 0, 1, 4]
[0, 0, 2, 3]
[0, 0, 3, 2]
[1, 0, 0, 4]
[1, 0, 1, 3]
[1, 0, 2, 2]
[1, 0, 3, 1]    
这可以通过检查每个递归级别上的剩余空间以及当剩余插槽不足以扩展数量时的短路遍历来进一步优化:

def partition(N,slots,space=None):
    if space is None: space = sum(slots)
    if N>space: return
    if len(slots)==1:
        if slots[0]>=N: yield [N]
        return
    for s in range(min(N,slots[0])+1):
        yield from ([s]+p for p in partition(N-s,slots[1:],space-slots[0]))
在解决方案数量少于所有插槽的完整产品的情况下,此优化可提高性能。在大多数时隙组合工作的情况下,它比迭代要慢

from timeit import timeit

t = timeit(lambda:list(partition(45,(8,9,10,11,12))),number=1)
print(t) # 0.000679596

t = timeit(lambda:list(partition_2(45,(8,9,10,11,12))),number=1)
print(t) # 0.027492302 (Sayandip's)


t = timeit(lambda:list(partition(15,(1,0,3,4)*3)),number=1)
print(t) # 0.024383259

t = timeit(lambda:list(partition_2(15,(1,0,3,4)*3)),number=1)
print(t) # 0.018362536
为了从递归方法中系统地获得更好的性能,我们需要限制递归的深度。这可以通过以不同的方式处理问题来实现。如果我们将插槽分成两组,并确定两个组合插槽(左侧和右侧)之间的分布,那么我们可以在每一侧应用分区并组合结果。这将只递归到Log2N的深度,并将大数据块组合在一起,而不是一次只添加一个值:

from itertools import product
def partition(N,slots,space=None):
    if space is not None and N>space: return
    if len(slots)==1:
        if slots[0]>=N: yield [N]
        return
    if len(slots)==2:
        for left in range(max(0,N-slots[1]),min(N,slots[0])+1):
            yield [left,N-left]
        return
    leftSlots  = slots[:len(slots)//2]
    rightSlots = slots[len(slots)//2:]
    leftSpace,rightSpace = sum(leftSlots),sum(rightSlots)
    for leftN,rightN in partition(N,[leftSpace,rightSpace],leftSpace+rightSpace):
        partLeft  = partition(leftN,  leftSlots,  leftSpace)
        partRight = partition(rightN, rightSlots, rightSpace)
        for leftSide,rightSide in product(partLeft,partRight):
            yield leftSide+rightSide
在所有情况下,性能改进都是系统化的:

t = timeit(lambda:list(partition(45,(8,9,10,11,12))),number=1)
print(t) # 0.00017742

t = timeit(lambda:list(partition_2(45,(8,9,10,11,12))),number=1)
print(t) # 0.02895038


t = timeit(lambda:list(partition(15,(1,0,3,4)*3)),number=1)
print(t) # 0.00338676

t = timeit(lambda:list(partition_2(15,(1,0,3,4)*3)),number=1)
print(t) # 0.02025453

您的代码似乎没有处理“includes the value 0”约束。也许我的措辞令人困惑。我的意思是0是分区中的一个可能值,如示例中所示。听起来你应该说“可能包含值0”,而不是“包含值0”。噢,谢谢。固定的。
t = timeit(lambda:list(partition(45,(8,9,10,11,12))),number=1)
print(t) # 0.00017742

t = timeit(lambda:list(partition_2(45,(8,9,10,11,12))),number=1)
print(t) # 0.02895038


t = timeit(lambda:list(partition(15,(1,0,3,4)*3)),number=1)
print(t) # 0.00338676

t = timeit(lambda:list(partition_2(15,(1,0,3,4)*3)),number=1)
print(t) # 0.02025453