Suppose I have a n x n grid, and I want a function that generates a list of all columns and rows by taking n as the input, in python. By a list of columns, I mean each column is represented as its own list, with elements being coordinates of the elements in the column. (Or each column could be a set of coordinates, that would work too)
I could do this using two list comprehensions,
x = [[ (i, j) for j in range(n)] for i in range(n)] + [[ (i, j) for i in range(n)] for j in range(n)]
With n=3 this produces a list with 9 elements, each of which is a list of 3 coordinates.
x = [[(0, 0), (0, 1), (0, 2)], [(1, 0), (1, 1), (1, 2)], [(2, 0), (2, 1), (2, 2)], [(0, 0), (1, 0), (2, 0)], [(0, 1), (1, 1), (2, 1)], [(0, 2), (1, 2), (2, 2)]]
I was wondering if there is a cleaner way to do the same thing, maybe using itertools or a similar module.
Not exactly sure if this is what you're looking for but you could try itertools.product
For example
>>> from itertools import product
>>> n = 3
>>> list(product(range(n),range(n)))
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
>>> [list(product(range(n),range(n)))[n*i:n*i+n] for i in range(n)]
[[(0, 0), (0, 1), (0, 2)], [(1, 0), (1, 1), (1, 2)], [(2, 0), (2, 1), (2, 2)]]
>>> [list(product(range(n),range(n)))[i::n] for i in range(n)]
[[(0, 0), (1, 0), (2, 0)], [(0, 1), (1, 1), (2, 1)], [(0, 2), (1, 2), (2, 2)]]
How would one go about generating a graph-like object (in R or Python) representing a regular rectangular network of connected hexagons, like in the following image:
Vertices at the "center" of the graph should each have 6 edges, and vertices at the "sides" of the graph should have either 2, 3 or 5 edges.
You can create a grid of adjacency sets representing the indices of the cells each individual cell is connected with:
class HexGraph:
def __init__(self, rows, cols):
self.rows = rows
self.cols = cols
self.cells = [[set() for c in range(self.cols)] for r in range(self.rows)]
self.build_connections()
def build_connections(self):
offsets = (((-1, 0), (1, 0), (0, -1), (0, 1), (-1, -1), (1, -1)),
((-1, 0), (1, 0), (0, -1), (0, 1), (-1, 1), (1, 1)))
for rdx, line in enumerate(self.cells):
for cdx, cell in enumerate(line):
for dr, dc in offsets[rdx % 2]:
r = rdx + dr
c = cdx + dc
if r >= 0 and r < self.rows and c >= 0 and c < self.cols:
cell.add((r, c))
def __str__(self):
result = []
for line in self.cells:
res = ''
for cell in line:
res += str(cell) + ', '
result.append(res)
return '\n'.join(result)
if __name__ == '__main__':
g = HexGraph(5, 4)
print(g)
output:
{(0, 1), (1, 0)}, {(0, 2), (1, 0), (0, 0), (1, 1)}, {(1, 2), (0, 3), (0, 1), (1, 1)}, {(1, 2), (1, 3), (0, 2)},
{(0, 1), (0, 0), (2, 1), (2, 0), (1, 1)}, {(0, 1), (1, 2), (2, 1), (2, 2), (1, 0), (0, 2)}, {(1, 3), (0, 2), (2, 3), (2, 2), (0, 3), (1, 1)}, {(1, 2), (0, 3), (2, 3)},
{(3, 0), (1, 0), (2, 1)}, {(3, 0), (3, 1), (2, 0), (2, 2), (1, 0), (1, 1)}, {(1, 2), (3, 2), (3, 1), (2, 1), (2, 3), (1, 1)}, {(1, 2), (3, 2), (1, 3), (3, 3), (2, 2)},
{(3, 1), (2, 1), (2, 0), (4, 1), (4, 0)}, {(3, 2), (3, 0), (2, 1), (2, 2), (4, 2), (4, 1)}, {(3, 3), (3, 1), (2, 3), (4, 3), (2, 2), (4, 2)}, {(3, 2), (2, 3), (4, 3)},
{(3, 0), (4, 1)}, {(3, 0), (4, 2), (3, 1), (4, 0)}, {(3, 2), (3, 1), (4, 1), (4, 3)}, {(4, 2), (3, 2), (3, 3)},
It corresponds to the connections between nodes in the image you posted, with each second row pulled a bit to the left to align vertically with the nodes directly above & under it.
Please pardon the poor quality drawing.
I want to apply the 4 color theorem ie. each neighbor polygons on a map should have a different color. The theorem state that only 4 colors is needed for any kind of map.
As input I have an array of polygon containing id and color id and a graph array of adjacent polygons. As output I want to associate a color id between 0-3 (or at maximum 0-4) to each polygons ensuring that adjacent polygons have different color id.
I have the following code (from this question) that returns a different color id for each neighboor, but it does not ensure that the minimum number of colors is used.
#array of input polygon tuples (id, color_id) color_id is None at beginning
input_polygons = [(0, None), (1, None), (2, None), (3, None), (4, None), (5, None), (6, None), (7, None), (8, None)]
#graph array of neighbors ids
adjacent_polygons_graph = [[0, 1], [0, 2], [0, 3], [0, 4], [0, 5], [1, 0], [2, 0], [2, 3], [3, 0], [3, 2], [3, 6], [3, 8], [4, 0], [4, 5], [4, 6], [5, 0], [5, 4], [6, 3], [6, 4], [6, 7], [7, 6], [8, 3]]
colored = []
for polygon in input_polygons:
nbrs = [second for first, second in adjacent_polygons_graph if first == polygon[0]]
used_colors = []
for nbr in nbrs:
used_colors += [second for first, second in colored if first == nbr]
polygon[1]=[color for color in range(10) if color not in used_colors][0]
colored.append(polygon)
I would like the script to reduce at maximum the number of colors (4 or 5) using brute force loop or other method.
Here is the graph coloring code extracted from the code in my math.stackexchange answer. That code is admittedly a little complicated because it uses Knuth's Algorithm X for two tasks: solving the trominoe packing problem, and then solving the coloring problem for each packing problem solution.
This code finds 24 3-color solutions or 756 4-color for your sample data in less than 1 second. There are actually more solutions, but I arbitrarily set the colors for the first 2 nodes to reduce the total number of solutions, and to speed up the searching algorithm a little.
''' Knuth's Algorithm X for the exact cover problem,
using dicts instead of doubly linked circular lists.
Written by Ali Assaf
From http://www.cs.mcgill.ca/~aassaf9/python/algorithm_x.html
and http://www.cs.mcgill.ca/~aassaf9/python/sudoku.txt
Python 2 / 3 compatible
Graph colouring version
'''
from __future__ import print_function
from itertools import product
def solve(X, Y, solution):
if not X:
yield list(solution)
else:
c = min(X, key=lambda c: len(X[c]))
Xc = list(X[c])
for r in Xc:
solution.append(r)
cols = select(X, Y, r)
for s in solve(X, Y, solution):
yield s
deselect(X, Y, r, cols)
solution.pop()
def select(X, Y, r):
cols = []
for j in Y[r]:
for i in X[j]:
for k in Y[i]:
if k != j:
X[k].remove(i)
cols.append(X.pop(j))
return cols
def deselect(X, Y, r, cols):
for j in reversed(Y[r]):
X[j] = cols.pop()
for i in X[j]:
for k in Y[i]:
if k != j:
X[k].add(i)
#Invert subset collection
def exact_cover(X, Y):
newX = dict((j, set()) for j in X)
for i, row in Y.items():
for j in row:
newX[j].add(i)
return newX
def colour_map(nodes, edges, ncolours=4):
colours = range(ncolours)
#The edges that meet each node
node_edges = dict((n, set()) for n in nodes)
for e in edges:
n0, n1 = e
node_edges[n0].add(e)
node_edges[n1].add(e)
for n in nodes:
node_edges[n] = list(node_edges[n])
#Set to cover
coloured_edges = list(product(colours, edges))
X = nodes + coloured_edges
#Subsets to cover X with
Y = dict()
#Primary rows
for n in nodes:
ne = node_edges[n]
for c in colours:
Y[(n, c)] = [n] + [(c, e) for e in ne]
#Dummy rows
for i, ce in enumerate(coloured_edges):
Y[i] = [ce]
X = exact_cover(X, Y)
#Set first two nodes
partial = [(nodes[0], 0), (nodes[1], 1)]
for s in partial:
select(X, Y, s)
for s in solve(X, Y, []):
s = partial + [u for u in s if not isinstance(u, int)]
s.sort()
yield s
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
input_polygons = [
(0, None), (1, None), (2, None), (3, None), (4, None),
(5, None), (6, None), (7, None), (8, None),
]
#graph array of neighbors ids
adjacent_polygons_graph = [
(0, 1), (0, 2), (0, 3), (0, 4), (0, 5), (1, 0), (2, 0), (2, 3),
(3, 0), (3, 2), (3, 6), (3, 8), (4, 0), (4, 5), (4, 6),
(5, 0), (5, 4), (6, 3), (6, 4), (6, 7), (7, 6), (8, 3),
]
# Extract the nodes list
nodes = [t[0] for t in input_polygons]
# Get an iterator of all solutions with 3 colours
all_solutions = colour_map(nodes, adjacent_polygons_graph, ncolours=3)
# Print all solutions
for count, solution in enumerate(all_solutions, start=1):
print('%2d: %s' % (count, solution))
output
1: [(0, 0), (1, 1), (2, 1), (3, 2), (4, 2), (5, 1), (6, 1), (7, 0), (8, 1)]
2: [(0, 0), (1, 1), (2, 1), (3, 2), (4, 2), (5, 1), (6, 1), (7, 0), (8, 0)]
3: [(0, 0), (1, 1), (2, 1), (3, 2), (4, 2), (5, 1), (6, 1), (7, 2), (8, 1)]
4: [(0, 0), (1, 1), (2, 1), (3, 2), (4, 2), (5, 1), (6, 1), (7, 2), (8, 0)]
5: [(0, 0), (1, 1), (2, 1), (3, 2), (4, 2), (5, 1), (6, 0), (7, 2), (8, 0)]
6: [(0, 0), (1, 1), (2, 1), (3, 2), (4, 2), (5, 1), (6, 0), (7, 1), (8, 0)]
7: [(0, 0), (1, 1), (2, 1), (3, 2), (4, 2), (5, 1), (6, 0), (7, 2), (8, 1)]
8: [(0, 0), (1, 1), (2, 1), (3, 2), (4, 2), (5, 1), (6, 0), (7, 1), (8, 1)]
9: [(0, 0), (1, 1), (2, 1), (3, 2), (4, 1), (5, 2), (6, 0), (7, 1), (8, 1)]
10: [(0, 0), (1, 1), (2, 1), (3, 2), (4, 1), (5, 2), (6, 0), (7, 1), (8, 0)]
11: [(0, 0), (1, 1), (2, 1), (3, 2), (4, 1), (5, 2), (6, 0), (7, 2), (8, 0)]
12: [(0, 0), (1, 1), (2, 1), (3, 2), (4, 1), (5, 2), (6, 0), (7, 2), (8, 1)]
13: [(0, 0), (1, 1), (2, 2), (3, 1), (4, 1), (5, 2), (6, 2), (7, 0), (8, 0)]
14: [(0, 0), (1, 1), (2, 2), (3, 1), (4, 2), (5, 1), (6, 0), (7, 2), (8, 0)]
15: [(0, 0), (1, 1), (2, 2), (3, 1), (4, 1), (5, 2), (6, 0), (7, 2), (8, 0)]
16: [(0, 0), (1, 1), (2, 2), (3, 1), (4, 2), (5, 1), (6, 0), (7, 1), (8, 0)]
17: [(0, 0), (1, 1), (2, 2), (3, 1), (4, 1), (5, 2), (6, 0), (7, 1), (8, 0)]
18: [(0, 0), (1, 1), (2, 2), (3, 1), (4, 1), (5, 2), (6, 2), (7, 1), (8, 0)]
19: [(0, 0), (1, 1), (2, 2), (3, 1), (4, 1), (5, 2), (6, 2), (7, 1), (8, 2)]
20: [(0, 0), (1, 1), (2, 2), (3, 1), (4, 1), (5, 2), (6, 2), (7, 0), (8, 2)]
21: [(0, 0), (1, 1), (2, 2), (3, 1), (4, 2), (5, 1), (6, 0), (7, 2), (8, 2)]
22: [(0, 0), (1, 1), (2, 2), (3, 1), (4, 1), (5, 2), (6, 0), (7, 2), (8, 2)]
23: [(0, 0), (1, 1), (2, 2), (3, 1), (4, 2), (5, 1), (6, 0), (7, 1), (8, 2)]
24: [(0, 0), (1, 1), (2, 2), (3, 1), (4, 1), (5, 2), (6, 0), (7, 1), (8, 2)]
If you just want a single solution, replace the last for loop with
output_polygons = next(all_solutions)
print(output_polygons)
FWIW, here's some code that can be used to create a diagram from the graph data.
# Create a Graphviz DOT file from graph data
colors = {
None: 'white',
0: 'red', 1: 'yellow',
2: 'green', 3: 'blue'
}
def graph_to_dot(nodes, edges, outfile=sys.stdout):
outfile.write('strict graph test{\n')
outfile.write(' node [style=filled];\n')
for n, v in nodes:
outfile.write(' {} [fillcolor={}];\n'.format(n, colors[v]))
outfile.write('\n')
for u, v in edges:
outfile.write(' {} -- {};\n'.format(u, v))
outfile.write('}\n')
You can call it like this:
# Produce a DOT file of the colored graph
with open('graph.dot', 'w') as f:
graph_to_dot(output_polygons, adjacent_polygons_graph, f)
It can also be used to produce a DOT file of the input_polygons.
To generate a PNG image file from the DOT file using the Graphviz neato utility, you can do this (in Bash).
neato -Tpng -ograph.png graph.dot
Here's the DOT file for the first solution above:
strict graph test{
node [style=filled];
0 [fillcolor=red];
1 [fillcolor=yellow];
2 [fillcolor=yellow];
3 [fillcolor=green];
4 [fillcolor=green];
5 [fillcolor=yellow];
6 [fillcolor=yellow];
7 [fillcolor=red];
8 [fillcolor=yellow];
0 -- 1;
0 -- 2;
0 -- 3;
0 -- 4;
0 -- 5;
1 -- 0;
2 -- 0;
2 -- 3;
3 -- 0;
3 -- 2;
3 -- 6;
3 -- 8;
4 -- 0;
4 -- 5;
4 -- 6;
5 -- 0;
5 -- 4;
6 -- 3;
6 -- 4;
6 -- 7;
7 -- 6;
8 -- 3;
}
And here's the PNG:
Basically I need lists of rows that go like this:
[0,0]
[1,0],[0,1]
[2,0],[1,1],[0,2]
[3,0],[2,1],[1,2],[0,3]
[4,0],[3,1],[2,2],[1,3],[0,4]
up to an arbitrary number of elements and then back down
[4,1],[3,2],[2,3],[1,4]
[4,2],[3,3],[2,4]
[4,3],[3,4]
[4,4]
I'm just wanting all of these pairs in one large list of lists, so I can iterate over the pairs in the order they appear above for isometric rendering.
the output would look like this
[ [ (0,0) ], [ (1,0),(0,1) ], [ (2,0), (1,1), (0,2) ]....]etc
It's not entirely clear what generalization you're looking for, but IIUC there are lots of ways you can do it. One is to build each sublist from the previous list (adding one to each subelement and avoiding duplicates), but another is to work directly from the arithmetic:
def sherwood(n):
N = 2*n+1
for i in range(N):
low, high = max(0, i-n), min(i, n)
w = list(range(low, high+1))
yield zip(w[::-1], w)
gives me
>>> out = list(sherwood(2))
>>> for x in out: print(x)
[(0, 0)]
[(1, 0), (0, 1)]
[(2, 0), (1, 1), (0, 2)]
[(2, 1), (1, 2)]
[(2, 2)]
>>> out = list(sherwood(4))
>>> for x in out: print(x)
[(0, 0)]
[(1, 0), (0, 1)]
[(2, 0), (1, 1), (0, 2)]
[(3, 0), (2, 1), (1, 2), (0, 3)]
[(4, 0), (3, 1), (2, 2), (1, 3), (0, 4)]
[(4, 1), (3, 2), (2, 3), (1, 4)]
[(4, 2), (3, 3), (2, 4)]
[(4, 3), (3, 4)]
[(4, 4)]
def create_lists(max_num):
retlist = []
for i in range(max_num+1):
i_list = []
for j in range(i, -1, -1):
i_list.append((j, i-j))
retlist.append(i_list)
for i in range(1, max_num+1):
i_list = []
for j in range(i, max_num+1):
i_list.append((max_num+i-j, j))
retlist.append(i_list)
return retlist