代码需要大量时间才能大量运行

时间:2018-09-27 22:58:20

标签: python algorithm

鉴于棋盘(例如拼字游戏或国际象棋棋盘)中的方块数,N和尺寸AxB,此代码尝试确定所有可能的 尺寸组合可以在木板上给出N个正方形。

示例:N = 8

有四种可能的尺寸组合,可以在板上精确地获得8个正方形。因此,代码输出板的尺寸 1x8 2x3、3x2和8x1。 8x1板有8个1x1正方形; 3x2板有六个1x1正方形和两个2x2正方形。

这是我的解决方法:

def dims(num_sqrs):
    dim_list=[]
    for i in range(1,num_sqrs+1):
        temp = []
        for x in range(1,num_sqrs+1):
            res = 0
            a = i
            b = x
            while (a != 0) and (b !=0):
                res = res + (a*b)
                a = a -1
                b = b-1
            if res == num_sqrs:
                dim_list.append((i,x))
    print(dim_list)

dims(8)

但是,对于较大的N值,此代码需要太多时间才能运行。 任何优化代码效率的建议将不胜感激。

4 个答案:

答案 0 :(得分:2)

我认为关键的细节是@Qudus正在寻找存在任意大小N个正方形的木板。

一种简单的优化方法是在res > n时中断。使它快两倍的另一种优化方法是仅在长度> =宽度的板上运行它。

def dims(num_sqrs):
    dim_list=[]
    for i in range(1, num_sqrs + 1):
        temp = []
        for x in range(1, i + 1):
            res = 0
            a = i
            b = x
            while (a != 0) and (b != 0):
                res = res + (a * b)
                a = a - 1
                b = b - 1
                if res > num_sqrs:
                    break
            if res == num_sqrs:
                dim_list.append((i, x))
                if i != x:
                    dim_list.append((x, i))
    print(dim_list)

这是采用不同方法的更快解决方案:

def dims(num_sqrs):
    dim_list = []
    sum_squares = [0]
    sums = [0] 
    for i in range(1, num_sqrs + 1):
        sums.append(sums[-1] + i)
        sum_squares.append(sum_squares[-1] + i * i)

    for i in range(1, num_sqrs + 1):
        if sum_squares[i] > num_sqrs:
            break
        if sum_squares[i] == num_sqrs:
            dim_list.append((i, i))
            break
        for x in range(i + 1, num_sqrs + 1):
            total_squares = sum_squares[i] + sums[i] * (x - i)
            if total_squares == num_sqrs:
                dim_list.append((x, i))
                dim_list.append((i, x))
                break
            if total_squares > num_sqrs:
                break
    return dim_list

答案 1 :(得分:2)

以下是两个非常明显的观察结果:

  1. AxB的平方数与BxA的平方数

  2. 如果C> B,则AxC的平方数大于AxB的平方数

鉴于这些事实,应该明确:

  1. 我们只需要考虑A≤B的AxB,因为只要A≠B,我们就可以将BxA添加到列表中

  2. 对于给定的A和N,最多有一个B的值,其平方数为N。

以下代码基于上面的代码。它依次尝试每个AxA,对于每个检查,看是否有一些B≥A产生正确的平方数。当AxA的平方数超过N时,它将停止。

现在,要找到正确的B值,一些不那么明显的观察结果。

  1. 假设AxA的平方数为N。那么(A + 1)x(Ax1)的平方数为N +(A + 1)²。

    证明:AxA中的每个正方形都可以通过其左上角坐标[ i j ]和大小 s 来识别。我将其写为[ s :* i, j ]。 (这里我假设坐标是从零开始的,并且从上到下,从左到右。)

    对于每个这样的平方0≤ i + s j + s

    现在,假设我们将每个正方形[ s i j ]更改为基于相同坐标但大小相同的正方形一个更大的[ s +1: i j ]。该新正方形是(A + 1)x(A + 1)中的正方形,因为0≤ i + s + 1 j )。这样,变换就给我们A + 1中每个大小至少为2的正方形。我们遗漏的唯一正方形是大小为1的正方形,并且正好有(A + 1)×(A + 1)个正方形

  2. 假设AxB的平方数为N,且B≥A。那么Ax(B + 1)的平方数是N +从1到A的每个整数的和。(这些是三角形的数字,它们是A×(A + 1)/ 2;我认为这是众所周知的。 )

    证明:Ax(B + 1)中的正方形正好是AxB中的正方形加上其右侧包括Ax(B + 1)的最后一列的正方形。因此,我们只需要计算这些。有一个这样的大小为A的正方形,两个为大小为A-1的正方形,三个为大小为A-2的正方形,依此类推,直到大小为1的A正方形。

因此,对于给定的A,我们可以计算AxA的平方数以及B中每次增加的平方数的增量。如果增量将目标计数和AxA的计数之间的差除以,则我们我们找到了AxB。

下面的程序还依赖于另一种代数恒等式,这很简单:两个连续三角形的和是一个平方。仅通过排列两个三角形就可以明显看出这一点。较大的一个包含正方形的对角线。这些事实用于计算下一个基本值,并为下一个A值递增。

def finds(n):
  a = 1
  base = 1   # Square count for AxA
  inc = 1    # Difference between count(AxB) and count(AxB+1)
  rects = []
  while base < n:
    if (n - base) % inc == 0:
      rects.append((a, a + (n - base) // inc))
    a += 1
    newinc = inc + a
    base += inc + newinc
    inc = newinc
  if base == n:
    return rects + [(a, a)] + list(map(lambda p:p[::-1], reversed(rects)))
  else:
    return rects + list(map(lambda p:p[::-1], reversed(rects)))

该功能最慢的部分是最后添加与AxB解决方案相反的内容,我这样做只是为了简化对解决方案的正确计数。我的第一次尝试几乎快两倍,它使用循环while base <= n,然后返回rects。但这仍然足够快。

例如:

>>> finds(1000000)
[(1, 1000000), (4, 100001), (5, 66668), (15, 8338), (24, 3341),
 (3341, 24), (8338, 15), (66668, 5), (100001, 4), (1000000, 1)]

>>> finds(760760)
[(1, 760760), (2, 253587), (3, 126794), (4, 76077), (7, 27172),
 (10, 13835), (11, 11530), (12, 9757), (13, 8364), (19, 4010),
 (20, 3629), (21, 3300), (38, 1039), (39, 988), (55, 512),
 (56, 495), (65, 376), (76, 285), (285, 76), (376, 65),
 (495, 56), (512, 55), (988, 39), (1039, 38), (3300, 21),
 (3629, 20), (4010, 19), (8364, 13), (9757, 12), (11530, 11),
 (13835, 10), (27172, 7), (76077, 4), (126794, 3), (253587, 2), 
 (760760, 1)]

The last one came out of this test, which took a few seconds: (It finds each successive maximum number of solutions, if you don't feel like untangling the functional elements)

>>> from functools import reduce
>>> print('\n'.join(
      map(lambda l:' '.join(map(lambda ab:"%dx%d"%ab, l)),
          reduce(lambda a,b: a if len(b) <= len(a[-1]) else a + [b],
                 (finds(n) for n in range(2,1000001)),[[(1,1)]])))) 
1x1
1x2 2x1
1x5 2x2 5x1
1x8 2x3 3x2 8x1
1x14 2x5 3x3 5x2 14x1
1x20 2x7 3x4 4x3 7x2 20x1
1x50 2x17 3x9 4x6 6x4 9x3 17x2 50x1
1x140 2x47 3x24 4x15 7x7 15x4 24x3 47x2 140x1
1x280 4x29 5x20 6x15 7x12 12x7 15x6 20x5 29x4 280x1
1x770 2x257 3x129 4x78 10x17 11x15 15x11 17x10 78x4 129x3 257x2 770x1
1x1430 2x477 3x239 4x144 10x29 11x25 12x22 22x12 25x11 29x10 144x4 239x3 477x2 1430x1
1x3080 2x1027 3x514 4x309 7x112 10x59 11x50 20x21 21x20 50x11 59x10 112x7 309x4 514x3 1027x2 3080x1
1x7700 2x2567 3x1284 4x771 7x277 10x143 11x120 20x43 21x40 40x21 43x20 120x11 143x10 277x7 771x4 1284x3 2567x2 7700x1
1x10010 2x3337 3x1669 4x1002 10x185 11x155 12x132 13x114 20x54 21x50 50x21 54x20 114x13 132x12 155x11 185x10 1002x4 1669x3 3337x2 10010x1
1x34580 2x11527 3x5764 4x3459 7x1237 12x447 13x384 19x188 20x171 38x59 39x57 57x39 59x38 171x20 188x19 384x13 447x12 1237x7 3459x4 5764x3 11527x2 34580x1
1x40040 2x13347 3x6674 4x4005 7x1432 10x731 11x610 12x517 13x444 20x197 21x180 39x64 64x39 180x21 197x20 444x13 517x12 610x11 731x10 1432x7 4005x4 6674x3 13347x2 40040x1
1x100100 2x33367 3x16684 4x10011 7x3577 10x1823 11x1520 12x1287 13x1104 20x483 21x440 25x316 39x141 55x83 65x68 68x65 83x55 141x39 316x25 440x21 483x20 1104x13 1287x12 1520x11 1823x10 3577x7 10011x4 16684x3 33367x2 100100x1
1x340340 2x113447 3x56724 4x34035 7x12157 10x6191 11x5160 12x4367 13x3744 20x1627 21x1480 34x583 39x449 55x239 65x180 84x123 123x84 180x65 239x55 449x39 583x34 1480x21 1627x20 3744x13 4367x12 5160x11 6191x10 12157x7 34035x4 56724x3 113447x2 340340x1
1x760760 2x253587 3x126794 4x76077 7x27172 10x13835 11x11530 12x9757 13x8364 19x4010 20x3629 21x3300 38x1039 39x988 55x512 56x495 65x376 76x285 285x76 376x65 495x56 512x55 988x39 1039x38 3300x21 3629x20 4010x19 8364x13 9757x12 11530x11 13835x10 27172x7 76077x4 126794x3 253587x2 760760x1

答案 2 :(得分:1)

从基本的代数分析开始。我为各种大小的总和得出了自己的公式。从最初的分析中,我们得到一个大小为n x m的板,存在(n-k)*(m-k)个正方形,大小为k。对于[0,min(m,n)]中的k求和,我们有一个简单的计算公式:

sum(((n-k) * (m-k) for k in range(0, min(n, m))))

我将乘积扩展为nm - k(n+m) + k^2,重新推导各个系列的总和,并假设n <= m:

得出一个非迭代公式。
n * n * m 
- n * (n - 1) / 2 * (n + m)
+ ((n - 1) * n * (2 * n - 1))/6

然后用一个更短的公式破坏了我的乐趣:

t = m - n
n * (n + 1) / 6 * (2 * n + 3 * t + 1)

这是我的,对术语进行了一些巧妙的重新排列。


现在该练习的重点是:给定所需的正方形计数Q,找到精确那么多正方形的所有矩形尺寸(n,m)。从上面的公式开始:

q = n * (n + 1) / 6 * (2 * n + 3 * t + 1)

由于我们获得了Q的期望值q,因此我们可以遍历n的所有值,以找出{{1 }}满足公式。首先解决t的问题:

t

结合分母:

t = (6/(n*(n+1)) * q - 2*n - 1) / 3

我将使用第一个版本。由于t = (6*q) / (3*n*(n+1)) - (2*n + 1)/3 的解决方案意味着n x m的解决方案,因此我们可以将搜索限制为仅m x n的情况。另外,由于分子缩小(n ^ 3项为负数),我们可以限制搜索允许n <= m的{​​{1}}值-换句话说,组合分子的大小至少要等于分母:

n

解决这个问题:

t >= 1

因此,(numer = 6 * num_sqrs - n * (n+1) * (2*n+1) denom = 3 * n * (n+1) 的立方根)/ 3是我们的循环限制的便捷上限。

这在代码中为我们提供了一个简单的迭代循环:

num_sqrs > (n * (n+1) * (n+2)) / 3

几个测试用例的输出:

n

这些立即返回,因此我希望此解决方案对于OP而言足够快。

参数化方程很可能会给我们直接解决方案,而不是反复进行各种可能性。我将其留给欧拉计划的人们。 :-)

答案 3 :(得分:0)

这使用在OP提供的链接中得出的公式。唯一真正的优化是尽量不要查看不能产生结果的尺寸。 预加载带有两种最终情况(figures = [(1,n_squares),(n_squares,1)])的结果可以节省大量的数量。我认为还有其他大块可以丢弃,但我还没有弄清楚。

def h(n_squares):
    # easiest case for a n x m figure:
    # n = 1 and m = n_squares
    figures = [(1,n_squares),(n_squares,1)]
    for n in range(2, n_squares+1):
        for m in range(n, n_squares+1):
            t = m - n
            x = int((n * (n + 1) / 6) * ((2 * n) + (3 * t) + 1))
            if x > n_squares:
                break
            if x == n_squares:
                figures.extend([(n,m),(m,n)])
                #print(f'{n:>6} x {m:<6} has {n_squares} squares')
        if x > n_squares and n == m:
            break
    return figures    

它也不会列出很多列表,这些列表可能会使您的计算机崩溃,并造成21493600(400x401)之类的大数字。


OP注释中链接的公式推导(以防资源消失):
http://mathforum.org/library/drmath/view/63172.html

中的文字
  

礼貌:
  -数学论坛的安东尼医生
    http://mathforum.org/dr.math/

     

如果我们有8 x 9的棋盘,则正方形数如下:

Size of Square        Number of Squares  
--------------        -----------------
    1 x 1                8 x 9  = 72
    2 x 2                7 x 8  = 56
    3 x 3                6 x 7  = 42
    4 x 4                5 x 6  = 30
    5 x 5                4 x 5  = 20
    6 x 6                3 x 4  = 12
    7 x 7                2 x 3  =  6
    8 x 8                1 x 2  =  2
  ----------------------------------------
                         Total  = 240
     

对于n x m板的一般情况,其中m = n + t    我们需要

         n                n
        SUM[r(r + t)]  = SUM[r^2 + rt} 
        r=1              r=1

       = n(n + 1)(2n + 1)/6 + tn(n + 1)/2

       = [n(n + 1)/6]*[2n + 1 + 3t]
     

不。的平方=

             [n(n + 1)/6]*[2n + 3t + 1]  .......(1)
     

在上面的示例中t = 1等等

 No. of squares  = 8 x 9/6[16 + 3 + 1]

                 = (72/6)[20]

                 = 240    (as required)
     

(n x n + t)板的一般公式为(1)中给出的公式   以上。

No. of squares = [n(n + 1)/6]*[2n + 3t + 1]