pseudocodigo pseint matriz lenguaje imprimir forma espiral caracol algoritmo python matrix loops circle spiral

python - lenguaje - matriz en espiral pseint



Iterar más de 2d matriz en una espiral circular en expansión (7)

Aquí hay una implementación basada en bucle para 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)

Dada una n por n matriz M , en la fila i y columna j , me gustaría iterar sobre todos los valores vecinos en una espiral circular.

El punto de hacer esto es probar alguna función, f , que depende de M, para encontrar el radio lejos de (i, j) en el que f devuelve True . Entonces, f ve así:

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

y se llamaría así:

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

Donde circle_around es la función que devuelve (un iterador a) índices en una espiral circular. Entonces, para cada punto en M , este código calcularía y almacenaría el radio desde el punto en el que f devuelve True .

Si hay una forma más eficiente de calcular R , estaría abierto a eso también.

Actualizar:

Gracias a todos los que enviaron sus respuestas. He escrito una breve función para trazar el resultado de los iteradores circle_around , para mostrar lo que hacen. Si actualiza su respuesta o publica una nueva, puede usar este código para validar su solución.

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")

Aquí están los resultados: plot(circle_around(0, 0), "FJ") :

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

He codificado la sugerencia de Magnesium de la siguiente manera:

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") :

Como puede ver, ninguno de los resultados que satisfacen la interfaz que busco produjo una espiral circular que cubre todos los índices alrededor de 0, 0. FJ es el más cercano, aunque WolframH golpea los puntos correctos, simplemente no en espiral orden.


Aunque no estoy del todo seguro de lo que estás tratando de hacer, comenzaría así:

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

No estoy seguro de cuánto orden quieres para tu espiral, ¿es importante? tiene que ser en orden R creciente? o tal vez en el sentido de las agujas del reloj a partir de azimut particular?

¿Cuál es la medida de distancia para R, manhattan? euclidiano? ¿algo más?


Bueno, estoy bastante avergonzado, este es el mejor que he encontrado hasta ahora. Pero tal vez te ayude. Como en realidad no es un iterador circular, tuve que aceptar su función de prueba como argumento.

Problemas:

  • no optimizado para saltar puntos fuera de la matriz
  • todavía usa un iterador cuadrado, pero encuentra el punto más cercano
  • No he utilizado numpy, por lo que está hecho para la lista de listas. los dos puntos que necesita cambiar son comentados
  • Dejé el iterador cuadrado en una forma larga para que sea más fácil de leer. podría ser más seco

Aquí está el código. La solución clave para su pregunta es la función de nivel superior "spiral_search" que agrega un poco de lógica adicional sobre el iterador espiral cuadrado para asegurarse de que se encuentre el punto más cercano.

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


Como se mencionó que el orden de los puntos no importa, simplemente los ordené por el ángulo ( arctan2 ) en el que aparecen en un radio dado. Cambia N para obtener más puntos.

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()

Da para N=8 :

Más puntos N=16 (lo siento por el daltónico):

Esto se acerca claramente a un círculo y golpea cada punto de la cuadrícula en orden de radio creciente.


Lo que haría es usar la ecuación para una espiral de Arquímedes:

r(theta) = a + b*theta

y luego convierte las coordenadas polares (r, theta) en (x, y), usando

x = r*cos(theta) y = r*sin(theta)

cos y el sin están en la biblioteca de math . Luego redondee los números x e y resultantes a enteros. Puede compensar xey después con el índice inicial, para obtener los índices finales de la matriz.

Sin embargo, si solo está interesado en encontrar el primer radio donde f devuelve verdadero, creo que sería más beneficioso hacer el siguiente pseudocódigo:

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


Si sigue los índices helicoidales xey, observará que ambos pueden definirse de forma recursiva. Por lo tanto, es bastante fácil crear una función que genere recursivamente los índices correctos:

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")

Como puede ver en la imagen de arriba, esto proporciona exactamente lo que se solicita.


Una manera de ceder puntos al aumentar la distancia es dividirla en partes fáciles y luego combinar los resultados de las partes. Es bastante obvio que itertools.merge debería hacer la fusión. Las partes fáciles son columnas , porque para x fijo, los puntos (x, y) se pueden ordenar mirando solo el valor de y.

A continuación se muestra una implementación (simplista) de ese algoritmo. Tenga en cuenta que se utiliza la distancia euclidiana al cuadrado y que se incluye el punto central. Lo que es más importante, solo se consideran los puntos (x, y) con x en el range(x_end) , pero creo que está bien para su caso de uso (donde x_end sería n en su notación anterior).

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

Editar: Código de prueba:

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)))

Resultado de la prueba (los puntos están numerados en el orden en que aparecen en 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

Edición 2: si realmente necesita valores negativos de i , reemplace el range(end_x) con el range(-end_x, end_x) en la función cirlce_around .