I'm trying to print the diagonals for a 2d array starting with the bottom left corner and moving towards the top right corner. I've managed to print the first half of the matrix but I got stuck when I have to print the second part of it and I'm hoping somebody can give me a clue how to continue. Here is what I have:
matrix = [["A", "B", "C", "D"],
["E", "F", "G", "H"],
["I", "J", "K", "L"],
["M", "N", "O", "P"],
["Q", "R", "S", "T"]]
and the partial function that print the diagonals up to a point:
def diagonal_matrix_print(input_matrix):
width = len(input_matrix[0])
height = len(input_matrix)
start_row = height - 1
first_row = 0
for start_row in reversed(range(0, height)):
i = start_row
for column in range(0, width):
if i == height:
start_row = start_row - 1
break
print input_matrix[i][column]
i = i + 1
print
The issue I'm facing is printing the diagonals that start with the second half of the matrix - B G L, C H, D
I tried using another 2 for loops for it like:
for row in range (0, height -1):
i = row
for start_column in range(1, width):
print input_matrix[i][start_column]
i = i + 1
but when the row value changes to 1, is not printing the diagonal anymore...
Suppose we have the list of lists L:
>>> L = [[ 0, 1, 2],
[ 3, 4, 5],
[ 6, 7, 8],
[ 9, 10, 11]])
We want a function diagonals such that
>>> diagonals(L)
[[9], [6, 10], [3, 7, 11], [0, 4, 8], [1, 5], [2]]
We can think about items in L with respect to 2 different coordinate
systems. There is the usual (x, y) coordinate system where (x, y) corresponds to
the location with value L[x][y].
And then there is also the (p, q) coordinate system where p represents the
pth diagonal, with p=0 being the diag at the lower-left corner. And q
represents the qth item along the pth diag, with q=0 starting at the left
edge. Thus (p, q) = (0,0) corresponds to the location with value L[3][0] = 9
in the example above.
Let h,w equal the height and width of L respectively.
Then p ranges from 0 to h + w - 1.
We want a formula for translating from (p, q) coordinates to (x, y) coordinates.
x decreases linearly as p increases.
x increases linearly as q increases.
When (p, q) = (0, 0), x equals h.
Therefore: x = h - p + q.
y does not change with p (if q is fixed).
y increases linearly as q increases.
When (p, q) = (0, 0), y equals q.
Therefore, y = q.
Now the extent of valid values for x and y requires that:
(0 <= x = h - p + q < h) and (0 <= y = q < w)
which is equivalent to
(p - h + 1 <= q < p + 1) and (0 <= q < w)
which is equivalent to
max(p - h + 1, 0) <= q < min(p + 1, w)
Therefore we can loop through the items of L using
for p in range(h + w - 1):
for q in range(max(p-h+1, 0), min(p+1, w))
L[h - p + q - 1][q]
def diagonals(L):
h, w = len(L), len(L[0])
return [[L[h - p + q - 1][q]
for q in range(max(p-h+1, 0), min(p+1, w))]
for p in range(h + w - 1) ]
matrix = [ ["A", "B", "C", "D"], ["E","F","G","H"], ["I","J","K","L"], ["M","N","O","P"], ["Q", "R", "S","T"]]
for diag in diagonals(matrix):
print(diag)
yields
['Q']
['M', 'R']
['I', 'N', 'S']
['E', 'J', 'O', 'T']
['A', 'F', 'K', 'P']
['B', 'G', 'L']
['C', 'H']
['D']
Related
This is a follow-up to my previous question here. I have a optimization model that tries to find the highest coverage of a set of probe to a sequence. I approached it by creating an overlap matrix as shown below.
import pyomo
import pyomo.environ as pe
import pyomo.opt as po
import numpy as np
import matplotlib.pyplot as plt
# Initialise all sequences and probes
sequence = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
probes = ["a", "b", "c", "d", "e", "f", "g", "h"]
probe_starts = {"a": 0, "b": 1, "c": 4, "d": 5, "e": 6, "f": 8, "g": 13, "h": 12}
probe_ends = {"a": 2, "b": 2, "c": 6, "d": 6, "e": 8, "f": 11, "g": 15, "h": 14}
probe_lengths = {
p: e - s + 1 for (p, s), e in zip(probe_starts.items(), probe_ends.values())
}
# Create a matrix of probes against probes to check for overlap
def is_overlapping(x, y):
x_start, x_end = x
y_start, y_end = y
return (
(x_start >= y_start and x_start <= y_end)
or (x_end >= y_start and x_end <= y_end)
or (y_start >= x_start and y_start <= x_end)
or (y_end >= x_start and y_end <= x_end)
)
overlap = {}
matrix = np.zeros((len(probes), len(probes)))
for row, x in enumerate(zip(probe_starts.values(), probe_ends.values())):
for col, y in enumerate(zip(probe_starts.values(), probe_ends.values())):
matrix[row, col] = is_overlapping(x, y)
overlap[probes[row]] = list(matrix[row].astype(int))
I now build up my model as normal, adding a constraint that if one probe is assigned than any overlapping probes cannot be assigned.
# Model definition
model = pe.ConcreteModel()
model.probes = pe.Set(initialize=probes)
model.lengths = pe.Param(model.probes, initialize=probe_lengths)
model.overlap = pe.Param(model.probes, initialize=overlap, domain=pe.Any)
model.assign = pe.Var(model.probes, domain=pe.Boolean)
# Objective - highest coverage
obj = sum(model.assign[p] * probe_lengths[p] for p in model.probes)
model.objective = pe.Objective(expr=obj, sense=pe.maximize)
# Constraints
model.no_overlaps = pe.ConstraintList()
for query in model.probes:
model.no_overlaps.add(
sum(
[
model.assign[query] * model.assign[p]
for idx, p in enumerate(model.probes)
if model.overlap[query][idx]
]
)
<= 1
)
This works when solving with the quadratic BONMIN solver as shown below. However, when scaling up to a few thousand probes with significantly more overlap then this becomes prohibitively slowly.
solver = po.SolverFactory("BONMIN")
results = solver.solve(model)
visualize = np.zeros((len(probes), len(sequence)))
for idx, (start, end, val) in enumerate(
zip(probe_starts.values(), probe_ends.values(), model.assign.get_values().values())
):
visualize[idx, start : end + 1] = val + 1
plt.imshow(visualize)
plt.yticks(ticks=range(len(probes)), labels=probes)
plt.xticks(range(len(sequence)))
plt.colorbar()
plt.show()
Any suggestions regarding how to convert this into a linear problem would be appreciated. Thanks in advance!
You can attack this as an Integer Program (IP). There are 2 variables you need: one to indicate whether a probe has been "assigned" and another to indicate (or count) if a spot s in the sequence is covered by probe p in order to do the accounting.
It also helps to chop up the sequence into subsets (shown) that are indexed by the probes which could cover them, if assigned.
There is probably a dynamic programming approach to this as well that somebody might chip in. This works...
Code:
# model to make non-contiguous connections across a sequence
# with objective to "cover" as many points in sequence as possible
import pyomo.environ as pe
sequence = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
probes = ["a", "b", "c", "d", "e", "f", "g", "h"]
probe_starts = {"a": 0, "b": 1, "c": 4, "d": 5, "e": 6, "f": 8, "g": 13, "h": 12}
probe_ends = {"a": 2, "b": 2, "c": 6, "d": 6, "e": 8, "f": 11, "g": 15, "h": 14}
# sequence = [0, 1, 2, 3, 4, 5]
# probes = ["a", "b", "c"]
# probe_starts = {"a": 0, "b": 2, "c": 3}
# probe_ends = {"a": 2, "b": 4, "c": 5}
coverages = {p:[t for t in sequence if t>=probe_starts[p] and t<=probe_ends[p]] for p in probes}
# Model definition
model = pe.ConcreteModel()
model.sequence = pe.Set(initialize=sequence)
model.probes = pe.Set(initialize=probes)
# make an indexed set as convenience of probes:coverage ...
model.covers = pe.Set(model.probes, within=model.sequence, initialize=coverages)
model.covers_flat_set = pe.Set(initialize=[(p,s) for p in probes for s in model.covers[p]])
model.assign = pe.Var(model.probes, domain=pe.Binary) # 1 if probe p is used...
model.covered = pe.Var(model.covers_flat_set, domain=pe.Binary) # s is covered by p
# model.pprint()
# Objective
obj = sum(model.covered[p, s] for (p, s) in model.covers_flat_set)
model.objective = pe.Objective(expr=obj, sense=pe.maximize)
# Constraints
# selected probe must cover the associated points between start and end, if assigned
def cover(model, p):
return sum(model.covered[p, s] for s in model.covers[p]) == len(model.covers[p])*model.assign[p]
model.C1 = pe.Constraint(model.probes, rule=cover)
# cannot cover any point by more than 1 probe
def over_cover(model, s):
cov_options = [(p,s) for p in model.probes if (p, s) in model.covers_flat_set]
if not cov_options:
return pe.Constraint.Skip # no possible coverages
return sum(model.covered[p, s] for (p, s) in cov_options) <= 1
model.C2 = pe.Constraint(model.sequence, rule=over_cover)
solver = pe.SolverFactory('glpk')
result = solver.solve(model)
print(result)
#model.display()
# el-cheapo visualization...
for s in model.sequence:
probe = None
print(f'{s:3d}', end='')
for p in model.probes:
if (p, s) in model.covers_flat_set and model.assign[p].value:
probe = p
if probe:
print(f' {probe}')
else:
print()
Yields:
Problem:
- Name: unknown
Lower bound: 13.0
Upper bound: 13.0
Number of objectives: 1
Number of constraints: 24
Number of variables: 32
Number of nonzeros: 55
Sense: maximize
Solver:
- Status: ok
Termination condition: optimal
Statistics:
Branch and bound:
Number of bounded subproblems: 5
Number of created subproblems: 5
Error rc: 0
Time: 0.007474184036254883
Solution:
- number of solutions: 0
number of solutions displayed: 0
0 a
1 a
2 a
3
4 c
5 c
6 c
7
8 f
9 f
10 f
11 f
12 h
13 h
14 h
15
16
[Finished in 609ms]
I'm working on A star algorithm to generate an optimal trajectory. For my problem, I'm trying to find an optimal path to drone between two points, but I need to take into consideration the height of obstacles. As you know, through searching the algorithm will calculate the g (cost) and h(heuristic) in every node and choose the best one through this formula F=G+H. I need also to calculate the height of obstacles and add it to my formula to become F=G+H+E. E will represent the height of obstacles.
If the drone is flying with a specific height and faces a very high obstacle will turn around, if the high of the obstacle is close to the drone height, it means the risk will be very high and will consider the short obstacle to fly over it.
I have generated a map with the same size as my grid and I gave random numbers (random heights) to the obstacles, then I applied it to my formula. Throughout my expansion stage, I have made a condition if the height of obstacles less the height of drone height then the drone will be able to fly over it, but I see there is no effect. Could I get any assistance?.
This is below my code:
#grid format
# 0 = navigable space
# 1 = occupied space
import random
grid = [[0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0]]
heuristic = [[9, 8, 7, 6, 5, 4],
[8, 7, 6, 5, 4, 3],
[7, 6, 5, 4, 3, 2],
[6, 5, 4, 3, 2, 1],
[5, 4, 3, 2, 1, 0]]
init = [0,0] #Start location is (0,0) which we put it in open list.
goal = [len(grid)-1,len(grid[0])-1] #Our goal in (4,5) and here are the coordinates of the cell.
#Below the four potential actions to the single field
delta = [[-1 , 0], #up
[ 0 ,-1], #left
[ 1 , 0], #down
[ 0 , 1]] #right
delta_name = ['^','<','V','>'] #The name of above actions
cost = 1 #Each step costs you one
drone_h = 60 #The altitude of drone
def search():
#open list elements are of the type [g,x,y]
closed = [[0 for row in range(len(grid[0]))] for col in range(len(grid))]
action = [[-1 for row in range(len(grid[0]))] for col in range(len(grid))]
#We initialize the starting location as checked
closed[init[0]][init[1]] = 1
expand=[[-1 for row in range(len(grid[0]))] for col in range(len(grid))]
elevation = [[0 for row in range(len(grid[0]))] for col in range(len(grid))]
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == 1:
elevation[i][j] = random.randint(1,100)
print(elevation[i][j])
else:
elevation[i][j] = 0
# we assigned the cordinates and g value
x = init[0]
y = init[1]
g = 0
h = heuristic[x][y]
e = elevation[x][y]
f = g + h + e
#our open list will contain our initial value
open = [[f, g, h, x, y]]
found = False #flag that is set when search complete
resign = False #Flag set if we can't find expand
count = 0
#print('initial open list:')
#for i in range(len(open)):
#print(' ', open[i])
#print('----')
while found is False and resign is False:
#Check if we still have elements in the open list
if len(open) == 0:
resign = True
print('Fail')
print('############# Search terminated without success')
print()
else:
open.sort()
open.reverse()
next = open.pop()
#print('list item')
#print('next')
x = next[3]
y = next[4]
g = next[1]
expand[x][y] = count
count+=1
#Check if we are done
if x == goal[0] and y == goal[1]:
found = True
print(next)
print('############## Search is success')
print()
else:
#expand winning element and add to new open list
for i in range(len(delta)):
x2 = x + delta[i][0]
y2 = y + delta[i][1]
#if x2 and y2 falls into the grid
if x2 >= 0 and x2 < len(grid) and y2 >=0 and y2 <= len(grid[0])-1:
#if x2 and y2 not checked yet and there is not obstacles
if closed[x2][y2] == 0 and grid[x2][y2] == 0 and elevation[x2][y2]< drone_h:
g2 = g + cost
h2 = heuristic[x2][y2]
e = elevation[x2][y2]
f2 = g2 + h2 + e
open.append([f2,g2,h2,x2,y2])
#print('append list item')
#print([g2,x2,y2])
#Then we check them to never expand again
closed[x2][y2] = 1
action[x2][y2] = i
for i in range(len(expand)):
print(expand[i])
print()
policy=[[' ' for row in range(len(grid[0]))] for col in range(len(grid))]
x=goal[0]
y=goal[1]
policy[x][y]='*'
while x !=init[0] or y !=init[1]:
x2=x-delta[action[x][y]][0]
y2=y-delta[action[x][y]][1]
policy[x2][y2]= delta_name[action[x][y]]
x=x2
y=y2
for i in range(len(policy)):
print(policy[i])
search()
The results that I got:
[11, 11, 0, 4, 5]
############## Search is success
[0, -1, -1, -1, -1, -1]
[1, -1, -1, -1, -1, -1]
[2, -1, -1, -1, -1, -1]
[3, -1, 8, 9, 10, 11]
[4, 5, 6, 7, -1, 12]
['V', ' ', ' ', ' ', ' ', ' ']
['V', ' ', ' ', ' ', ' ', ' ']
['V', ' ', ' ', ' ', ' ', ' ']
['V', ' ', ' ', '>', '>', 'V']
['>', '>', '>', '^', ' ', '*']
You need to associate a cost with the height of obstacles. So, you need to figure out how costly, in terms of distance, the risk a high obstacle represents, is.
So, basically your heuristic stays your actual distance is G(up to now)+E(up to now), and your heuristic stays G(up to now)+E(up to now)+H
Since you haven't stated how bad taking the risk is and how much you want to avoid it, let's say, as an example, you never want to take the risk unless there is no other way.
Then you could associate a cost for the elevation as follows:
E(x) = 0 if obstacle is low
E(x) = maximal_possible_distance + (elevation - drone height)
That way, it is always better to take a detour, and it favors smaller elevations (add a factor or exponent if you want to favor smaller elevations even more)
I'm working on A star algorithm. I'm trying to build a trajectory for drone depending on A star. I have implemented my code below. I need to consider the height of obstacles and modify my equation :
F= G+H to F=H+G+E
E: represents the elevation of obstacles. We have the drone is flying in a specific altitude over a map, if the obstacle was very high(it means its risk very high) the distance between the obstacle and the drone is too close, so the drone will prefer to fly over the short obstacle. If the obstacle higher than the altitude of the drone, it will turn around.
I added an elevation map with random height generation and drone_altitude, but it doesn't work with me. Could I get some assistance, please?.
The A-star Python Code:
import numpy
grid = [[0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 1, 0, 0, 0, 0],
[0, 0, 0, 0, 1, 0]]
heuristic = [[9, 8, 7, 6, 5, 4],
[8, 7, 6, 5, 4, 3],
[7, 6, 5, 4, 3, 2],
[6, 5, 4, 3, 2, 1],
[5, 4, 3, 2, 1, 0]]
init = [0,0]
goal = [len(grid)-1,len(grid[0])-1]
delta = [[-1 , 0], #up
[ 0 ,-1], #left
[ 1 , 0], #down
[ 0 , 1]] #right
delta_name = ['^','<','V','>'] #The name of above actions
cost = 1 #Each step costs you one
drone_height = 60
def search():
#open list elements are of the type [g,x,y]
closed = [[0 for row in range(len(grid[0]))] for col in range(len(grid))]
action = [[-1 for row in range(len(grid[0]))] for col in range(len(grid))]
#We initialize the starting location as checked
closed[init[0]][init[1]] = 1
expand=[[-1 for row in range(len(grid[0]))] for col in range(len(grid))]
elvation = numpy.random.randint(0, 100+1, size=(5, 6))
print(elvation)
# we assigned the cordinates and g value
x = init[0]
y = init[1]
g = 0
h = heuristic[x][y]
e = elvation[x][y]
f = g + h + e
#our open list will contain our initial value
open = [[f, g, h, x, y]]
found = False #flag that is set when search complete
resign = False #Flag set if we can't find expand
count = 0
#print('initial open list:')
#for i in range(len(open)):
#print(' ', open[i])
#print('----')
while found is False and resign is False:
#Check if we still have elements in the open list
if len(open) == 0: #If our open list is empty, there is nothing to expand.
resign = True
print('Fail')
print('############# Search terminated without success')
print()
else:
#if there is still elements on our list
#remove node from list
open.sort()
open.reverse() #reverse the list
next = open.pop()
#print('list item')
#print('next')
x = next[3]
y = next[4]
g = next[1]
expand[x][y] = count
count+=1
#Check if we are done
if x == goal[0] and y == goal[1]:
found = True
print(next) #The three elements above this "if".
print('############## Search is success')
print()
else:
#expand winning element and add to new open list
for i in range(len(delta)):
x2 = x + delta[i][0]
y2 = y + delta[i][1]
#if x2 and y2 falls into the grid
if x2 >= 0 and x2 < len(grid) and y2 >=0 and y2 <= len(grid[0])-1:
#if x2 and y2 not checked yet and there is not obstacles
if closed[x2][y2] == 0 and grid[x2][y2] == 0 and e < drone_height:
g2 = g + cost #we increment the cose
h2 = heuristic[x2][y2]
e2 = elvation[x2][y2]
f2 = g2 + h2 + e2
open.append([f2,g2,h2,x2,y2]) #we add them to our open list
#print('append list item')
#print([g2,x2,y2])
#Then we check them to never expand again
closed[x2][y2] = 1
action[x2][y2] = i
for i in range(len(expand)):
print(expand[i])
print()
policy=[[' ' for row in range(len(grid[0]))] for col in range(len(grid))]
x=goal[0]
y=goal[1]
policy[x][y]='*'
while x !=init[0] or y !=init[1]:
x2=x-delta[action[x][y]][0]
y2=y-delta[action[x][y]][1]
policy[x2][y2]= delta_name[action[x][y]]
x=x2
y=y2
for i in range(len(policy)):
print(policy[i])
search()
Given an m x n matrix of positive integers representing the height of each unit cell in a 2D elevation map suspended in space, compute the volume of water it is able to trap after unlimited supply of water from top . 0 represents a leakage ( matrix is suspended in space )
Note:
Both m and n are less than 110. The height of each unit cell is greater than 0 and is less than 20,000. have a list of lists where 0 in the sublists represents a leakage.
Below are described example :
[[3, 3, 3, 3, 5, 3],
[3, 0, 2, 3, 1, 3],
[3, 1, 2, 3, 1, 3],
[3, 3, 3, 1, 3, 3]]
ans = 4
We can only fill 4 units of water in this case. That's is 2 units in both 1's in 5th column, because if more than 2 units is filled it would overflow. while the water in the left valley that is left of columns 0 to 3 would leak through 0.
Eg 2
[[1,4,3,1,3,2],
[3,2,1,3,2,4],
[2,3,3,2,3,1]]
Ans = 4
How to proceed?
Below is the logic of treating 0 as on the ground. I want to modify this to the case of 0 representing leakage/drainage.
def trapRainWater(self, heightMap):
"""
:type heightMap: List[List[int]]
:rtype: int
"""
m = len(heightMap)
if not m:
return 0
n = len(heightMap[0])
if not n:
return 0
is_visited = [[False for i in xrange(n)] for j in xrange(m)]
heap = []
for i in xrange(m):
heappush(heap, [heightMap[i][0], i, 0])
is_visited[i][0] = True
heappush(heap, [heightMap[i][n-1], i, n-1])
is_visited[i][n-1] = True
for j in xrange(n):
heappush(heap, [heightMap[0][j], 0, j])
is_visited[0][j] = True
heappush(heap, [heightMap[m-1][j], m-1, j])
is_visited[m-1][j] = True
trap = 0
while heap:
height, i, j = heappop(heap)
for (dx, dy) in [(1,0), (-1,0), (0,1), (0,-1)]:
x, y = i+dx, j+dy
if 0 <= x < m and 0 <= y < n and not is_visited[x][y]:
trap += max(0, height - heightMap[x][y])
heappush(heap, [max(height, heightMap[x][y]), x, y])
is_visited[x][y] = True
return trap
1How would I traverse an array from the centre outwards, visiting each cell only once?
I can do it with a breadthfirst traversal by finding unvisited neighbours, but how could I do it with some kind of edit-distance traversal? I've been trying to figure it out on paper, but can't wrap my head around it.
eg, in an array
[
[5 6 8 9 0]
[1 2 4 5 6]
[5 4 0 2 1]
[1 2 3 4 5]
[1 2 3 4 5]]
starting from the zero in the centre, we would visit the 4 at [1][2] then the 2 at [2][3] then the 3 at [3][2] then the 4 at [2][1] then the 8 at [0][2]and then the 5 at the [1][3] etc etc
I've tried this, which gets close, but misses some.
def traversalOrder(n): #n is size of array (always square)
n = n/2
t = []
for k in range(1,n+1):
t += [(i,j) for i in range(n-k,n+k+1) for j in range(n-k,n+k+1) if (i,j) not in t and (i-j == k or j-i == k) ]
I have an open source library pixelscan that does this sort of spatial pattern traversal on a grid. The library provides various scan functions and coordinate transformations. For example,
x0, y0, r1, r2 = 0, 0, 0, 2
for x, y in ringscan(x0, y0, r1, r2, metric=manhattan):
print x, y
where
x0 = Circle x center
y0 = Circle y center
r1 = Initial radius
r2 = Final radius
metric = Distance metric
produces the following points in a diamond:
(0,0) (0,1) (1,0) (0,-1) (-1,0) (0,2) (1,1) (2,0) (1,-1) (0,-2) (-1,-1) (-2,0) (-1,1)
You can apply a translation to start at any center point you want.
It seems that you could use some sort of priority queue to sort the elements based on an edit distance, like you suggested. While my edit distance doesn't give you the EXACT order you are looking for, it might be a starting point. I used heapq.
import heapq
grid = [
[5, 6, 8, 9, 0],
[1, 2, 4, 5, 6],
[5, 4, 0, 2, 1],
[1, 2, 3, 4, 5],
[1, 2, 3, 4, 5]]
rows = len(grid)
cols = len(grid[0])
heap_list = []
for row in xrange(rows):
for col in xrange(cols):
edit_distance = abs(row - rows/2) + abs(col - cols/2)
#heappush(heap, (priority, object))
heapq.heappush(heap_list, (edit_distance, grid[row][col]))
for i in xrange(len(heap_list)):
print heapq.heappop(heap_list)
# prints (distance, value)
# (0, 0)
# (1, 2)
# (1, 3)
# (1, 4)
# (1, 4)
# (2, 1)
# etc...
I think the easiest way to do this is with three nested loops. The outer most loop is over the expanding radiuses of your diamonds. The next loop is the four sides of a given diamond, described by a starting point and a vector the move along. The innermost loop is over the points along that side.
def traversal(n):
h = n//2
yield h, h # center tile doesn't get handled the by the loops, so yield it first
for r in range(1, n):
for x0, y0, dx, dy in [(h, h-r, 1, 1),
(h+r, h, -1, 1),
(h, h+r, -1, -1),
(h-r, h, 1, -1)]:
for i in range(r):
x = x0 + dx*i
y = y0 + dy*i
if 0 <= x < n and 0 <= y < n:
yield x, y
If n is always odd, you can improve the performance of the inner loop a bit by limiting i rather than computing all the points and doing bounds tests to skip the ones that are outside your grid. Switching range(r) to range(max(0, r-h), max(r, h+1)) and getting rid of the if before the yield should do it. I'll leave the version above however, since its logic is much more clear.