我们可以计算位于我们网格之外的方格,然后从范围内的奇数/偶数方格总数中减去这些方格(不管边界内/外)。
这实际上比我们想象的要容易。首先,我们希望能够计算范围内奇数/偶数方格的数量,而不考虑边界。使用自然数之和的公式N
-S = N * (N + 1) / 2
我们可以为此推导出几个简单的方程:
def count_all(size):
if size % 2: # odd size
return (size + 1) ** 2
return size * (size + 2) + 1
尝试自己推导这些可能是一个很好的练习,或者至少用一些例子来验证它们实际上是正确的。
继续前进——我们可以通过“缩小”我们的半径来消除从上方超出边界的点。这是非常直观的,所以让我给你一个图表。只想象范围是某个固定数字的点,例如range=13
,而我们的中心位于右下象限,例如(17, 5)
。如果我们绘制这些点,用线将它们连接起来,它会创建一个菱形:
| /\
| / \
| / \
-+-----------
| / \
| \ /
| \ /
| \ /
| \/
如果我们只关心计算轴上方的点,我们可以等效地只计算相应向上移动的较小钻石的轴上方的点。例子:
| /\
| / \
| / \
-+-----------
| \ /
| \ /
| \/
现在,使用起来非常方便,因为钻石的一半在上面,一半在下面。尽管我们做了一半要小心——有些点落在轴上,两者都需要同等地考虑在界内或界外,但我们可以很容易地解释这一点。
使用这种洞察力,我们可以通过缩小范围和移动中心点来计算跨轴超出范围的点数,并在这个新图的一半上计算点数。计数代码:
def count_side(size):
if size % 2:
return (size + 1) // 2
return size // 2 + 1
def count_half(size):
if size < 0:
return 0
return count_all(size) // 2 + count_side(size)
请注意,我们必须注意偶数范围,因为我们需要只计算中心(范围 0)一次。
我们还没有完成——如果我们只是减去超出边界的点数,然后独立地向左移动,我们就会多算要删除的点数,因为我们计算的是左上角的点象限两次。为了解决这个问题,我们使用相同的技巧。我们将首先在 x 轴上缩小 + 移动钻石,然后我们将在这个新钻石上再次执行此操作,但在 y 轴上。请注意,这颗新钻石最终将以原点为中心。我建议您在这一点上暂停片刻以想象这一点并说服自己这很好,实际上会为我们提供任何特定范围的新菱形,包含 4 倍落在左上角的点数象限。
使用它,我们计算左上象限中的点数,将它们重新添加到总数中。然后我们对右侧、底部和其他三个角重复相同的过程,以获得总体总数。整个解决方案如下:
from itertools import product
def count_all(size):
if size % 2:
return (size + 1) ** 2
return size * (size + 2) + 1
def count_side(size):
if size % 2:
return (size + 1) // 2
return size // 2 + 1
def count_half(size):
if size < 0:
return 0
return count_all(size) // 2 + count_side(size)
def count_quarter(size):
if size < 0:
return 0
return count_all(size) // 4 + count_side(size)
def get_deltas(pos, limit):
return -(pos + 1), pos - (limit + 1)
def count_inside(c, r, x, y, s):
total = count_all(s)
vertical_deltas = get_deltas(x, c)
horizontal_deltas = get_deltas(y, r)
out_sides = sum(count_half(s + delta)
for delta in horizontal_deltas + vertical_deltas)
out_corners = sum(count_quarter(s + delta_vert + delta_horiz)
for delta_vert, delta_horiz in product(vertical_deltas, horizontal_deltas))
inside = total - out_sides + out_corners
return inside
几个例子:
>>> print(count_inside(5, 4, 2, 1, 3))
12
>>> print(count_inside(5, 4, 2, 1, 4))
14
>>> print(count_inside(5, 4, 2, 1, 5))
15
>>> print(count_inside(10, 6, 3, 2, 8))
36