Here's a solution using itertools and with little or no math, just observing what the spiral looks like. I think it is elegant and quite easy to understand.
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
Using:
from pprint import pprint pprint(spiral(range(1, 26)))
Output:
[[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]]
Here the same solution is abridged:
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
I ignored the fact that you want the input to be a 2D array, since it makes more sense to use any 1D fighter for it. You can easily smooth the input 2D array if you want. I also suggested that the exit should be a square, since I cannot think about what you would be wise to want otherwise. It can move around the edge and raise an error if the square has an even length and the input is too long: again, I donβt know which alternative will be.