Estoy tratando de ordenar un ndarray numpy en el sentido de las agujas del reloj (en orden ascendente). Puede entenderlo como tomar todos los valores de la matriz, ordenarlos y luego colocarlos en una espiral en el sentido de las agujas del reloj en una nueva matriz con la misma forma. Las direcciones de la espiral en el sentido de las agujas del reloj se muestran a continuación:
Por ejemplo, supongamos que tengo una matriz de
import numpy as np a1 = np.array(([2, 4, 6], [1, 5, 3], [7, 9, 8]))La salida esperada es
np.array([[1, 2, 3], [8, 9, 4], [7, 6, 5]])Si tengo una matriz de
a2 = np.array(([2, 4, 6], [1, 5, 3], [7, 9, 8], [12, 11, 10]))La salida esperada es
np.array([[1, 2, 3], [10, 11, 4], [9, 12, 5], [8, 7, 6]]) Mi idea es rastrear el índice de fila x y el índice de columna y del iterador en movimiento y el índice actual cur de la lista aplanada ordenada sa . El número de filas por recorrer ( lenr ) y el número de columnas por recorrer ( lenc ) se restan en 1 una vez que el iterador en movimiento pasa lenr filas horizontalmente y lenc columnas verticalmente. Aquí está la función que logré escribir:
def clockwise_sorted(a, key=None, reverse=False): nr, nc = a.shape res = a.tolist() sa = a.ravel().tolist() if key is None: sa.sort(reverse=reverse) else: sa.sort(key=key, reverse=reverse) res[0] = sa[:nc] cur, lenr, lenc = nc, nr - 1, nc - 1 x, y = 0, nc - 1 while (lenc > 0 and lenr > 0): # go down, then go left for _ in range(lenr): x += 1 res[x][y] = sa[cur] cur += 1 for _ in range(lenc): y -= 1 res[x][y] = sa[cur] cur += 1 lenr -= 1 lenc -= 1 # go up, then go right for _ in range(lenr): x -= 1 res[x][y] = sa[cur] cur += 1 for _ in range(lenc): y += 1 res[x][y] = sa[cur] cur += 1 lenr -= 1 lenc -= 1 return np.array(res)Para matrices cuadradas, mi código funciona bien:
print(clockwise_sorted(a1)) #[[1 2 3] # [8 9 4] # [7 6 5]] #%timeit 5.98 µs ± 413 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)Pero no funciona para matrices no cuadradas:
print(clockwise_sorted(a2)) #[[ 1 2 3] # [10 11 4] # [ 9 9 5] # [ 8 7 6]] Claramente, pierde el 12 en [2,1] .
Tenga en cuenta que no espero que entienda y corrija mi código. No me gusta mucho mi código porque siento que debería haber más formas numpyic de hacer esto. Por lo tanto, quiero ver algunas soluciones numpyic. Sin embargo, sería bueno si alguien puede comentar sobre lo que está mal con mi código.
Puede usar numpy.rot90 con recursividad para girar en el sentido de las agujas del reloj sobre la matriz. Ordene los valores de la matriz después de cambiar a 1d y edite los valores en la matriz por ella
def rotate(matrix, arr): if not len(matrix): return matrix[0] = arr[:len(matrix[0])] rotate(np.rot90(matrix[1:]), arr[len(matrix[0]):]) a1 = np.array(([2, 4, 6], [1, 5, 3], [7, 9, 8])) sorted_arr = sorted(a1.ravel()) rotate(a1, sorted_arr) print(a1)Producción
[[1 2 3] [8 9 4] [7 6 5]]Es probable que esta no sea la solución más numpy, pero creo que es una solución Pythonic ordenada, ya que es una función generadora que produce coordenadas en espiral en el sentido de las agujas del reloj que luego se usa para indexar la matriz numpy (que debe transponerse dos veces para obtener las coordenadas correctas, pero eso es maní...).
import numpy as np def spiral_coords(w, h): maxx = w - 1 maxy = h - 1 minx = miny = 0 while (minx, miny) != (maxx, maxy): for x in range(minx, maxx): # right yield (x, miny) yield (maxx, miny) # upper-right corner for y in range(miny + 1, maxy): # down yield (maxx, y) yield (maxx, maxy) # lower-right corner for x in range(maxx - 1, minx, -1): # left yield (x, maxy) yield (minx, maxy) # lower-left corner for y in range(maxy - 1, miny, -1): # up yield (minx, y) minx += 1 miny += 1 maxx -= 1 maxy -= 1 yield (minx, miny) # final point def clockwise_sorted(a): a = aT nr, nc = a.shape sa = a.ravel() sa.sort() res = np.zeros_like(a) for (x, y), v in zip(spiral_coords(nr, nc), sa): res[x, y] = v return res.T a2 = np.array( [ [2, 4, 6], [1, 5, 3], [7, 9, 8], [12, 11, 10], ] ) print(a2) print(clockwise_sorted(a2))Esto produce
[[ 2 4 6] [ 1 5 3] [ 7 9 8] [12 11 10]] [[ 1 2 3] [10 11 4] [ 9 12 5] [ 8 7 6]]Si no necesita necesariamente Numpy, también puede usar el mismo generador para convertir en espiral cualquier cosa, por ejemplo, un mapeo de coordenadas a letras:
coords = {(x, y): c for (x, y), c in zip(spiral_coords(6, 5), "Hello World! This is a spiral!")} for y in range(5): print("".join(coords.get((x, y), " ") for x in range(6)))salidas
Hello is aW sal! o iripsr hT !dl