I am creating a simple program that operates using Moore's Neighborhood. So given a grid, row, and a column it should return the amount of cells in the vicinity of the position that contain a 1. It works, except when given a position on the edge of the grid. Since it is checking all grids surrounding it, it throws an IndexError when it tries to check a position outside of the grid. What I want it to do is just ignore it without stopping, throwing an error, or manipulating my results, and move onto the next one. But I'm not sure how, I tried doing an exception on the IndexError but it quits out of the loop once it encounters one.
def count_neighbours(grid, row, col):
count = 0
pos = grid[row][col]
try:
for cell in [grid[row+1][col], #(0,-1) All relative to pos
grid[row-1][col], #(0,1)
grid[row+1][col+1], #(1,-1)
grid[row+1][col-1], #(-1,-1)
grid[row][col-1], #(-1,0)
grid[row][col+1], #(1,0)
grid[row-1][col+1], #(1,-1)
grid[row-1][col-1]]: #(-1,1)
if cell == 1:
count += 1
except IndexError:
pass
return count
assert count_neighbours(((1, 1, 1),
(1, 1, 1),
(1, 1, 1),), 0, 2) == 3
The loop is stopping because you are wrapping the entire loop in a try except you want something like this
def count_neighbours(grid, row, col): count = 0
pos = grid[row][col]
for cell in [[row+1,col], #(0,-1) All relative to pos
[row-1,col], #(0,1)
[row+1,col+1], #(1,-1)
[row+1,col-1], #(-1,-1)
[row,col-1], #(-1,0)
[row,col+1], #(1,0)
[row-1,col+1], #(1,-1)
[row-1,col-1]]: #(-1,1)
try:
temp_cell = grid[cell[0]][cell[1]]
if temp_cell == 1:
count += 1
except IndexError:
pass
return count
assert count_neighbours(((1, 1, 1),
(1, 1, 1),
(1, 1, 1),), 0, 2) == 3
Try a different approach, first compute the valid coords for a given point and then check for ones.
For instance you could use this function:
def compute_coords_around(x, y, boundary):
xcoords = [x-1, x, x+1]
ycoords = [y-1, y, y+1]
valid_coords = []
for xc in xcoords:
for yc in ycoords:
if xc <= boundary and yc <= boundary:
valid_coords.append((xc,yc))
return valid_coords
and lets say you want check for adjacent cells of (2, 2) in a matrix of 3x3. You know the maximum value of a column or a row is 2. So you can:
compute_coords_around(2, 2, 2)
That will give you the list:
[(1, 1), (1, 2), (2, 1), (2, 2)]
while:
compute_coords_around(1, 1, 2)
give you:
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
Then your code could be modified to:
def count_neighbours(grid, row, col):
count = 0
pos = grid[row][col]
for (x, y) in compute_coords_around(row, col, len(grid) - 1)
if grid[x][y] == 1:
count += 1
return count
You need finer-grained exception handling (and your algorithm needs to explicitly check for otherwise legal -- in Python -- indices less than zero). Here's one way to achieve both:
OFFSETS = ((-1, -1), (-1, 0), (-1, 1),
( 0, -1), ( 0, 1),
( 1, -1), ( 1, 0), ( 1, 1))
def count_neighbours(grid, row, col):
count = 0
for dr, dc in OFFSETS:
try:
x, y = row+dr, col+dc
if x < 0 or y < 0: # Disallow negative indices.
raise IndexError
if grid[x][y] == 1: # Could also cause an IndexError.
count += 1
except IndexError:
pass
return count
assert count_neighbours(((1, 1, 1),
(1, 1, 1),
(1, 1, 1),), 0, 2) == 3
However having to add an explicit check for negative indices in the inner-most loop is kind of ugly. As I mentioned in a comment, adding an extra row and column to the grid would certainly simplify the processing, as illustrated below:
OFFSETS = ((-1, -1), (-1, 0), (-1, 1),
( 0, -1), ( 0, 1),
( 1, -1), ( 1, 0), ( 1, 1))
def count_neighbours(grid, row, col):
count = 0
for dr, dc in OFFSETS:
try:
if grid[row+dr][col+dc] == 1:
count += 1
except IndexError:
pass
return count
# Note the changed position coordinate arguments.
assert count_neighbours(((0, 0, 0, 0),
(0, 1, 1, 1),
(0, 1, 1, 1),
(0, 1, 1, 1),), 1, 3) == 3
Related
This question already has an answer here:
Stimulating Liquid Flow on Matrix
(1 answer)
Closed 2 years ago.
Simulate a liquid flow through a randomly created square matrix that contains a set of integers from 0-9 using Python. The liquid should start from the top left corner of the matrix. It could only move towards right or below adjacent matrix. The lower value of the adjacent matrix, the higher potential for it to flow. In the case of the right and below adjacent matrix have the same value, the program should be able to simulate both conditions (Refer attached example image). The movement of the liquid is considered stop at the right or below edge of the matrix. The program should be able to show the sum of all numbers that the liquid has pass through, in all possibilities and visualize the path. Visualization can be done by replacing the numbers lying in the path with asterisk (*) , vertical line/pipe symbol (|), hyphen (-) or other relevant symbols such as (~,>= etc). Other methods to visualize can be accepted. Example output should look like this:
Example output visualization
This is what i have coded so far, but it does not output all the possible outcomes and the sum of integers in which the liquid flows through;
import random
import numpy as np
import copy
a=[]
n=int(input("Enter matrix size between 8 to 20 only= "))
while True:
if n<8:
n=int(input("Input size less than 8! Enter at least 8 or more = "))
elif n>20:
n=int(input("Input size more than 20! Enter at most 20 or lesser = "))
else:
break
print()
print(f'{n} × {n} matrix generated from random numbers of 0-9 :\n')
def create_matrix(n, a):
for i in range (n):
v=[]
for j in range (n):
v.append(random.randint(0,9))
a.append(v)
my.create_matrix(n, a)
b=copy.deepcopy(a)
#c=copy.deepcopy(a)
def print_array(n, array):
for i in range(n):
for j in range(n):
print(array[i][j], end=" ")
print()
print()
my.print_array(n, a)
def move_right(b, i, j, n):
b[0][0]="."
while i+1 < n and j+1<n :
if b[i+1][j] < b[i][j+1]:
b[i+1][j]="."
i+=1
elif b[i][j+1] < b[i+1][j]:
b[i][j+1]="."
j+=1
elif b[i+1][j] == b[i][j+1]:
b[i][j]="*"
#c=copy.deepcopy(b)
#move_bottom(c, i, j, n)
b[i][j+1]="."
j+=1
else:
break
def move_bottom(array,i ,j, n):
array[i][j]="."
alt=0
while i+1 < n and j+1<n :
if array[i+1][j] < array[i][j+1]:
array[i+1][j]="."
i+=1
elif array[i][j+1] < array[i+1][j]:
array[i][j+1]="."
j+=1
elif array[i+1][j] == array[i][j+1]:
array[i][j]="*"
bb=copy.deepcopy(array)
move_right(bb,i,j,n)
array[i+1][j]="."
i+=1
alt+=1
else:
break
print_array(n, array)
my.move_bottom(b, 0, 0, n)
I really need help with my coding so that i can output all the possible outcomes that the liquid can flow and the sum of integers in which the liquid flows through. If there's any other way to easily code this program using python given the conditions, please let me know!
Here is how you could do this for all possible ways.
A map of
456
867
978
would be represented as dictionary of dictionaries:
{ (0,0): {(1,0): {(2,0): {},
(1,1): { (2,1): {},
(1,2): {}}}}
and can be used to generate all paths from it.
Then you need a copy of the original numbers and can add the ascii art instead of the numbers at the correct positions. The total map has to check if you add another way to a formerly already set position, and if so replace it with a "both-ways" marker.
Some utility-methods:
import random
import copy
random.seed(42) # fixed for repeatability
class Consts:
"""Some constants to use throughout"""
VALUES = range(10)
SIZE = 8
START = (0,0)
OUT_OF_BOUNDS = 99
def generate():
"""Generates an Consts.SIZE * Consts.SIZE list of lists of numbers 0-9"""
n = Consts.SIZE
data = random.choices(Consts.VALUES, k = n*n)
return [data[i * n : i * n + n] for i in range(n)]
def v(data, x, y):
"""Returns the value in data at position x,y or
Consts.OUT_OF_BOUNDS if out of bounds."""
try:
return data[y][x]
except:
return Consts.OUT_OF_BOUNDS
def peek_east(data, pos):
"""Returs the value, position tuple of the position one to the east"""
new_pos = pos[0] + 1, pos[1]
return v(data, *new_pos), new_pos
def peek_south(data, pos):
"""Returs the value, position tuple of the position one to the south"""
new_pos = pos[0], pos[1] + 1
return v(data, *new_pos), new_pos
def done(pos):
"""Returns True if a position is at the border of the map"""
return Consts.SIZE-1 in pos
def pp(arr):
"""Pretty print a map / list of lists"""
print('\n'.join(''.join(map(str, n)) for n in arr))
the exploration part:
def explore(data, start=None, ways=None):
"""Creates/Explores all the ways. The exploration position are stored
as dictionary that contains all explored tiles as a dict of dict of ...
of possible ways to go through the map."""
size = Consts.SIZE
OUT = Consts.OUT_OF_BOUNDS
start = start or Consts.START
ways = ways or {}
pos = start
if done(pos):
ways[pos] = "DONE"
return
routes = []
# get east and south data to see where we can go from here
east, east_pos = peek_east(data, pos)
south, south_pos = peek_south(data, pos)
# where to move to
if east <= south:
routes.append(east_pos)
if east >= south:
routes.append(south_pos)
# add the visited tiles and the empty dicts for them to ways
for way in routes:
if pos not in ways:
ways[pos] = {}
if way not in ways:
ways[way] = {}
ways[pos][way] = ways[way]
# explore further
for way in routes:
explore(data, way, ways)
# simplify dict, only return the (0,0) element
return {Consts.START: ways[Consts.START]}
How to use it & ascii art:
array = generate()
pp(array)
exp = explore(array)
# used to create all possible paths from the dict we generated
def get_paths(tree, cur=()):
""" Source: https://stackoverflow.com/a/11570745/7505395 """
if not tree or tree == "DONE":
yield cur
else:
for n, s in tree.items():
for path in get_paths(s, cur+(n,)):
yield path
p = list(get_paths(exp))
# copy the original map for a "all in one" map
d_all = copy.deepcopy(array)
for path in p:
# copy the original map for this paths map
d = copy.deepcopy(array)
# get from,to pairs from this runway
for (_from, _to) in zip(path, path[1:]):
_, pe = peek_east(array, _from)
_, ps = peek_south(array, _from)
# ASCII Art the map
if _to == pe:
d[_from[1]][_from[0]] = "-"
d_all[_from[1]][_from[0]] = "-" if isinstance(d_all[_from[1]][_from[0]], int) else ("-" if d_all[_from[1]][_from[0]] == "-" else "+")
else:
d[_from[1]][_from[0]] = "|"
d_all[_from[1]][_from[0]] = "|" if isinstance(d_all[_from[1]][_from[0]], int) else ("|" if d_all[_from[1]][_from[0]] == "|" else "+")
# ASCII Art the last one
d[_to[1]][_to[0]] = "°"
d_all[_to[1]][_to[0]] = "°"
# print this map
print("\nPath: ", path)
pp(d)
# and total map
print("\nTotal mapping")
pp(d_all)
Output:
60227680
40250165
25808631
93008687
59358685
70220212
63322966
17139656
Path: ((0, 0), (1, 0), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (6, 1),
(6, 2), (7, 2))
-|227680
4-----|5
258086-°
93008687
59358685
70220212
63322966
17139656
Path: ((0, 0), (1, 0), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (5, 2),
(6, 2), (7, 2))
-|227680
4----|65
25808--°
93008687
59358685
70220212
63322966
17139656
Path: ((0, 0), (1, 0), (1, 1), (2, 1), (3, 1), (3, 2), (3, 3), (3, 4),
(3, 5), (4, 5), (5, 5), (6, 5), (7, 5))
-|227680
4--|0165
258|8631
930|8687
593|8685
702----°
63322966
17139656
Path: ((0, 0), (1, 0), (1, 1), (2, 1), (3, 1), (3, 2), (3, 3), (3, 4),
(3, 5), (4, 5), (4, 6), (5, 6), (6, 6), (6, 7))
-|227680
4--|0165
258|8631
930|8687
593|8685
702-|212
6332--|6
171396°6
Path: ((0, 0), (1, 0), (1, 1), (2, 1), (3, 1), (3, 2), (3, 3), (3, 4),
(3, 5), (4, 5), (4, 6), (5, 6), (5, 7))
-|227680
4--|0165
258|8631
930|8687
593|8685
702-|212
6332-|66
17139°56
Path: ((0, 0), (1, 0), (1, 1), (2, 1), (3, 1), (3, 2), (3, 3), (3, 4),
(3, 5), (4, 5), (4, 6), (4, 7))
-|227680
4--|0165
258|8631
930|8687
593|8685
702-|212
6332|966
1713°656
Total mapping
-|227680
4--+-+|5
258|8--°
930|8687
593|8685
702-+--°
6332++|6
1713°°°6
I have a board, and I want to model a bishop's possible moves on it. I attempted this code:
for c1, c2 in [(1, -1), (1, 1), (-1, -1), (-1, 1)]:
for x, y in [range(x+c1, board_size), range(y+c2, board_size)]:
moves.append(x, y)
But it doesn't work to find all the moves. Yet, I don't understand why. Doesn't it check all four directions?
Your logic is sound, but your execution is not.
Half of your calculations must go from x or y to 0 (the other half go from x or y to board_size
Ranges don't work from larger to smaller values with the default step, so you'll need to introduce a step of -1 to count from x or y to 0
You should use zip() to create an iterable collection of tuples.
This will work:
right_up = zip(range(x + 1, board_size), range(y - 1, -1, -1))
right_down = zip(range(x + 1, board_size), range(y + 1, board_size))
left_up = zip(range(x - 1, -1, -1), range(y - 1, -1, -1))
left_down = zip(range(x - 1, -1, -1), range(y + 1, board_size))
for r in (right_up, right_down, left_up, left_down):
for new_x, new_y in r: # add coordinates to move list
I have a list that consists of all combinations of tuples that each elements can only be -1 or 1. The list can be generated as:
N=2
list0 = [p for p in itertools.product([-1, 1], repeat=N)]
For example, if the tuple has N=2 elements:
list0 = [(-1, -1), (-1, 1), (1, -1), (1, 1)]
Thus the total number of tuples is 2^2=4.
If the tuple has N=3 elements:
list0 = [(-1, -1, -1), (-1, -1, 1), (-1, 1, -1), (-1, 1, 1),
(1, -1, -1), (1, -1, 1), (1, 1, -1), (1, 1, 1)]
Here is my concern:
Now I would like to get all the results of dot products between any pair of tuples in the list(including ones a tuple with itself). So for N=2 there will be 6(pairs) + 4(itself) = 10 combinations; for N=3 there will be 28(pairs) + 8(itself) = 36 combinations.
For small N I can do something like:
for x in list0:
for y in list0:
print(np.dot(x,y))
However, assuming I already have list0, what is the optimal way to calculate all the possibilities of dot products, if N is large, like ~50?
You could use the np.dot itself:
import numpy as np
list0 = [(-1, -1, -1), (-1, -1, 1), (-1, 1, -1), (-1, 1, 1), (1, -1, -1), (1, -1, 1), (1, 1, -1), (1, 1, 1)]
# approach using np.dot
a = np.array(list0)
result = np.dot(a, a.T)
# brute force approach
brute = []
for x in list0:
brute.append([np.dot(x, y) for y in list0])
brute = np.array(brute)
print((brute == result).all())
Output
True
What you are asking is the matrix multiplication of a with itself, from the documentation:
if both a and b are 2-D arrays, it is matrix multiplication,
Note that the most pythonic solutio is to use the operator #:
import numpy as np
list0 = [(-1, -1, -1), (-1, -1, 1), (-1, 1, -1), (-1, 1, 1), (1, -1, -1), (1, -1, 1), (1, 1, -1), (1, 1, 1)]
# approach using np.dot
a = np.array(list0)
result = a # a.T
# brute force approach
brute = []
for x in list0:
brute.append([np.dot(x, y) for y in list0])
brute = np.array(brute)
print((brute == result).all())
Output
True
Note: The code was run in Python 3.5
You can stick with numpy
import numpy as np
import random
vals = []
num_vecs = 3
dimension = 4
for n in range(num_vecs):
val = []
for _ in range(dimension):
val.append(random.random())
vals.append(val)
# make into numpy array
vals = np.stack(vals)
print(vals.shape == (num_vecs, dimension))
# multiply every vector with every other using broadcastin
every_with_every_mult = vals[:, None] * vals[None, :]
print(every_with_every_mult.shape == (num_vecs, num_vecs, dimension))
# sum the final dimension
every_with_every_dot = np.sum(every_with_every_mult, axis=every_with_every_mult.ndim - 1)
print(every_with_every_dot.shape == (num_vecs, num_vecs))
# check it works
for i in range(num_vecs):
for j in range(num_vecs):
assert every_with_every_dot[i,j] == np.sum(vals[i]*vals[j])
I am writing a Chess program in Python that needs to generate all the moves of a knight. For those not familiar with chess, a knight moves in an L shape.
So, given a position of (2, 4) a knight could move to (0, 3), (0, 5), (1, 2), (3, 2), etc. for a total of (at most) eight different moves.
I want to write a function called knight_moves that generates these tuples in a list. What is the easiest way to do this in Python?
def knight_moves(position):
''' Returns a list of new positions given a knight's current position. '''
pass
Ok, so thanks to Niall Byrne, I came up with this:
from itertools import product
def knight_moves(position):
x, y = position
moves = list(product([x-1, x+1],[y-2, y+2])) + list(product([x-2,x+2],[y-1,y+1]))
moves = [(x,y) for x,y in moves if x >= 0 and y >= 0 and x < 8 and y < 8]
return moves
Why not store the relative pairs it can move in ? So take your starting point, and add a set of possible moves away from it, you then would just need a sanity check to make sure they are still in bounds, or not on another piece.
ie given your (2, 4) starting point, the options are (-2,-1), (-2,+1), (-1,+2), (+2,+1)
The relative positions would thus always be the same.
Not familiar with chess...
deltas = [(-2, -1), (-2, +1), (+2, -1), (+2, +1), (-1, -2), (-1, +2), (+1, -2), (+1, +2)]
def knight_moves(position):
valid_position = lambda (x, y): x >= 0 and y >= 0 and ???
return filter(valid_position, map(lambda (x, y): (position[0] + x, position[1] + y), deltas))
Instead of using an array, I would suggest you use bitboards. Not only are they very easy to manipulate, they will also reduce the need for boundary checking. With as few as 12 bitboards, you could probably encode the information you need for the whole game.
https://www.chessprogramming.org/Bitboards
The basic idea of bitboards is to use a 64 bit integer and set 1 if a piece is present on the bit. For example, if you had a 64 bit integer to represent white knights, you would set the 2nd and 6th bits at the starting of the game as they are the positions where the white knights are located. Using this notation, it becomes easy to calculate the knight's moves. It will be easy to calculate other pieces' moves too.
With this representation, you could take a look at this link to the chess engine for a ready made algorithm to implement knight's moves.
http://www.mayothi.com/nagaskakichess6.html
This might sound as an overkill if you're not familiar with analytical geometry (or complex numbers geometry) but I came up with a very elegant mathematical solution when
I was implementing a validation for the movement of pieces.
The knight's moves are lying on a circle which can be defined as
(x-x_0)^2+(y-y_0)^2=5 where x_0 and y_0 are the Knight's current coordinates. If you switch to polar coordinates, you can get all possible coordinates with this simple code:
import math
def knight_moves(x,y):
new_positions=[]
r=math.sqrt(5) #radius of the circle
for phi in [math.atan(2),math.atan(1/2)]: #angles in radians
for quadrant in range(4):
angle=phi+quadrant*math.pi/2 # add 0, 90, 180, 270 degrees in radians
new_x=round(x+r*math.cos(angle))
new_y=round(y+r*math.sin(angle))
if max(new_x,new_y,7-new_x,7-new_y)<=7: #validation whether the move is in grid
new_positions.append([new_x,new_y])
return(new_positions)
def validate_knight_move(x,y,x_0,y_0):
return((x-x_0)**2+(y-y_0)**2==5)
x_0=2
y_0=4
moves=knight_moves(x_0,y_0)
print(moves)
validation=[validate_knight_move(move[0],move[1],x_0,y_0) for move in moves]
print(validation)
[[3, 6], [0, 5], [1, 2], [4, 3], [4, 5], [1, 6], [0, 3], [3, 2]]
[True, True, True, True, True, True, True, True]
It's good to point here, that it is much simpler to validate the position than to construct it directly. Therefore, it might be a good idea to just try whether all possible moves lie on the circle or not:
def knight_moves2(x,y):
new_positions=[]
for dx in [-2,-1,1,2]:
for dy in [-2,-1,1,2]:
if(validate_knight_move(x+dx,y+dy,x,y)): #is knight move?
if max(x+dx,y+dy,7-(x+dx),7-(y+dy))<=7: #validation whether the move is in grid
new_positions.append([x+dx,y+dy])
return(new_positions)
new_positions=knight_moves2(x_0,y_0)
print(new_positions)
[[0, 3], [0, 5], [1, 2], [1, 6], [3, 2], [3, 6], [4, 3], [4, 5]]
Here's an easy implementation:
def knights_moves():
a = []
b = (1, 2)
while 1:
a.append(b)
b = (-b[0], b[1])
a.append(b)
b = (b[1], b[0])
if b in a:
return a
[(1, 2), (-1, 2), (2, -1), (-2, -1), (-1, -2), (1, -2), (-2, 1), (2, 1)]
From there you can just simply add the current position to every member of this list, and then double check for validity.
Completing xiaowl's answer,
possible_places = [(-2, -1), (-2, +1), (+2, -1), (+2, +1), (-1, -2), (-1, +2), (+1, -2), (+1, +2)]
def knight_moves(cur_pos):
onboard = lambda (x, y): x >= 0 and y >= 0 and x<8 and y<8
eval_move = lambda(x,y): (cur_pos[0] + x, cur_pos[1] + y)
return filter(onboard, map(eval_move, possible_places))
For the knights moves:
def getAllValidMoves(x0, y0):
deltas = [(-2, -1), (-2, +1), (+2, -1), (+2, +1), (-1, -2), (-1, +2), (+1, -2), (+1, +2)]
validPositions = []
for (x, y) in deltas:
xCandidate = x0 + x
yCandidate = y0 + y
if 0 < xCandidate < 8 and 0 < yCandidate < 8:
validPositions.append([xCandidate, yCandidate])
return validPositions
print getAllValidMoves(3,3)
I just stored all the possible deltas, applied each one of them to the "initial position" and saved the ones that were inside the chessboard
from itertools import product
def moves():
""" The available (relative) moves"""
a = list(product( (1, -1), (2,-2)))
return a + [tuple(reversed(m)) for m in a]
def neighbors(a,b):
# true if x,y belongs in a chess table
in_table = lambda (x, y): all((x < 8, y < 8, x >= 0, y >= 0))
# returns the possible moving positions
return filter(in_table, [(a+x, b+y) for x, y in moves()])
"neighbors" are the available positions that a knight can go from a,b
The below method is implemented in python. It accepts the board (which can be of any m*n & has values 0(available) and 1(occupied) and current position of knight)
def get_knight_moves(board, position):
KNIGHT_STEPS = ((1, 2), (-1, 2), (1, -2), (-1, -2), (2, 1), (-2, 1), (2, -1), (-2, -1))
knight_moves = []
for (i, j) in KNIGHT_STEPS:
try:
x, y = position[0] + i, position[1] + j
if board[x][y] == 0:
knight_moves.append((x, y))
except IndexError:
pass
print(knight_moves)
I have this:
shape = (2, 4) # arbitrary, could be 3 dimensions such as (3, 5, 7), etc...
for i in itertools.product(*(range(x) for x in shape)):
print(i)
# output: (0, 0) (0, 1) (0, 2) (0, 3) (1, 0) (1, 1) (1, 2) (1, 3)
So far, so good, itertools.product advances the rightmost element on every iteration. But now I want to be able to specify the iteration order according to the following:
axes = (0, 1) # normal order
# output: (0, 0) (0, 1) (0, 2) (0, 3) (1, 0) (1, 1) (1, 2) (1, 3)
axes = (1, 0) # reversed order
# output: (0, 0) (1, 0) (2, 0) (3, 0) (0, 1) (1, 1) (2, 1) (3, 1)
If shapes had three dimensions, axes could have been for instance (0, 1, 2) or (2, 0, 1) etc, so it's not a matter of simply using reversed(). So I wrote some code that does that but seems very inefficient:
axes = (1, 0)
# transposed axes
tpaxes = [0]*len(axes)
for i in range(len(axes)):
tpaxes[axes[i]] = i
for i in itertools.product(*(range(x) for x in shape)):
# reorder the output of itertools.product
x = (i[y] for y in tpaxes)
print(tuple(x))
Any ideas on how to properly do this?
Well, this is in fact a manual specialised product. It should be faster since axes are reordered only once:
def gen_chain(dest, size, idx, parent):
# iterate over the axis once
# then trigger the previous dimension to update
# until everything is exhausted
while True:
if parent: next(parent) # StopIterator is propagated upwards
for i in xrange(size):
dest[idx] = i
yield
if not parent: break
def prod(shape, axes):
buf = [0] * len(shape)
gen = None
# EDIT: fixed the axes order to be compliant with the example in OP
for s, a in zip(shape, axes):
# iterate over the axis and put to transposed
gen = gen_chain(buf, s, a, gen)
for _ in gen:
yield tuple(buf)
print list(prod((2,4), (0,1)))
# [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3)]
print list(prod((2,4), (1,0)))
# [(0, 0), (1, 0), (2, 0), (3, 0), (0, 1), (1, 1), (2, 1), (3, 1)]
print list(prod((4,3,2),(1,2,0)))
# [(0, 0, 0), (1, 0, 0), (0, 0, 1), (1, 0, 1), (0, 0, 2), (1, 0, 2), ...
If you can afford it memory-wise: Let itertools.product do the hard work, and use zip to switch the axes around.
import itertools
def product(shape, axes):
prod_trans = tuple(zip(*itertools.product(*(range(shape[axis]) for axis in axes))))
prod_trans_ordered = [None] * len(axes)
for i, axis in enumerate(axes):
prod_trans_ordered[axis] = prod_trans[i]
return zip(*prod_trans_ordered)
Small test:
>>> print(*product((2, 2, 4), (1, 2, 0)))
(0, 0, 0) (1, 0, 0) (0, 0, 1) (1, 0, 1) (0, 0, 2) (1, 0, 2) (0, 0, 3) (1, 0, 3) (0, 1, 0) (1, 1, 0) (0, 1, 1) (1, 1, 1) (0, 1, 2) (1, 1, 2) (0, 1, 3) (1, 1, 3)
The above version is fast if there are not too may products. For large result sets, the following is faster, but... uses eval (although in a rather safe way):
def product(shape, axes):
d = dict(("r%i" % axis, range(shape[axis])) for axis in axes)
text_tuple = "".join("x%i, " % i for i in range(len(axes)))
text_for = " ".join("for x%i in r%i" % (axis, axis) for axis in axes)
return eval("((%s) %s)" % (text_tuple, text_for), d)
Edit: If you want to not only change the order of iteration, but also the shape (as in the OP's example), small changes are needed:
import itertools
def product(shape, axes):
prod_trans = tuple(zip(*itertools.product(*(range(s) for s in shape))))
prod_trans_ordered = [None] * len(axes)
for i, axis in enumerate(axes):
prod_trans_ordered[axis] = prod_trans[i]
return zip(*prod_trans_ordered)
And the eval version:
def product(shape, axes):
d = dict(("r%i" % axis, range(s)) for axis, s in zip(axes, shape))
text_tuple = "".join("x%i, " % i for i in range(len(axes)))
text_for = " ".join("for x%i in r%i" % (axis, axis) for axis in axes)
return eval("((%s) %s)" % (text_tuple, text_for), d)
Test:
>>> print(*product((2, 2, 4), (1, 2, 0)))
(0, 0, 0) (1, 0, 0) (2, 0, 0) (3, 0, 0) (0, 0, 1) (1, 0, 1) (2, 0, 1) (3, 0, 1) (0, 1, 0) (1, 1, 0) (2, 1, 0) (3, 1, 0) (0, 1, 1) (1, 1, 1) (2, 1, 1) (3, 1, 1)
I don't know how efficient this is, but you should be able to do something like this...
shape = (2, 4, 3)
axes = (2, 0, 1)
# Needed to get the original ordering back
axes_undo = tuple(reversed(axes))
# Reorder the shape in a configuration so that .product will give you
# the order you want.
reordered = tuple(reversed(map(lambda x: shape[x], list(axes))))
# When printing out the results from .product, put the results back
# into the original order.
for i in itertools.product(*(range(x) for x in reordered)):
print(tuple(map(lambda x: i[x], list(axes_undo))))
I tried is up to 4 dimensions and it seems to work. ;)
I'm just swapping the dimensions around and then swapping them back.
Have you tried timing to see how much longer it takes? What you have shouldn't be much slower than without reordering.
You could try modify what you have to use in-place splice assignment.
tpaxes = tuple(tpaxes)
for i in itertools.product(*(range(x) for x in shape)):
# reorder the output of itertools.product
i[:] = (i[y] for y in tpaxes)
print(tuple(x))
Also you could get a speedup by making tpaxes a local variable of a function rather than a global variable (which has slower lookup times)
Otherwise my suggestion is somehow write your own product function..
for i in itertools.product(*(range(x) for x in reversed(shape))):
print tuple(reversed(i))
import itertools
normal = (0, 1)
reverse = (1, 0)
def axes_ordering(x):
a, b = x
return b - a
shape = (2, 4)
for each in itertools.product(*(range(x) for x in shape)):
print(each[::axes_ordering(normal)], each[::axes_ordering(reverse)])
result:
(0, 0) (0, 0)
(0, 1) (1, 0)
(0, 2) (2, 0)
(0, 3) (3, 0)
(1, 0) (0, 1)
(1, 1) (1, 1)
(1, 2) (2, 1)
(1, 3) (3, 1)