Moi et mon compagnon essayions de créer un jeu amusant en python où les éléments entrés dans le tableau sont accessibles en spirale. J'ai essayé peu de méthodes comme celle donnée ci-dessous ( source ).
def spiral(X, Y):
x = y = 0
dx = 0
dy = -1
for i in range(max(X, Y)**2):
if (-X/2 < x <= X/2) and (-Y/2 < y <= Y/2):
print (x, y)
# DO STUFF...
if x == y or (x < 0 and x == -y) or (x > 0 and x == 1-y):
dx, dy = -dy, dx
x, y = x+dx, y+dy
L'instruction ci-dessus accède aux éléments en boucle en spirale et les imprime pour un tableau AE défini. Je voudrais savoir comment puis-je transformer un tableau AE donné en un spiral
Vous pouvez construire une spirale en commençant près du centre de la matrice et en tournant à droite, sauf si l'élément a déjà été visité:
#!/usr/bin/env python
NORTH, S, W, E = (0, -1), (0, 1), (-1, 0), (1, 0) # directions
turn_right = {NORTH: E, E: S, S: W, W: NORTH} # old -> new direction
def spiral(width, height):
if width < 1 or height < 1:
raise ValueError
x, y = width // 2, height // 2 # start near the center
dx, dy = NORTH # initial direction
matrix = [[None] * width for _ in range(height)]
count = 0
while True:
count += 1
matrix[y][x] = count # visit
# try to turn right
new_dx, new_dy = turn_right[dx,dy]
new_x, new_y = x + new_dx, y + new_dy
if (0 <= new_x < width and 0 <= new_y < height and
matrix[new_y][new_x] is None): # can turn right
x, y = new_x, new_y
dx, dy = new_dx, new_dy
else: # try to move straight
x, y = x + dx, y + dy
if not (0 <= x < width and 0 <= y < height):
return matrix # nowhere to go
def print_matrix(matrix):
width = len(str(max(el for row in matrix for el in row if el is not None)))
fmt = "{:0%dd}" % width
for row in matrix:
print(" ".join("_"*width if el is None else fmt.format(el) for el in row))
Exemple:
>>> print_matrix(spiral(5, 5))
21 22 23 24 25
20 07 08 09 10
19 06 01 02 11
18 05 04 03 12
17 16 15 14 13
La question est étroitement liée à un problème d’impression d’un tableau en spirale. En fait, si nous avons déjà une fonction qui le fait, le problème en question est relativement simple.
Il existe une multitude de ressources sur comment produire une matrice en spirale ou comment en boucle ou imprimer un tableau dans un ordre en spirale. Malgré tout, j'ai décidé d'écrire ma propre version, en utilisant des tableaux numpy. L'idée n'est pas originale, mais l'utilisation de numpy rend le code plus concis.
L'autre raison est que la plupart des exemples de production d'une matrice en spirale que j'ai trouvée (y compris le code dans la question et dans les autres réponses) ne concernent que des matrices carrées de taille n x n pour n impair. Trouver le point de départ (ou de fin) dans des matrices d'autres tailles peut être délicat. Par exemple, pour une matrice 3x5, il ne peut s'agir de la cellule du milieu. Le code ci-dessous est général et la position du point de départ (final) dépend du choix de la fonction spiral_xxx
.
La première fonction ouvre un tableau dans un ordre en spirale dans le sens des aiguilles d'une montre:
import numpy as np
def spiral_cw(A):
A = np.array(A)
out = []
while(A.size):
out.append(A[0]) # take first row
A = A[1:].T[::-1] # cut off first row and rotate counterclockwise
return np.concatenate(out)
Nous pouvons écrire cette fonction de huit manières différentes selon le début et la rotation de la matrice. J'en donnerai une autre, qui est cohérente (ce sera évident par la suite) avec la transformation de la matrice dans l'image de la question. Donc, plus tard, je vais utiliser cette version:
def spiral_ccw(A):
A = np.array(A)
out = []
while(A.size):
out.append(A[0][::-1]) # first row reversed
A = A[1:][::-1].T # cut off first row and rotate clockwise
return np.concatenate(out)
Comment ça marche:
A = np.arange(15).reshape(3,5)
print(A)
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
print(spiral_ccw(A))
[ 4 3 2 1 0 5 10 11 12 13 14 9 8 7 6]
Notez que le point de fin (ou de début) n'est pas la cellule du milieu. Cette fonction fonctionne pour tous les types de matrices, mais nous aurons besoin d'une fonction d'assistance qui génère indices de spirale:
def base_spiral(nrow, ncol):
return spiral_ccw(np.arange(nrow*ncol).reshape(nrow,ncol))[::-1]
Par exemple:
print(base_spiral(3,5))
[ 6 7 8 9 14 13 12 11 10 5 0 1 2 3 4]
Viennent maintenant les deux fonctions principales. L'un transforme une matrice en une forme spirale de mêmes dimensions, l'autre inverse la transformation:
def to_spiral(A):
A = np.array(A)
B = np.empty_like(A)
B.flat[base_spiral(*A.shape)] = A.flat
return B
def from_spiral(A):
A = np.array(A)
return A.flat[base_spiral(*A.shape)].reshape(A.shape)
Matrice 3 x 5:
A = np.arange(15).reshape(3,5)
print(A)
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
print(to_spiral(A))
[[10 11 12 13 14]
[ 9 0 1 2 3]
[ 8 7 6 5 4]]
print(from_spiral(to_spiral(A)))
[[ 0 1 2 3 4]
[ 5 6 7 8 9]
[10 11 12 13 14]]
Matrice de la question:
B = np.arange(1,26).reshape(5,5)
print(B)
[[ 1 2 3 4 5]
[ 6 7 8 9 10]
[11 12 13 14 15]
[16 17 18 19 20]
[21 22 23 24 25]]
print(to_spiral(B))
[[21 22 23 24 25]
[20 7 8 9 10]
[19 6 1 2 11]
[18 5 4 3 12]
[17 16 15 14 13]]
print(from_spiral(to_spiral(B)))
[[ 1 2 3 4 5]
[ 6 7 8 9 10]
[11 12 13 14 15]
[16 17 18 19 20]
[21 22 23 24 25]]
Si vous ne travaillez qu'avec des matrices de taille fixe, par exemple 5x5, remplacez base_spiral(*A.shape)
dans les définitions des fonctions par une matrice d'indices fixe, par exemple Ind
(où Ind = base_spiral(5,5)
).
Voici une solution utilisant itertools
et pratiquement pas de maths, juste des observations sur l'aspect de la spirale. Je pense que c'est élégant et assez facile à comprendre.
from math import ceil, sqrt
from itertools import cycle, count, izip
def spiral_distances():
"""
Yields 1, 1, 2, 2, 3, 3, ...
"""
for distance in count(1):
for _ in (0, 1):
yield distance
def clockwise_directions():
"""
Yields right, down, left, up, right, down, left, up, right, ...
"""
left = (-1, 0)
right = (1, 0)
up = (0, -1)
down = (0, 1)
return cycle((right, down, left, up))
def spiral_movements():
"""
Yields each individual movement to make a spiral:
right, down, left, left, up, up, right, right, right, down, down, down, ...
"""
for distance, direction in izip(spiral_distances(), clockwise_directions()):
for _ in range(distance):
yield direction
def square(width):
"""
Returns a width x width 2D list filled with Nones
"""
return [[None] * width for _ in range(width)]
def spiral(inp):
width = int(ceil(sqrt(len(inp))))
result = square(width)
x = width // 2
y = width // 2
for value, movement in izip(inp, spiral_movements()):
result[y][x] = value
dx, dy = movement
x += dx
y += dy
return result
Usage:
from pprint import pprint
pprint(spiral(range(1, 26)))
Sortie:
[[21, 22, 23, 24, 25],
[20, 7, 8, 9, 10],
[19, 6, 1, 2, 11],
[18, 5, 4, 3, 12],
[17, 16, 15, 14, 13]]
Voici la même solution raccourcie:
def stretch(items, counts):
for item, count in izip(items, counts):
for _ in range(count):
yield item
def spiral(inp):
width = int(ceil(sqrt(len(inp))))
result = [[None] * width for _ in range(width)]
x = width // 2
y = width // 2
for value, (dx, dy) in izip(inp,
stretch(cycle([(1, 0), (0, 1), (-1, 0), (0, -1)]),
stretch(count(1),
repeat(2)))):
result[y][x] = value
x += dx
y += dy
return result
J'ai ignoré le fait que vous souhaitiez que l'entrée soit un tableau 2D, car il est beaucoup plus logique que ce soit un élément 1D itérable. Vous pouvez facilement aplatir le tableau 2D d'entrée si vous le souhaitez. J'ai également supposé que la sortie devrait être un carré car je ne peux pas penser à ce que vous voudriez raisonnablement autrement. Cela peut aller au-dessus de Edge et générer une erreur si le carré a une longueur égale et que la saisie est trop longue: encore une fois, je ne sais pas quelle serait la solution de remplacement.
Ci-dessous, le code python3 qui transforme:
[[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19],
[20, 21, 22, 23, 24]]
à
[[20, 19, 18, 17, 16],
[21, 6, 5, 4, 15],
[22, 7, 0, 3, 14],
[23, 8, 1, 2, 13],
[24, 9, 10, 11, 12]]
Vous pouvez facilement changer la mise en œuvre de cette manière, comment voulez-vous ...
def spiral(X, Y):
x = y = 0
dx = 0
dy = -1
for i in range(max(X, Y) ** 2):
if (-X / 2 < x <= X / 2) and (-Y / 2 < y <= Y / 2):
yield x, y
# print(x, y)
# DO STUFF...
if x == y or (x < 0 and x == -y) or (x > 0 and x == 1 - y):
dx, dy = -dy, dx
x, y = x + dx, y + dy
spiral_matrix_size = 5
my_list = list(range(spiral_matrix_size**2))
my_list = [my_list[x:x + spiral_matrix_size] for x in range(0, len(my_list), spiral_matrix_size)]
print(my_list)
for i, (x, y) in enumerate(spiral(spiral_matrix_size, spiral_matrix_size)):
diff = int(spiral_matrix_size / 2)
my_list[x + diff][y + diff] = i
print(my_list)
Vous pouvez remplir un tableau avec quelque chose comme ceci:
#!/usr/bin/python
class filler:
def __init__(self, srcarray):
self.size = len(srcarray)
self.array = [[None for y in range(self.size)] for y in range(self.size)]
self.xpos, self.ypos = 0, 0
self.directions = [self.down, self.right, self.up, self.left]
self.direction = 0
self.fill(srcarray)
def fill(self, srcarray):
for row in reversed(srcarray):
for elem in reversed(row):
self.array[self.xpos][self.ypos] = elem
self.go_to_next()
def check_next_pos(self):
np = self.get_next_pos()
if np[1] in range(self.size) and np[0] in range(self.size):
return self.array[np[0]][np[1]] == None
return False
def go_to_next(self):
i = 0
while not self.check_next_pos() and i < 4:
self.direction = (self.direction + 1) % 4
i += 4
self.xpos, self.ypos = self.get_next_pos()
def get_next_pos(self):
return self.directions[self.direction](self.xpos, self.ypos)
def down(self, x, y):
return x + 1, y
def right(self, x, y):
return x, y + 1
def up(self, x, y):
return x - 1, y
def left(self, x, y):
return x, y - 1
def print_grid(self):
for row in self.array:
print(row)
f = filler([[x+y*5 for x in range(5)] for y in range(5)])
f.print_grid()
Le résultat de ceci sera:
[24, 9, 10, 11, 12]
[23, 8, 1, 2, 13]
[22, 7, 0, 3, 14]
[21, 6, 5, 4, 15]
[20, 19, 18, 17, 16]
def counter(n):
for i in range(1,n*n):
yield i+1
n = 11
a = [[1 for x in range(n)] for y in range(n)]
x = y = n//2
val = counter(n)
for i in range(2, n, 2):
y += 1
x -= 1
for k in range(i):
x += 1
a[x][y] = next(val)
for k in range(i):
y -= 1
a[x][y] = next(val)
for k in range(i):
x -= 1
a[x][y] = next(val)
for k in range(i):
y += 1
a[x][y] = next(val)
for i in range(n):
for j in range(n):
print (a[i][j] , end="")
print (" " , end="")
print("\n")