Here's a solution using itertools
and virtually no maths, just observations about what the spiral looks like. I think it's elegant and pretty 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
Usage:
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's the same solution shortened:
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've ignored the fact that you want the input to be a 2D array since it makes much more sense for it to be any 1D iterable. You can easily flatten the input 2D array if you want. I've also assumed the output should be a square since I can't think what you'd sensibly want otherwise. It may go over the edge and raise an error if the square has even length and the input is too long: again, I don't know what the alternative would be.