Skip to content

Commit 6c08d21

Browse files
committed
Added day 2017-21 and a drawing library (not very efficient...)
1 parent 60f7527 commit 6c08d21

File tree

2 files changed

+256
-0
lines changed

2 files changed

+256
-0
lines changed

2017/21-Fractal Art.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# -------------------------------- Input data -------------------------------- #
2+
import os, drawing, itertools, math
3+
4+
test_data = {}
5+
6+
test = 1
7+
test_data[test] = {"input": """../.# => ##./#../...
8+
.#./..#/### => #..#/..../..../#..#""",
9+
"expected": ['12', 'Unknown'],
10+
}
11+
12+
test = 'real'
13+
input_file = os.path.join(os.path.dirname(__file__), 'Inputs', os.path.basename(__file__).replace('.py', '.txt'))
14+
test_data[test] = {"input": open(input_file, "r+").read().strip(),
15+
"expected": ['139', '1857134'],
16+
}
17+
18+
# -------------------------------- Control program execution -------------------------------- #
19+
20+
case_to_test = 'real'
21+
part_to_test = 2
22+
verbose_level = 1
23+
24+
# -------------------------------- Initialize some variables -------------------------------- #
25+
26+
puzzle_input = test_data[case_to_test]['input']
27+
puzzle_expected_result = test_data[case_to_test]['expected'][part_to_test-1]
28+
puzzle_actual_result = 'Unknown'
29+
30+
31+
# -------------------------------- Actual code execution -------------------------------- #
32+
33+
pattern = '''.#.
34+
..#
35+
###'''
36+
37+
grid = drawing.text_to_grid(pattern)
38+
parts = drawing.split_in_parts(grid, 2, 2)
39+
merged_grid = drawing.merge_parts(parts, 2, 2)
40+
41+
42+
if case_to_test == 1:
43+
iterations = 2
44+
elif part_to_test == 1:
45+
iterations = 5
46+
else:
47+
iterations = 18
48+
49+
50+
enhancements = {}
51+
for string in puzzle_input.split('\n'):
52+
if string == '':
53+
continue
54+
55+
source, _, target = string.split(' ')
56+
source = source.replace('/', '\n')
57+
target = target.replace('/', '\n')
58+
59+
source_grid = drawing.text_to_grid(source)
60+
enhancements[source] = target
61+
62+
for rotated_source in drawing.rotate(source_grid):
63+
rotated_source_text = drawing.grid_to_text(rotated_source)
64+
enhancements[rotated_source_text] = target
65+
66+
for flipped_source in drawing.flip(rotated_source):
67+
flipped_source_text = drawing.grid_to_text(flipped_source)
68+
enhancements[flipped_source_text] = target
69+
70+
pattern_grid = drawing.text_to_grid(pattern)
71+
for i in range(iterations):
72+
73+
grid_x, grid_y = zip(*pattern_grid.keys())
74+
grid_width = max(grid_x) - min(grid_x) + 1
75+
76+
if grid_width % 2 == 0:
77+
parts = drawing.split_in_parts(pattern_grid, 2, 2)
78+
else:
79+
parts = drawing.split_in_parts(pattern_grid, 3, 3)
80+
81+
grid_size = int(math.sqrt(len(parts)))
82+
83+
new_parts = []
84+
for part in parts:
85+
part_text = drawing.grid_to_text(part)
86+
new_parts.append(drawing.text_to_grid(enhancements[part_text]))
87+
88+
new_grid = drawing.merge_parts(new_parts, grid_size, grid_size)
89+
90+
pattern_grid = new_grid
91+
92+
grid_text = drawing.grid_to_text(pattern_grid)
93+
94+
puzzle_actual_result = grid_text.count('#')
95+
96+
97+
98+
# -------------------------------- Outputs / results -------------------------------- #
99+
100+
if verbose_level >= 3:
101+
print ('Input : ' + puzzle_input)
102+
print ('Expected result : ' + str(puzzle_expected_result))
103+
print ('Actual result : ' + str(puzzle_actual_result))
104+
105+
106+
107+

2017/drawing.py

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import math, os
2+
3+
4+
def text_to_grid (text):
5+
"""
6+
Converts a text to a set of coordinates
7+
8+
The text is expected to be separated by newline characters
9+
Each character will have its coordinates as keys
10+
11+
:param string text: The text to convert
12+
:return: The converted grid, its height and width
13+
"""
14+
grid = {}
15+
lines = text.splitlines()
16+
height = len(lines)
17+
width = 0
18+
for y in range(len(lines)):
19+
width = max(width, len(lines[y]))
20+
for x in range(len(lines[y])):
21+
grid[(x, y)] = lines[y][x]
22+
23+
return grid
24+
25+
def grid_to_text (grid, blank_character = ' '):
26+
"""
27+
Converts the grid to a text format
28+
29+
:param dict grid: The grid to convert, in format (x, y): value
30+
:param string blank_character: What to use for cells with unknown value
31+
:return: The grid in text format
32+
"""
33+
34+
text = ''
35+
36+
grid_x, grid_y = zip(*grid.keys())
37+
38+
for y in range (min(grid_y), max(grid_y)+1):
39+
for x in range (min(grid_x), max(grid_x)+1):
40+
if (x, y) in grid:
41+
text += grid[(x, y)]
42+
else:
43+
text += blank_character
44+
text += os.linesep
45+
text = text[:-len(os.linesep)]
46+
47+
return text
48+
49+
def split_in_parts (grid, width, height):
50+
"""
51+
Splits a grid in parts of width*height size
52+
53+
:param dict grid: The grid to convert, in format (x, y): value
54+
:param integer width: The width of parts to use
55+
:param integer height: The height of parts to use
56+
:return: The different parts
57+
"""
58+
59+
if not isinstance(width, int) or not isinstance(height, int):
60+
return False
61+
if width <= 0 or height <= 0:
62+
return False
63+
64+
grid_x, grid_y = zip(*grid.keys())
65+
grid_width = max(grid_x) - min(grid_x) + 1
66+
grid_height = max(grid_y) - min(grid_y) + 1
67+
68+
parts = []
69+
70+
for part_y in range(math.ceil(grid_height / height)):
71+
for part_x in range (math.ceil(grid_width / width)):
72+
parts.append({(x, y):grid[(x, y)] \
73+
for x in range(part_x*width, min((part_x + 1)*width, grid_width)) \
74+
for y in range(part_y*height, min((part_y + 1)*height, grid_height))})
75+
76+
return parts
77+
78+
def merge_parts (parts, width, height):
79+
"""
80+
Merges different parts in a single grid
81+
82+
:param dict parts: The parts to merge, in format (x, y): value
83+
:return: The merged grid
84+
"""
85+
86+
grid = {}
87+
88+
part_x, part_y = zip(*parts[0].keys())
89+
part_width = max(part_x) - min(part_x) + 1
90+
part_height = max(part_y) - min(part_y) + 1
91+
92+
part_nr = 0
93+
for part_y in range(height):
94+
for part_x in range(width):
95+
grid.update({(x + part_x*part_width, y + part_y*part_height): parts[part_nr][(x, y)] for (x, y) in parts[part_nr]})
96+
part_nr += 1
97+
98+
return grid
99+
100+
def rotate (grid, rotations = (0, 90, 180, 270)):
101+
"""
102+
Rotates a grid and returns the result
103+
104+
:param dict grid: The grid to rotate, in format (x, y): value
105+
:param tuple rotations: Which angles to use for rotation
106+
:return: The parts in text format
107+
"""
108+
109+
rotated_grid = []
110+
111+
grid_x, grid_y = zip(*grid.keys())
112+
width = max(grid_x) - min(grid_x) + 1
113+
height = max(grid_y) - min(grid_y) + 1
114+
115+
for angle in rotations:
116+
if angle == 0:
117+
rotated_grid.append(grid)
118+
elif angle == 90:
119+
rotated_grid.append({(height-y, x): grid[(x, y)] for (x, y) in grid})
120+
elif angle == 180:
121+
rotated_grid.append({(width-x, height-y): grid[(x, y)] for (x, y) in grid})
122+
elif angle == 270:
123+
rotated_grid.append({(y, width-x): grid[(x, y)] for (x, y) in grid})
124+
125+
return rotated_grid
126+
127+
def flip (grid, flips = ('V', 'H')):
128+
"""
129+
Flips a grid and returns the result
130+
131+
:param dict grid: The grid to rotate, in format (x, y): value
132+
:param tuple flips: Which flips (horizontal, vertical) to use for flip
133+
:return: The parts in text format
134+
"""
135+
136+
flipped_grid = []
137+
138+
grid_x, grid_y = zip(*grid.keys())
139+
width = max(grid_x) - min(grid_x) + 1
140+
height = max(grid_y) - min(grid_y) + 1
141+
142+
for flip in flips:
143+
if flip == 'H':
144+
flipped_grid.append({(x, height-y): grid[(x, y)] for (x, y) in grid})
145+
elif flip == 'V':
146+
flipped_grid.append({(width-x, y): grid[(x, y)] for (x, y) in grid})
147+
148+
return flipped_grid
149+

0 commit comments

Comments
 (0)