将正方形放在笛卡尔平面上并根据序列号计算出它们的 X、Y 坐标

Placing squares on a Cartesian plane and working out their X,Y coords based on sequence number

工作中的一群人和我自己都在努力解决这个问题,但找不到一种干净有效的数学方法来解决这个问题。这是:

给定一个标准的笛卡尔平面和一系列图像(都是正方形且尺寸相同,例如我们只说 1 个单位)我们如何以这样的方式组织它们,使它们围绕原点旋转( 0,0) 平面的左上角。但更具体地说,如果给定数字 25(序列中的第 25 个方块),它的 X、Y 坐标是什么?

希望这张粗略的图像有助于解释序列。放置在网格上的第一个方块将是红色,然后是蓝色、黄色、紫色、绿色、黑色、棕色,然后依此类推,如圆点所示。

我们希望有一个 "relatively" 简单的数学公式来解决这个问题,但这也许只是一厢情愿。

假设您使用的是基于 1 的索引,您可以使用以下算法找到您的答案(不确定是否存在封闭形式的解决方案)。

这背后的想法是找出你在哪一层(你的 x 或 y 离 0 有多远)。然后找到你在哪个象限。然后找出你在哪一侧然后找出坐标。

get_coords(n)
    layer = 1
    while (n > 3 * layer * layer + (layer+1)*(layer+1))
        ++layer
    layer_ind = n - 3 * (layer - 1) * (layer - 1) - layer * layer
    if (layer_ind < 2 * layer + 1) # in bottom right
        if (layer_ind <= layer + 1)
            return (layer, - layer_ind + 1)
        else
            return (2 * layer + 1 - layer_ind, -layer)
    else if ((layer_ind - 2 * layer - 1)/(2 * layer - 1) < 1) # bottom left
        layer_ind = layer_ind - 2 * layer - 1
        if (layer_ind <= layer)
            return (-layer_ind+1, -layer+1)
        else
            return (-layer+1, -2*layer + 1 + layer_ind)
    else if ((layer_ind - 2 * layer - 1)/(2 * layer - 1) < 2) # top left
        layer_ind = layer_ind - 4 * layer
        if (layer_ind <= layer)
            return (-layer+1, layer_ind)
        else
            return (layer_ind - 2 * layer, layer)
    else # top right
        layer_ind = layer_ind - 6 * layer + 1
        if (layer_ind <= layer)
            return (layer_ind-1, layer)
        else
            return (layer-1, 2 * layer - layer_ind)

我之前的答案(现在归入修订历史)是一个递归解决方案。下面是我称之为 "cyclic" 的解决方案。

我们的想法是我们围绕一个正方形进行多次旋转。在任何给定索引处:

  • 我们正在围绕一个较小的正方形外侧进行革命
  • 我们在这个广场的一侧
  • 我们部分位于这个广场的一侧

通过跟踪正方形、边和边的距离,我们可以索引每个点。

我们通过画 4 条相等的线绕一个正方形。 (称他们为"segments"。)我们在第一段的中间开始每一圈。

例如:

第 1 圈围绕一个大小为 0、长度为 1 的正方形:

            O    OO
 O    OO    OO   OO

第 2 圈围绕一个大小为 4、长度为 3 的正方形:

            O     OOOO  OOOO 
 OO    OO   OOO   OOO   OOOO
 OOO   OOO  OOOO  OOOO  OOOO
   O  OOOO  OOOO  OOOO  OOOO

第 3 次旋转是围绕一个大小为 16、长度为 5 的正方形:

                 O       OOOOOO  OOOOOO
 OOOO    OOOO    OOOOO   OOOOO   OOOOOO
 OOOO    OOOO    OOOOO   OOOOO   OOOOOO
 OOOOO   OOOOO   OOOOOO  OOOOOO  OOOOOO
 OOOOO   OOOOO   OOOOOO  OOOOOO  OOOOOO
     O  OOOOOO   OOOOOO  OOOOOO  OOOOOO

下面是 Python 中的一个实现。

import math

def makeSquare(main_index, offset_size):
    step_index = int(math.sqrt(main_index))/2   # How many revolutions have we made?
    last_square = int(math.pow(step_index*2,2)) # Whats the largest square less than main_index?
    segment_length = (2 * step_index) + 1       # How long is the side of the square we've made so far? The segment length is 1 more than that.

    main_index = main_index + step_index + 1    # Skip ahead so we start in the middle of the right side instead of the top. We do some modulo stuff below to wrap around.

    segment_index = (main_index - last_square) / segment_length # Which segment are we on?
    dot_index = main_index - segment_index*segment_length - last_square # How far along the segment are we?

    draw_functions = [
        lambda i, size:  [size,     size - i], # Draw the right side of a square
        lambda i, size:  [size - i, 0       ], # Draw the bottom of a square
        lambda i, size:  [0,        i       ], # Draw the left side of a square
        lambda i, size:  [i,        size    ], # Draw the top of a square
    ]    

    x, y = draw_functions[segment_index % 4](dot_index % (4 * segment_length), segment_length)
    return [ x + offset_size - step_index - 1, y + offset_size - step_index - 1]

# Print the points to the console in sequence
import time, os
step = 4
points = [makeSquare(i, step) for i in range(int(math.pow(step*2,2)))]
board = [[" " for x in range(step*2)] for x in range(step*2)] 
for p in range(len(points)):
    print
    print p, "------------------"
    board[step*2-1 - points[p][1]][points[p][0]] = "O" # Do some coordinate wrangling so we have normal X and Y
    print (os.linesep).join([''.join(row) for row in board])
    time.sleep(.1)

(编辑时:我添加了第二个函数,可以直接获取笛卡尔坐标版本。)

在我的头爆炸之前我已经走了这么远。它是封闭形式,因为它给出了比方说百万分之一平方的坐标,而不需要将它们一个接一个地放在一个循环中。把它写下来不会给出一个很好的公式,但你可以写成一个由 8 个部分组成的分段定义的公式。答案以基于 1 的网格坐标给出。第一个坐标告诉您在 y 轴的右边或左边有多少个方块,第二个坐标告诉您向上或向下有多远。从这些数字中可以相对容易地得出例如得到他们左上角的笛卡尔坐标。我在 Python 中实现了它:

from math import sqrt, ceil

def coordinates(m):
    n = ceil(sqrt(m)/2) #shell number
    i = m - 4*(n-1)**2 #index in shell
    if i <= n:
        return (n,-i)
    elif i <= 2*n-1:
        return (2*n - i, -n)
    elif i <= 3*n - 1:
        return (2*n - 1 - i, -n)
    elif i <= 4*n - 2:
        return (-n, -4*n + 1  + i)
    elif i <= 5*n - 2:
        return (-n, -4*n + 2 + i)
    elif i <= 6*n - 3:
        return (-6*n + 2 + i, n)
    elif i <= 7*n - 3:
        return (-6*n + 3 + i,n)
    else:
        return (n, 8*n -3 - i)

要从 (i,j) 网格坐标获取左上角的 (x,y) 笛卡尔坐标,您可以使用以下函数,它有一个可选的宽度参数以允许非单位正方形:

def cartesianFromGrid(i,j,w = 1):
    x = w * (i if i < 0 else i - 1)
    y = w * (j if j > 0 else j + 1)
    return (x,y)

可以不用先通过网格坐标直接得到左上角的直角坐标。生成的公式涉及的情况更少(因为我不需要从 1 直接跳到 -1 或反之亦然),尽管我将这两个公式都保留在答案中,因为在许多方面网格透视更自然:

def  cartCoordinates(m):
    n = ceil(sqrt(m)/2) #shell number
    i = m - 4*(n-1)**2 #index in shell
    if i <= n:
        return (n-1,-i+1)
    elif i <= 3*n - 1:
        return (2*n - 1 - i, -n + 1)
    elif i <= (5*n - 2):
        return (-n, -4*n + 2 + i)
    elif i <= 7*n - 3:
        return (-6*n + 2 + i, n)
    else:
        return (n-1, 8 * n - 3 - i)

1-16 的输出:

>>> for n in range(1,17):
    print(n, ': grid coords =', coordinates(n),
          'Cartesian =',cartesianFromGrid(*coordinates(n)))


1 : grid coords = (1, -1) Cartesian = (0, 0)
2 : grid coords = (-1, -1) Cartesian = (-1, 0)
3 : grid coords = (-1, 1) Cartesian = (-1, 1)
4 : grid coords = (1, 1) Cartesian = (0, 1)
5 : grid coords = (2, -1) Cartesian = (1, 0)
6 : grid coords = (2, -2) Cartesian = (1, -1)
7 : grid coords = (1, -2) Cartesian = (0, -1)
8 : grid coords = (-1, -2) Cartesian = (-1, -1)
9 : grid coords = (-2, -2) Cartesian = (-2, -1)
10 : grid coords = (-2, -1) Cartesian = (-2, 0)
11 : grid coords = (-2, 1) Cartesian = (-2, 1)
12 : grid coords = (-2, 2) Cartesian = (-2, 2)
13 : grid coords = (-1, 2) Cartesian = (-1, 2)
14 : grid coords = (1, 2) Cartesian = (0, 2)
15 : grid coords = (2, 2) Cartesian = (1, 2)
16 : grid coords = (2, 1) Cartesian = (1, 1)

如果您想知道:

>>> coordinates(1000000)
(500, 1)

最后一个答案是有道理的,因为百万分之一的正方形是 1000x1000 正方形网格的顶点。

我使用上面的方法在 tkinter canvas 小部件上放置彩色方块: