I want to make a function that I give it a number and the function returns a spiral from 1 to that number(in 2 dimensional array). For example if I give the number 25 to the function it will return something like this:
I tried different ways but nothing worked out. I just cant figure it out.
Hope I explained myself properly.
Mostly the issue here is one of enumerating coordinates - match numbers to coordinates, then print it out however you want.
Start by noticing the two fundamental patterns:
- (Direction) Move right, then down, then left, then up, then... (this is hopefully obvious)
- (Magnitude) Move one, then one, then two, then two, then three...
So with those rules, write a generator that yields number, coordinates
tuples.
It's clearest if you set up some helper functions first; I'll be extra verbose:
def move_right(x,y):
return x+1, y
def move_down(x,y):
return x,y-1
def move_left(x,y):
return x-1,y
def move_up(x,y):
return x,y+1
moves = [move_right, move_down, move_left, move_up]
Easy enough, now the generator:
def gen_points(end):
from itertools import cycle
_moves = cycle(moves)
n = 1
pos = 0,0
times_to_move = 1
yield n,pos
while True:
for _ in range(2):
move = next(_moves)
for _ in range(times_to_move):
if n >= end:
return
pos = move(*pos)
n+=1
yield n,pos
times_to_move+=1
demo:
list(gen_points(25))
Out[59]:
[(1, (0, 0)),
(2, (1, 0)),
(3, (1, -1)),
(4, (0, -1)),
(5, (-1, -1)),
(6, (-1, 0)),
(7, (-1, 1)),
(8, (0, 1)),
(9, (1, 1)),
(10, (2, 1)),
(11, (2, 0)),
(12, (2, -1)),
(13, (2, -2)),
(14, (1, -2)),
(15, (0, -2)),
(16, (-1, -2)),
(17, (-2, -2)),
(18, (-2, -1)),
(19, (-2, 0)),
(20, (-2, 1)),
(21, (-2, 2)),
(22, (-1, 2)),
(23, (0, 2)),
(24, (1, 2)),
(25, (2, 2))]
Here's a diagram to help you think about the problem:
You can think of this as repeatedly adding to an NxN square to make an (N+1)x(N+1) square:
if N is odd:
move right one step
move down N steps
move left N steps
else:
move left one step
move up N steps
move right N steps
and at each step you write a number to the current location.
As @Milan points out, you may not always want to complete the current shell (ie if you only want to count to 23). The easiest way to do this is to make a generator function which produces an endless stream of steps, then consume only as many steps as you need:
from itertools import count
def steps_from_center():
for n in count(start=1):
if n % 2:
yield RIGHT
for i in range(n):
yield DOWN
for i in range(n):
yield LEFT
else:
yield LEFT
for i in range(n):
yield UP
for i in range(n):
yield RIGHT
Before this can be used we have to decide how to store the values and, based on that, how to represent UP
, DOWN
, LEFT
, and RIGHT
.
The simplest storage is a 2d array, or in Python terms a list of lists. The outer list will hold rows of output and the inner lists will each hold cells within a row, and each cell can be addressed as my_array[y][x]
with x increasing from left to right and y increasing from the top downward (this matches the order in which we expect to print output).
This allows us to define our directions:
from collections import namedtuple
Step = namedtuple("Step", ["dx", "dy"])
RIGHT = Step( 1, 0)
DOWN = Step( 0, 1)
LEFT = Step(-1, 0)
UP = Step( 0, -1)
Before we can allocate storage, we need to know how big an array we need:
from math import ceil, floor, log10, sqrt
max_i = int(input("What number do you want to display up to? "))
# how big does the square have to be?
max_n = int(ceil(sqrt(max_i)))
# here is our initialized data structure
square = [[EMPTY] * max_n for _ in range(max_n)]
# and we start by placing a 1 in the center:
x = y = max_n // 2
square[y][x] = output(1)
I have added two extra pieces here: in order for the output to be tidy, every item should print the same width. output()
is a function that takes a value and returns a string of the correct width, and EMPTY
is a string of spaces of that width:
# how many digits in the largest number?
max_i_width = int(floor(log10(max_i))) + 1
# custom output formatter - make every item the same width
def output(item, format_string="{{:>{}}}".format(max_i_width)):
return format_string.format(item)
EMPTY = output("")
Now the pieces are in place, and we can generate the spiral:
for i, step in enumerate(steps_from_center(), start=2):
if i > max_i:
break
else:
x += step.dx
y += step.dy
square[y][x] = output(i)
and print it out:
print("\n".join(" ".join(row) for row in square))
and it runs like:
What number do you want to display up to? 79
73 74 75 76 77 78 79
72 43 44 45 46 47 48 49 50
71 42 21 22 23 24 25 26 51
70 41 20 7 8 9 10 27 52
69 40 19 6 1 2 11 28 53
68 39 18 5 4 3 12 29 54
67 38 17 16 15 14 13 30 55
66 37 36 35 34 33 32 31 56
65 64 63 62 61 60 59 58 57
There are a few steps to the problem. First, set up a grid. The size of the grid will need to be equal to the next higher perfect square; for example, if you enter 23, you need a 5 × 5 (25) grid, or if you enter 31 you'll need a 6 × 6 grid (36). Next, store the next value of the number sequence in a "current position" (i.e., the center). At each step, check the cardinal directions and move the "current position" to the location which has not previously been filled in which is closest to the center, with a bias towards east (to deal with the initial step, where there is no difference in N, S, E, W). Proceed until your iterator is finished.
Edit: I really enjoyed this question, so I went to write a nice solution. It's been a bit since I wrote Python, so this may not be the most elegant, but nevertheless.
from functools import partial
from math import ceil, sqrt
def gen_grid(n):
grid_size = int(ceil(sqrt(n)))
return [[None for _ in range(grid_size)] for _ in range(grid_size)]
def valid_coord(grid, coord):
try:
return grid[coord[0]][coord[1]] is None
except:
return False
def origin(size):
adjustment = 1 if size % 2 == 0 else 0
return (size / 2 - adjustment), (size / 2 - adjustment)
north = lambda y, x: (y - 1, x)
south = lambda y, x: (y + 1, x)
east = lambda y, x: (y, x + 1)
west = lambda y, x: (y, x - 1)
directions = lambda y, x: [east(y, x), south(y, x), west(y, x), north(y, x)]
distance = lambda c, nxt: sqrt((c[0] - nxt[0]) ** 2 + (c[1] - nxt[1]) ** 2)
def walk_grid(nums):
grid = gen_grid(len(nums))
center = origin(len(grid[0]))
current_position = center
center_distance = partial(distance, center)
for n in nums:
y, x = current_position
grid[y][x] = n
unseen_points = [c for c in directions(y, x) if valid_coord(grid, c)]
if n != nums[-1]:
current_position = sorted(unseen_points, key=center_distance)[0]
return grid
def print_grid(highest):
result = walk_grid(range(1, highest + 1))
for row in result:
for col in row:
print "{:>4}".format(col if col is not None else ''),
print "\n"
Example output:
In [2]: grid.print_grid(25)
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
so I assume you can already somehow determine the size of the array and the position of the "one". now you need a function that allows you to change direction and a counter.
def get_new_direction(direction):
switch direction:
case E: return S
case S: return W
case W: return N
case N: return E
i,j = initial_coordinates_of_the_one
direction = right
steps = 1
next_number = 1
while not done:
place(next_number, i, j)
i,j = get_coordinates_after_move(direction, steps)
direction = get_new_direction(direction)
next_number++
if iteration is even:
steps++
this is just a sketch. what is still missing (but easy to figure out):
- how to implement the functions
- how to set multiple numbers while following one direction
来源:https://stackoverflow.com/questions/23706690/how-do-i-make-make-spiral-in-python