web-dev-qa-db-fra.com

créer un tableau en spirale en python?

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 

 Array AE

11
Smple_V

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
12
jfs

Remarques introductives

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.

Code

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)

Exemples

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

Remarque

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

7
ptrj

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.

2
Alex Hall

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)
1
Jarek Szymla

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]
0
vmonteco
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")
0
user640554