一般酒吧和星星

时间:2015-03-10 13:59:11

标签: python algorithm

我有一个以Python实现的以下"bars and stars"算法,该算法将总和的所有分解打印成3个分箱,总和从0到5。 我想概括我的代码,所以它适用于N个bin(其中N小于最大值,即5)。 模式是如果你有3个箱子你需要2个嵌套循环,如果你有N个箱子你需要N-1个嵌套循环。

有人会想到写这个的通用方式,可能不会使用循环吗?

# bars and stars algorithm
N=5
for n in range(0,N):
    x=[1]*n
    for i in range(0,(len(x)+1)):
        for j in range(i,(len(x)+1)):
            print sum(x[0:i]), sum(x[i:j]), sum(x[j:len(x)])

8 个答案:

答案 0 :(得分:11)

如果这不仅仅是一次学习练习,那么您不需要使用自己的算法来生成分区:Python的标准库已经拥有您需要的大部分内容,以itertools.combinations函数的形式。

在您关联的Wikipedia page上的定理2中,有n+k-1 choose k-1种方法将n个项目划分为k个二进制位,并且该定理的证明给出了明确的解释组合和分区之间的对应关系。所以我们需要的是(1)生成这些组合的方法,以及(2)将每个组合转换为相应分区的代码。 itertools.combinations函数已经提供了第一个成分。对于第二种,每种组合给出分隔物的位置;连续分隔符位置(减1)之间的差异给出了分区大小。这是代码:

import itertools

def partitions(n, k):
    for c in itertools.combinations(range(n+k-1), k-1):
        yield [b-a-1 for a, b in zip((-1,)+c, c+(n+k-1,))]

# Example usage
for p in partitions(5, 3):
    print(p)

这是运行上述代码的输出。

[0, 0, 5]
[0, 1, 4]
[0, 2, 3]
[0, 3, 2]
[0, 4, 1]
[0, 5, 0]
[1, 0, 4]
[1, 1, 3]
[1, 2, 2]
[1, 3, 1]
[1, 4, 0]
[2, 0, 3]
[2, 1, 2]
[2, 2, 1]
[2, 3, 0]
[3, 0, 2]
[3, 1, 1]
[3, 2, 0]
[4, 0, 1]
[4, 1, 0]
[5, 0, 0]

答案 1 :(得分:3)

另一个递归变体,使用生成器函数,即不是立即打印结果,而是yield它们一个接一个地打印出来。

将循环转换为递归算法的方法如下:

  • 识别“基本情况”:当没有更多的酒吧时,只需打印星星
  • 对于第一段中的任意数量的星星,递归地确定其余的可能分区,然后将它们组合起来

您还可以将其转换为将任意序列划分为块的算法:

def partition(seq, n, min_size=0):
    if n == 0:
        yield [seq]
    else:
        for i in range(min_size, len(seq) - min_size * n + 1):
            for res in partition(seq[i:], n-1, min_size):
                yield [seq[:i]] + res

使用示例:

for res in partition("*****", 2):
    print "|".join(res)

答案 2 :(得分:2)

一步一步。

首先,删除sum()来电。我们不需要它们:

N=5
for n in range(0,N):
    x=[1]*n
    for i in range(0,(n+1)):  # len(x) == n
        for j in range(i,(n+1)):
            print i, j - i, n - j

请注意x是未使用的变量:

N=5
for n in range(0,N):
    for i in range(0,(n+1)):
        for j in range(i,(n+1)):
            print i, j - i, n - j

时间概括。上述算法适用于N星和三个条,所以我们只需要对这些条进行推广。

递归地执行此操作。对于基本情况,我们有零条或零星,这些都是微不足道的。对于递归的情况,遍历最左边的条的所有可能位置并在每种情况下递归:

from __future__ import print_function

def bars_and_stars(bars=3, stars=5, _prefix=''):
    if stars == 0:
        print(_prefix + ', '.join('0'*(bars+1)))
        return
    if bars == 0:
        print(_prefix + str(stars))
        return
    for i in range(stars+1):
        bars_and_stars(bars-1, stars-i, '{}{}, '.format(_prefix, i))

对于奖励积分,我们可以将range()更改为xrange(),但这会在您移植到Python 3时给您带来麻烦。

答案 3 :(得分:1)

这可以通过以下方法递归解决:

#n bins, k stars,
def F(n,k):
  #n bins, k stars, list holds how many elements in current assignment
  def aux(n,k,list):
        if n == 0: #stop clause
            print list
        elif n==1: #making sure all stars are distributed
            list[0] = k
            aux(0,0,list)
        else: #"regular" recursion:
            for i in range(k+1):
                #the last bin has i stars, set them and recurse
                list[n-1] = i
                aux(n-1,k-i,list)
  aux(n,k,[0]*n)

这个想法是“猜测”最后一个垃圾箱中有多少个星星,分配它们,然后递归到一个较小的问题,星星数量较少(分配的数量太多),一个垃圾箱就少了。


注意:更换线

很容易
print list

在设置每个bin中的星数时,您需要任何输出格式。

答案 4 :(得分:0)

我需要解决同样的问题并发现这篇文章,但我真的想要一个非递归的通用算法,它不依赖于itertools并且找不到它,所以想出了这个。

默认情况下,生成器以词法顺序生成序列(如前面的递归示例),但也可以通过设置“reverse”标志来生成逆序序列。

def StarsAndBars(bins, stars, reversed=False):
    if bins < 1 or stars < 1:
        raise ValueError("Number of bins and objects must both be greater than or equal to 1.")

    if bins == 1:
        yield stars,
        return

    bars = [ ([0] * bins + [ stars ], 1) ]

    if reversed:           
        while len(bars)>0:
            b = bars.pop()
            if b[1] == bins:
                yield tuple(b[0][y] - b[0][y-1] for y in range(1, bins+1))
            else:
                bar = b[0][:b[1]]
                for x in range(b[0][b[1]], stars+1):
                    newBar = bar + [ x ] * (bins - b[1]) + [ stars ]
                    bars.append( (newBar, b[1]+1) )
        bars = [ ([0] * bins + [ stars ], 1) ]
    else:
        while len(bars)>0:
            newBars = []
            for b in bars:
                for x in range(b[0][-2], stars+1):
                    newBar = b[0][1:bins] +  [ x, stars ]      
                    if b[1] < bins-1 and x > 0:
                        newBars.append( (newBar, b[1]+1) )
                    yield tuple(newBar[y] - newBar[y-1] for y in range(1, bins+1))
            bars = newBars   

答案 5 :(得分:0)

这个问题也可以通过列表理解来解决,而不是以前的答案:

from numpy import array as ar
from itertools import product

number_of_stars = M
number_of_bins = N

decompositions = ar([ar(i) for i in product(range(M+1), repeat=N) if sum(i)==M])

这里,itertools.product()生成一个列表,其中包含列表范围(M + 1)与自身的笛卡尔积,其中已应用产品(重复=)N次。 if语句删除数字不等于星数的组合,例如,其中一个组合为0,0为0或[0,0,0]。 如果我们对列表列表感到满意,那么我们可以简单地删除np.array()(为简洁起见,在示例中)。以下是3个箱中3个星的示例输出:

array([[0, 0, 3],
       [0, 1, 2],
       [0, 2, 1],
       [0, 3, 0],
       [1, 0, 2],
       [1, 1, 1],
       [1, 2, 0],
       [2, 0, 1],
       [2, 1, 0],
       [3, 0, 0]])

我希望这个答案有所帮助!

答案 6 :(得分:0)

由于我发现大多数答案中的代码都很难遵循,即问自己所显示的算法与星条旗的实际问题的关系如何,让我们逐步进行操作:

首先,我们定义一个函数,以在给定位置|的字符串stars中插入条形p

def insert_bar(stars, p):
    head, tail = stars[:p], stars[p:]
    return head + '|' + tail

用法:

insert_bar('***', 1) # returns '*|**'

要在不同位置插入多个条,例如(1,3)的一种简单方法是使用reduce中的functools

reduce(insert_bar, (1,3), '***') # returns '*|*|*'

如果我们分支insert_bar的定义来处理这两种情况,我们将获得一个不错的并且可重用的函数,可以将任意数量的条形插入一串星星

def insert_bars(stars, p):
    if type(p) is int:
        head, tail = stars[:p], stars[p:]
        return head + '|' + tail
    else:
        return reduce(insert_bar, p, stars)

正如@Mark Dickinson在他的回答itertools.combinations中所解释的,我们可以产生 n + k-1选择k-1 条形位置的组合。

现在剩下要做的是创建一个长度为'*'的字符串n,在给定位置插入小节,在小节处拆分字符串,并计算每个结果仓的长度。因此,以下实现实际上是将问题陈述逐字翻译为代码

def partitions(n, k):
    for positions in itertools.combinations(range(n+k-1), k-1):
       yield [len(bin) for bin in insert_bars(n*"*", positions).split('|')]

答案 7 :(得分:0)

寻找k = 2的特定情况的任何人都可以通过简单地创建范围并将其反向堆叠来节省大量时间。比较和接受答案。

n = 500000

%timeit np.array([[i,j] for i,j in partitions(n,2)])

>>> 396 ms ± 13.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
rng = np.arange(n+1)
np.vstack([rng, rng[::-1]]).T

>>> 2.91 ms ± 190 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

它们确实是等效的。

it2k = np.array([[i,j] for i,j in partitions(n,2)])
rng = np.arange(n+1)
np2k = np.vstack([rng, rng[::-1]]).T

(np2k == it2k).all()

>>> True