在扩展的圆形螺旋中迭代2d阵列

时间:2012-01-23 21:59:35

标签: python matrix loops geometry spiral

根据n矩阵n Mi行和j行,我想迭代所有相邻的值圆形螺旋。

这样做的目的是测试一些函数f,它取决于M,以找到(i, j)返回f True之外的半径。所以,f看起来像这样:

def f(x, y):
    """do stuff with x and y, and return a bool"""

并会像这样调用:

R = numpy.zeros(M.shape, dtype=numpy.int)
# for (i, j) in M
for (radius, (cx, cy)) in circle_around(i, j):
    if not f(M[i][j], M[cx][cy]):
       R[cx][cy] = radius - 1
       break

其中circle_around是以循环螺旋形式返回(迭代器)索引的函数。因此,对于M中的每个点,此代码将计算并存储f返回True的点的半径。

如果有一种更有效的计算方式R,我也会对此持开放态度。


更新

感谢所有提交答案的人。我编写了一个简短的函数来绘制circle_around迭代器的输出,以显示它们的作用。如果您更新答案或发布新答案,则可以使用此代码验证您的解决方案。

from matplotlib import pyplot as plt
def plot(g, name):
    plt.axis([-10, 10, -10, 10])
    ax = plt.gca()
    ax.yaxis.grid(color='gray')
    ax.xaxis.grid(color='gray')

    X, Y = [], []
    for i in xrange(100):
        (r, (x, y)) = g.next()
        X.append(x)
        Y.append(y)
        print "%d: radius %d" % (i, r)

    plt.plot(X, Y, 'r-', linewidth=2.0)
    plt.title(name)
    plt.savefig(name + ".png")

以下是结果: plot(circle_around(0, 0), "F.J")circle_around by F.J

plot(circle_around(0, 0, 10), "WolframH")circle_around by WolframH

我已将Magnesium的建议编码如下:

def circle_around_magnesium(x, y):
    import math
    theta = 0
    dtheta = math.pi / 32.0
    a, b = (0, 1) # are there better params to use here?
    spiral = lambda theta : a + b*theta
    lastX, lastY = (x, y)
    while True:
        r = spiral(theta)
        X = r * math.cos(theta)
        Y = r * math.sin(theta)
        if round(X) != lastX or round(Y) != lastY:
            lastX, lastY = round(X), round(Y)
            yield (r, (lastX, lastY))
        theta += dtheta

plot(circle_around(0, 0, 10), "magnesium")circle_around by Magnesium

正如你所看到的,满足我正在寻找的界面的结果都没有产生一个圆形螺旋,覆盖了0,0附近的所有指数.FJ是最接近的,虽然WolframH击中了正确的点,只是不是螺旋式的。

7 个答案:

答案 0 :(得分:10)

由于有人提到点的顺序无关紧要,我只是按照它们出现在给定半径的角度(arctan2)对它们进行排序。更改N以获得更多积分。

from numpy import *
N = 8

# Find the unique distances
X,Y = meshgrid(arange(N),arange(N))
G = sqrt(X**2+Y**2)
U = unique(G)

# Identify these coordinates
blocks = [[pair for pair in zip(*where(G==idx))] for idx in U if idx<N/2]

# Permute along the different orthogonal directions
directions = array([[1,1],[-1,1],[1,-1],[-1,-1]])

all_R = []
for b in blocks:
    R = set()
    for item in b:
        for x in item*directions:
            R.add(tuple(x))

    R = array(list(R))

    # Sort by angle
    T = array([arctan2(*x) for x in R])
    R = R[argsort(T)]
    all_R.append(R)

# Display the output
from pylab import *
colors = ['r','k','b','y','g']*10
for c,R in zip(colors,all_R):
    X,Y = map(list,zip(*R))

    # Connect last point
    X = X + [X[0],]
    Y = Y + [Y[0],]
    scatter(X,Y,c=c,s=150)
    plot(X,Y,color=c)

axis('equal')
show()

给予N=8

enter image description here

更多积分N=16(抱歉为色盲):

enter image description here

这显然接近一个圆圈,并按照半径增加的顺序击中每个网格点。

enter image description here

答案 1 :(得分:7)

通过增加距离产生分数的一种方法是将其分解分成简单部分,然后合并部分的结果。很明显,itertools.merge应该进行合并。 简易部分,因为对于固定的x,可以通过查看y的值来排序点(x,y)。

以下是该算法的(简单)实现。请注意,使用平方欧几里德距离,并包含中心点。最重要的是,只考虑range(x_end)中带x的点(x,y),但我认为对于您的用例(上面的符号中x_endn)是可以的。

from heapq import merge
from itertools import count

def distance_column(x0, x, y0):
    dist_x = (x - x0) ** 2
    yield dist_x, (x, y0)
    for dy in count(1):
        dist = dist_x + dy ** 2
        yield dist, (x, y0 + dy)
        yield dist, (x, y0 - dy)

def circle_around(x0, y0, end_x):
    for dist_point in merge(*(distance_column(x0, x, y0) for x in range(end_x))):
        yield dist_point

修改:测试代码:

def show(circle):
    d = dict((p, i) for i, (dist, p) in enumerate(circle))
    max_x = max(p[0] for p in d) + 1
    max_y = max(p[1] for p in d) + 1
    return "\n".join(" ".join("%3d" % d[x, y] if (x, y) in d else "   " for x in range(max_x + 1)) for y in range(max_y + 1))

import itertools
print(show(itertools.islice(circle_around(5, 5, 11), 101)))

测试结果(点数按照circle_around产生的顺序编号):

             92  84  75  86  94                
     98  73  64  52  47  54  66  77 100        
     71  58  40  32  27  34  42  60  79        
 90  62  38  22  16  11  18  24  44  68  96    
 82  50  30  14   6   3   8  20  36  56  88    
 69  45  25   9   1   0   4  12  28  48  80    
 81  49  29  13   5   2   7  19  35  55  87    
 89  61  37  21  15  10  17  23  43  67  95    
     70  57  39  31  26  33  41  59  78        
     97  72  63  51  46  53  65  76  99        
             91  83  74  85  93                

修改2 :如果您确实需要i的负值,请将range(end_x)替换为range(-end_x, end_x)函数中的cirlce_around

答案 2 :(得分:3)

如果您遵循x和y螺旋索引,您会注意到它们都可以以递归方式定义。因此,很容易想出一个递归生成正确索引的函数:

def helicalIndices(n):
    num = 0
    curr_x, dir_x, lim_x, curr_num_lim_x = 0, 1, 1, 2
    curr_y, dir_y, lim_y, curr_num_lim_y = -1, 1, 1, 3
    curr_rep_at_lim_x, up_x = 0, 1
    curr_rep_at_lim_y, up_y = 0, 1

    while num < n:
        if curr_x != lim_x:
            curr_x +=  dir_x
        else:
            curr_rep_at_lim_x += 1
            if curr_rep_at_lim_x == curr_num_lim_x - 1:
                if lim_x < 0:
                    lim_x = (-lim_x) + 1
                else:
                    lim_x = -lim_x
                curr_rep_at_lim_x = 0
                curr_num_lim_x += 1
                dir_x = -dir_x
        if curr_y != lim_y:
            curr_y = curr_y + dir_y
        else:
            curr_rep_at_lim_y += 1
            if curr_rep_at_lim_y == curr_num_lim_y - 1:
                if lim_y < 0:
                    lim_y = (-lim_y) + 1
                else:
                    lim_y = -lim_y
                curr_rep_at_lim_y = 0
                curr_num_lim_y += 1
                dir_y = -dir_y
        r = math.sqrt(curr_x*curr_x + curr_y*curr_y)        
        yield (r, (curr_x, curr_y))
        num += 1

    hi = helicalIndices(101)
    plot(hi, "helicalIndices")

helicalIndices

从上图中可以看出,这确切地说明了所要求的内容。

答案 3 :(得分:2)

以下是circle_around()的基于循环的实现:

def circle_around(x, y):
    r = 1
    i, j = x-1, y-1
    while True:
        while i < x+r:
            i += 1
            yield r, (i, j)
        while j < y+r:
            j += 1
            yield r, (i, j)
        while i > x-r:
            i -= 1
            yield r, (i, j)
        while j > y-r:
            j -= 1
            yield r, (i, j)
        r += 1
        j -= 1
        yield r, (i, j)

答案 4 :(得分:0)

嗯,我很尴尬这是迄今为止我提出的最好的。但也许它会对你有所帮助。因为它实际上不是一个循环迭代器,所以我不得不接受你的测试函数作为参数。

问题:

  • 未优化以跳过数组之外的点
  • 仍然使用方形迭代器,但确实找到了最接近的点
  • 我没有使用numpy,所以它是为列表列表。您需要更改的两点是评论
  • 我以较长的形式离开了方形迭代器,因此更容易阅读。它可能更干嘛

这是代码。您的问题的关键解决方案是顶级“spiral_search”函数,它在方形螺旋迭代器之上添加一些额外的逻辑,以确保找到最近的点。

from math import sqrt

#constants
X = 0
Y = 1

def spiral_search(array, focus, test):
    """
    Search for the closest point to focus that satisfies test.
    test interface: test(point, focus, array)
    points structure: [x,y] (list, not tuple)
    returns tuple of best point [x,y] and the euclidean distance from focus
    """
    #stop if focus not in array
    if not _point_is_in_array(focus, array): raise IndexError("Focus must be within the array.")
    #starting closest radius and best point
    stop_radius = None
    best_point = None 
    for point in _square_spiral(array, focus):
        #cheap stop condition: when current point is outside the stop radius
        #(don't calculate outside axis where more expensive)
        if (stop_radius) and (point[Y] == 0) and (abs(point[X] - focus[X]) >= stop_radius):
            break #current best point is already as good or better so done
        #otherwise continue testing for closer solutions
        if test(point, focus, array):
            distance = _distance(focus, point)
            if (stop_radius == None) or (distance < stop_radius):
                stop_radius = distance
                best_point = point
    return best_point, stop_radius

def _square_spiral(array, focus):
    yield focus
    size = len(array) * len(array[0]) #doesn't work for numpy
    count = 1
    r_square = 0
    offset = [0,0]
    rotation = 'clockwise'
    while count < size:
        r_square += 1
        #left
        dimension = X
        direction = -1
        for point in _travel_dimension(array, focus, offset, dimension, direction, r_square):
            yield point
            count += 1
        #up
        dimension = Y
        direction = 1
        for point in _travel_dimension(array, focus, offset, dimension, direction, r_square):
            yield point
            count += 1
        #right
        dimension = X
        direction = 1
        for point in _travel_dimension(array, focus, offset, dimension, direction, r_square):
            yield point
            count += 1
        #down
        dimension = Y
        direction = -1
        for point in _travel_dimension(array, focus, offset, dimension, direction, r_square):
            yield point
            count += 1

def _travel_dimension(array, focus, offset, dimension, direction, r_square):
    for value in range(offset[dimension] + direction, direction*(1+r_square), direction):
        offset[dimension] = value
        point = _offset_to_point(offset, focus)
        if _point_is_in_array(point, array):
            yield point

def _distance(focus, point):
    x2 = (point[X] - focus[X])**2
    y2 = (point[Y] - focus[Y])**2
    return sqrt(x2 + y2)

def _offset_to_point(offset, focus):
    return [offset[X] + focus[X], offset[Y] + focus[Y]]

def _point_is_in_array(point, array):
    if (0 <= point[X] < len(array)) and (0 <= point[Y] < len(array[0])): #doesn't work for numpy
        return True
    else:
        return False

答案 5 :(得分:0)

虽然我不完全确定你要做什么,但我会这样开始:

def xxx():
    for row in M[i-R:i+R+1]:
        for val in row[j-R:j+r+1]:
            yield val

我不确定你想要的螺旋顺序是多少,这一点很重要吗?是否必须增加R顺序?或者可能从特定方位角顺时针开始?

曼哈顿R的距离是多少?欧式?别的什么?

答案 6 :(得分:0)

我要做的是使用阿基米德螺线的等式:

r(theta) = a + b*theta

然后使用

将极坐标(r,theta)转换为(x,y)
x = r*cos(theta)
y = r*sin(theta)

cossin位于math库中。然后将得到的x和y四舍五入为整数。您可以通过起始索引向后偏移x和y,以获得数组的最终索引。

但是,如果您只是想找到f返回true的第一个半径,我认为执行以下伪代码会更有利:

for (i,j) in matrix:
    radius = sqrt( (i-i0)^2 + (j-j0)^2) // (i0,j0) is the "center" of your spiral
    radiuslist.append([radius, (i,j)])
sort(radiuslist) // sort by the first entry in each element, which is the radius
// This will give you a list of each element of the array, sorted by the
// "distance" from it to (i0,j0)
for (rad,indices) in enumerate(radiuslist):
    if f(matrix[indices]):
        // you found the first one, do whatever you want