For a fourth year I did some advent of code.
This year’s advent of code
Compared to last year, the problems was OKish but I didn’t really have fun except for a few problems.
Maybe I’m now too used to some types of problems (map exploration, interpreter…) so there is no sense of discovery any more, even when learning a new programming language.
Some exercices like the 8thor the 19th really felt tedious (but maybe it’s because I failed to find a better solution).
I even skipped exercices from the 24th because I didn’t have enough spoon to analyse the code to find the pattern that is probably here (so this day’s code is only generating a somehow optimized Python code).
Python
I wanted to learn some Python because I want to play with Blender’s Python API. As I’m experienced in Java and Ruby the Python syntax is not particulary alien to me, but I wanted some training before using it for a real project, to avoid dealing with too many unknown things at the same time.
As several of my coworkers are Python developers I’ll avoid to post publicly my opinion about Python 😬.
Disclaimers:
-
The bellow code is the first code I’ve written in Python, so it’s probably more an exemple of Python written by a Ruby dev who spent too much type coding in Java than an example of good Python code.
-
Code is provided as is, blablabla.
The code
Day 1
Part one
import sys
last_depth = None
deeper = 0
for line in open(sys.argv[1], 'r'):
current_depth = int(line)
if last_depth is not None:
if current_depth > last_depth:
deeper += 1
last_depth = current_depth
print(deeper)
Part two
import sys
last_depth = None
deeper = 0
lines = list(map(int, open(sys.argv[1]).readlines()))
for line_index in range(2, len(lines)):
current_depth = lines[line_index - 2] + lines[line_index - 1] + lines[line_index]
if last_depth is not None:
if current_depth > last_depth:
deeper += 1
last_depth = current_depth
print(deeper)
Day 2
Part one
import sys
import binascii
horizontal_position = 0
depth = 0
for line in open(sys.argv[1], 'r'):
splitted_line = line.split(' ')
value = int(splitted_line[1])
instruction = splitted_line[0]
match instruction:
case 'forward':
horizontal_position += value
case 'down':
depth += value
case 'up':
depth -= value
print(horizontal_position * depth)
Part two
import sys
horizontal_position = 0
aim = 0
depth = 0
for line in open(sys.argv[1], 'r'):
splitted_line = line.split(' ')
value = int(splitted_line[1])
instruction = splitted_line[0]
match instruction:
case 'forward':
horizontal_position += value
depth += value * aim
case 'down':
aim += value
case 'up':
aim -= value
print(horizontal_position * depth)
Day 3
Part one
import sys
number_of_lines = 0
one_bits_numbers = None
for line in open(sys.argv[1], 'r'):
number_of_lines += 1
if one_bits_numbers is None:
one_bits_numbers = [0] * (len(line) - 1)
for i in range(len(line)):
bit = line[i]
if bit == '1':
one_bits_numbers[i] += 1
gamma_rate = 0
gamma_rate = list(map(lambda b: '1' if (b > number_of_lines / 2) else '0', one_bits_numbers))
epsilon = list(map(lambda b: '1' if (b < number_of_lines / 2) else '0', one_bits_numbers))
print(one_bits_numbers)
gamma_rate_int = int(''.join(gamma_rate), 2)
print(gamma_rate_int)
epsilon_int = int(''.join(epsilon), 2)
print(epsilon_int)
print(gamma_rate_int * epsilon_int)
Part two
import sys
lines = open(sys.argv[1]).readlines()
def finder(current_lines: [str], current_bit_index: int, comparator) -> str:
one_values = sum(map(lambda line: 1 if line[current_bit_index] == '1' else 0, current_lines))
value_to_find = '1' if comparator(one_values, (len(current_lines) / 2)) else '0'
new_lines = list(filter(lambda line: line[current_bit_index] == value_to_find, current_lines))
if len(new_lines) == 1:
return new_lines[0]
else:
return finder(new_lines, current_bit_index + 1, comparator)
oxygen = finder(lines, 0, lambda x, y: x >= y)
print(oxygen)
oxygen_int = int(''.join(oxygen), 2)
co2 = finder(lines, 0, lambda x, y: x < y)
print(co2)
co2_int = int(''.join(co2), 2)
print(oxygen_int * co2_int)
Day 4
Part one
import sys
class Board:
def __init__(self, data: [str]):
self.data = []
self.checked_items = []
for line in data:
parsed_line = []
for index in range(0, 5):
value = line[index * 3:(index * 3 + 2)]
parsed_line.append(int(value))
self.data.append(parsed_line)
self.checked_items.append([False] * 5)
def draw_number(self, value: int) -> bool:
for line_index in range(0, 5):
line = self.data[line_index]
try:
column_index = line.index(value)
self.checked_items[line_index][column_index] = True
return True
except ValueError:
pass
return False
def win(self) -> bool:
for index in range(0, 5):
if self.win_line(index):
return True
if self.win_column(index):
return True
return False
def win_line(self, line_index: int) -> bool:
for i in range(0, 5):
if not self.checked_items[line_index][i]:
return False
return True
def win_column(self, column_index: int) -> bool:
for i in range(0, 5):
if not self.checked_items[i][column_index]:
return False
return True
def score(self) -> int:
result = 0
for index_line in range(0, 5):
for index_column in range(0, 5):
if not self.checked_items[index_line][index_column]:
result += self.data[index_line][index_column]
return result
lines = open(sys.argv[1]).readlines()
number_of_boards = int((len(lines) - 1) / 6)
boards = []
for board_index in range(0, number_of_boards):
boards.append(Board(lines[(2 + (board_index * 6)):(7 + (board_index * 6))]))
for move in map(int, lines[0].split(',')):
for board in boards:
board.draw_number(move)
if board.win():
print(board.score() * move)
exit(0)
Part two
import sys
class Board:
def __init__(self, data: [str]):
self.data = []
self.checked_items = []
for line in data:
parsed_line = []
for index in range(0, 5):
value = line[index * 3:(index * 3 + 2)]
parsed_line.append(int(value))
self.data.append(parsed_line)
self.checked_items.append([False] * 5)
def draw_number(self, value: int) -> bool:
for line_index in range(0, 5):
line = self.data[line_index]
try:
column_index = line.index(value)
self.checked_items[line_index][column_index] = True
return True
except ValueError:
pass
return False
def win(self) -> bool:
for index in range(0, 5):
if self.win_line(index):
return True
if self.win_column(index):
return True
return False
def win_line(self, line_index: int) -> bool:
for i in range(0, 5):
if not self.checked_items[line_index][i]:
return False
return True
def win_column(self, column_index: int) -> bool:
for i in range(0, 5):
if not self.checked_items[i][column_index]:
return False
return True
def score(self) -> int:
result = 0
for index_line in range(0, 5):
for index_column in range(0, 5):
if not self.checked_items[index_line][index_column]:
result += self.data[index_line][index_column]
return result
lines = open(sys.argv[1]).readlines()
number_of_boards = int((len(lines) - 1) / 6)
current_boards = []
for board_index in range(0, number_of_boards):
current_boards.append(Board(lines[(2 + (board_index * 6)):(7 + (board_index * 6))]))
for move in map(int, lines[0].split(',')):
next_boards = []
for current_board in current_boards:
current_board.draw_number(move)
if current_board.win():
if len(current_boards) == 1:
print(current_board.score() * move)
exit(0)
else:
next_boards.append(current_board)
current_boards = next_boards
Day 5
Part one
import sys
import re
lines = open(sys.argv[1]).readlines()
line_regex = re.compile(r"^(?P<x1>\d+),(?P<y1>\d+) -> (?P<x2>\d+),(?P<y2>\d+)$")
checked_values = {}
def process_value(x: int, y: int, checked_values):
key = f"{x}-{y}"
if key in checked_values:
checked_values[key] += 1
else:
checked_values[key] = 1
for line in lines:
match = line_regex.search(line)
x1 = int(match.group('x1'))
y1 = int(match.group('y1'))
x2 = int(match.group('x2'))
y2 = int(match.group('y2'))
if x1 == x2:
r = range(y1, y2 + 1) if y2 > y1 else range(y2, y1 + 1)
for y in r:
process_value(x1, y, checked_values)
elif y1 == y2:
r = range(x1, x2 + 1) if x2 > x1 else range(x2, x1 + 1)
for x in r:
process_value(x, y1, checked_values)
print(len(list(filter(lambda x: x > 1, checked_values.values()))))
Part two
import sys
import re
lines = open(sys.argv[1]).readlines()
line_regex = re.compile(r"^(?P<x1>\d+),(?P<y1>\d+) -> (?P<x2>\d+),(?P<y2>\d+)$")
checked_values = {}
def process_value(x: int, y: int, checked_values):
key = f"{x}-{y}"
if key in checked_values:
checked_values[key] += 1
else:
checked_values[key] = 1
for line in lines:
match = line_regex.search(line)
x1 = int(match.group('x1'))
y1 = int(match.group('y1'))
x2 = int(match.group('x2'))
y2 = int(match.group('y2'))
if x1 == x2:
r = range(y1, y2 + 1) if y2 > y1 else range(y2, y1 + 1)
for y in r:
process_value(x1, y, checked_values)
elif y1 == y2:
r = range(x1, x2 + 1) if x2 > x1 else range(x2, x1 + 1)
for x in r:
process_value(x, y1, checked_values)
else:
r_x = list(range(x1, x2 + 1) if x2 > x1 else range(x1, x2 - 1, -1))
r_y = list(range(y1, y2 + 1) if y2 > y1 else range(y1, y2 - 1, -1))
for index in range(0, len(r_x)):
process_value(r_x[index], r_y[index], checked_values)
print(len(list(filter(lambda x: x > 1, checked_values.values()))))
Day 6
Part one
import sys
line = open(sys.argv[1]).readlines()[0]
current_fishes = [0] * 9
for starting_fish in line.split(','):
starting_fish_int = int(starting_fish)
current_fishes[starting_fish_int] += 1
print(current_fishes)
for day in range(0, 80):
next_fishes = [0] * 9
for index in range(1, 9):
next_fishes[index - 1] = current_fishes[index]
next_fishes[6] += current_fishes[0]
next_fishes[8] = current_fishes[0]
current_fishes = next_fishes
print(f"{day + 1} {current_fishes} {sum(current_fishes)}")
Part two
import sys
line = open(sys.argv[1]).readlines()[0]
current_fishes = [0] * 9
for starting_fish in line.split(','):
starting_fish_int = int(starting_fish)
current_fishes[starting_fish_int] += 1
print(current_fishes)
for day in range(0, 256):
next_fishes = [0] * 9
for index in range(1, 9):
next_fishes[index - 1] = current_fishes[index]
next_fishes[6] += current_fishes[0]
next_fishes[8] = current_fishes[0]
current_fishes = next_fishes
print(f"{day + 1} {current_fishes} {sum(current_fishes)}")
Day 7
Part one
import sys
positions_list = list(map(int, open(sys.argv[1]).readlines()[0].split(',')))
print(positions_list)
min_pos = min(positions_list)
max_pos = max(positions_list)
delta = max_pos - min_pos + 1
initial_positions = [0] * delta
for position in positions_list:
initial_positions[position - min_pos] += 1
def calculate_fuel(positions: [int], target_index: int) -> int:
result = 0
for index in range(0, len(positions)):
result += abs(target_index - index) * positions[index]
return result
print(initial_positions)
print(min(map(lambda i: calculate_fuel(initial_positions, i), range(0, len(initial_positions)))))
Part two
import sys
positions_list = list(map(int, open(sys.argv[1]).readlines()[0].split(',')))
print(positions_list)
min_pos = min(positions_list)
max_pos = max(positions_list)
delta = max_pos - min_pos + 1
initial_positions = [0] * delta
for position in positions_list:
initial_positions[position - min_pos] += 1
def calculate_fuel(positions: [int], target_index: int) -> int:
result = 0
for index in range(0, len(positions)):
distance = abs(target_index - index)
result += int(distance * (distance + 1) * positions[index] / 2)
return result
print(initial_positions)
print(min(map(lambda i: calculate_fuel(initial_positions, i), range(0, len(initial_positions)))))
Day 8
Part one
import sys
interesting_digits = [2, 3, 4, 7]
def process_line(line: str) -> int:
output_values = line.split('|')[1].strip().split(' ')
return len(list(filter(lambda v: len(v) in interesting_digits, output_values)))
print(sum(map(lambda l: process_line(l), open(sys.argv[1]).readlines())))
Part two
import sys
uniques_number_of_segments_to_numbers = {
2: 1,
3: 7,
4: 4,
7: 8
}
def process_line(line: str) -> int:
splitted_lines = line.split('|')
signal_patterns = list(
map(lambda s: ''.join(sorted(s)), splitted_lines[0].strip().split(' ')))
output_values = list(
map(lambda s: ''.join(sorted(s)), splitted_lines[1].strip().split(' ')))
values = signal_patterns + output_values
length_to_values = {}
for i in range(0, 8):
length_to_values[i] = set()
for value in values:
length = len(value)
length_to_values[length].add(value)
for length in length_to_values.keys():
length_to_values[length] = list(length_to_values[length])
known_numbers = {}
# 1, 4, 7, 8: only one per segment length
for number, segments_to_numbers in uniques_number_of_segments_to_numbers.items():
if len(length_to_values[number]) == 1:
known_numbers[segments_to_numbers] = set(length_to_values[number][0])
length_to_values[number].remove(length_to_values[number][0])
# 6: 1 - 6 has one segment, 0 and 9 have two
if 1 in known_numbers:
for candidate in length_to_values[6]:
candidate_set = set(candidate)
if len(list(known_numbers[1] - candidate_set)) == 1:
known_numbers[6] = candidate_set
length_to_values[6].remove(candidate)
# 9 & 0: 9 - (merging 4 and 7) has one segment, 0 has two
if (4 in known_numbers) and (7 in known_numbers):
four_seven = known_numbers[4] | known_numbers[7]
for candidate in length_to_values[6]:
candidate_set = set(candidate)
if len(list(candidate_set - four_seven)) == 1:
known_numbers[9] = candidate_set
length_to_values[6].remove(candidate)
for candidate in length_to_values[6]:
candidate_set = set(candidate)
if len(list(candidate_set - four_seven)) == 2:
known_numbers[0] = candidate_set
length_to_values[6].remove(candidate)
# 3: 1 - 3 has zero segment, 2 and 5 have two
if 1 in known_numbers:
for candidate in length_to_values[5]:
candidate_set = set(candidate)
if len(list(known_numbers[1] - candidate_set)) == 0:
known_numbers[3] = candidate_set
length_to_values[5].remove(candidate)
# 2 & 5: 5 - 6 has zero segment, 2 - 6 has one
if 6 in known_numbers:
for candidate in length_to_values[5]:
candidate_set = set(candidate)
if len(list(candidate_set - known_numbers[6])) == 0:
known_numbers[5] = candidate_set
length_to_values[5].remove(candidate)
for candidate in length_to_values[5]:
candidate_set = set(candidate)
if len(list(candidate_set - known_numbers[6])) == 1:
known_numbers[2] = candidate_set
length_to_values[5].remove(candidate)
known_numbers_reverse = dict((''.join(sorted(v)), k) for k, v in known_numbers.items())
result = known_numbers_reverse[output_values[0]] * 1000 + \
known_numbers_reverse[output_values[1]] * 100 + \
known_numbers_reverse[output_values[2]] * 10 + \
known_numbers_reverse[output_values[3]]
return result
print(sum(map(lambda l: process_line(l), open(sys.argv[1]).readlines())))
Day 9
Part one
import sys
def is_low_point(height_map: [[int]], map_height: int, map_width: int, line_index: int, column_index: int) -> bool:
point_value = height_map[line_index][column_index]
if (line_index > 0) and (height_map[line_index - 1][column_index] <= point_value):
return False
elif (column_index > 0) and (height_map[line_index][column_index - 1] <= point_value):
return False
if (line_index < (map_height - 1)) and (height_map[line_index + 1][column_index] <= point_value):
return False
elif (column_index < (map_width - 1)) and (height_map[line_index][column_index + 1] <= point_value):
return False
else:
return True
height_map = list(map(lambda line: list(map(int, line.strip())), open(sys.argv[1]).readlines()))
map_height = len(height_map)
map_width = len(height_map[0])
result = 0
for line_index in range(0, map_height):
for column_index in range(0, map_width):
if is_low_point(height_map, map_height, map_width, line_index, column_index):
point_value = height_map[line_index][column_index]
result += 1 + point_value
print(result)
Part two
import sys
def is_low_point(height_map: [[int]], map_height: int, map_width: int, line_index: int, column_index: int) -> bool:
point_value = height_map[line_index][column_index]
if (line_index > 0) and (height_map[line_index - 1][column_index] <= point_value):
return False
elif (column_index > 0) and (height_map[line_index][column_index - 1] <= point_value):
return False
if (line_index < (map_height - 1)) and (height_map[line_index + 1][column_index] <= point_value):
return False
elif (column_index < (map_width - 1)) and (height_map[line_index][column_index + 1] <= point_value):
return False
else:
return True
def check_point(height_map, current_point_value, target_point, visited_points, next_points_to_visit) -> None:
target_value = height_map[target_point[0]][target_point[1]]
if (target_point not in visited_points) \
and (target_point not in next_points_to_visit) \
and (target_value < 9) \
and (target_value > current_point_value):
next_points_to_visit.append(target_point)
def calculate_bassin(height_map: [[int]], map_height: int, map_width: int, line_index: int, column_index: int) -> int:
visited_points = []
past_points_to_visit = [(line_index, column_index)]
while len(past_points_to_visit) != 0:
next_points_to_visit = []
for current_point in past_points_to_visit:
current_point_line_index, current_point_column_index = current_point
current_point_value = height_map[current_point_line_index][current_point_column_index]
if current_point_line_index > 0:
target_point = (current_point_line_index - 1, current_point_column_index)
check_point(height_map, current_point_value, target_point, visited_points, next_points_to_visit)
if current_point_column_index > 0:
target_point = (current_point_line_index, current_point_column_index - 1)
check_point(height_map, current_point_value, target_point, visited_points, next_points_to_visit)
if current_point_line_index < (map_height - 1):
target_point = (current_point_line_index + 1, current_point_column_index)
check_point(height_map, current_point_value, target_point, visited_points, next_points_to_visit)
if current_point_column_index < (map_width - 1):
target_point = (current_point_line_index, current_point_column_index + 1)
check_point(height_map, current_point_value, target_point, visited_points, next_points_to_visit)
visited_points.append(current_point)
past_points_to_visit = next_points_to_visit
return len(visited_points)
height_map = list(map(lambda line: list(map(int, line.strip())), open(sys.argv[1]).readlines()))
map_height = len(height_map)
map_width = len(height_map[0])
bassins = []
for line_index in range(0, map_height):
for column_index in range(0, map_width):
if is_low_point(height_map, map_height, map_width, line_index, column_index):
bassins.append(calculate_bassin(height_map, map_height, map_width, line_index, column_index))
bassins.sort()
print(bassins[-1] * bassins[-2] * bassins[-3])
Day 10
Part one
import sys
matching_symbols = {
')': '(',
']': '[',
'}': '{',
'>': '<',
}
opening_symbols = list(matching_symbols.values())
closing_symbols = list(matching_symbols.keys())
def pop_valid(stack, char):
if stack[-1] == char:
stack.pop()
return True
else:
return False
def process_line(line: str):
stack = []
for char in line:
if char in opening_symbols:
stack.append(char)
elif char in closing_symbols:
if not pop_valid(stack, matching_symbols[char]):
return char
elif char == "\n":
return None
else:
raise Exception(char)
return None
symbol_to_value = {
')': 3,
']': 57,
'}': 1197,
'>': 25137,
}
result = 0
for line in open(sys.argv[1]).readlines():
line_result = process_line(line)
if line_result is not None:
result += symbol_to_value[line_result]
print(result)
Part two
import sys
matching_symbols = {
')': '(',
']': '[',
'}': '{',
'>': '<',
}
opening_symbols = list(matching_symbols.values())
closing_symbols = list(matching_symbols.keys())
def pop_valid(stack, char):
if stack[-1] == char:
stack.pop()
return True
else:
return False
def process_line(line: str):
stack = []
for char in line:
if char in opening_symbols:
stack.append(char)
elif char in closing_symbols:
if not pop_valid(stack, matching_symbols[char]):
return None
elif char == "\n":
return stack
else:
raise Exception(char)
return stack
symbol_to_value = {
'(': 1,
'[': 2,
'{': 3,
'<': 4,
}
scores = []
for line in open(sys.argv[1]).readlines():
line_result = process_line(line)
if line_result is not None:
line_total = 0
for char in reversed(line_result):
line_total = (line_total * 5) + symbol_to_value[char]
scores.append(line_total)
scores.sort()
print(scores[int(len(scores) / 2)])
Day 11
Part one
import sys
octopus_map = list(map(lambda line: list(map(int, line.strip())), open(sys.argv[1]).readlines()))
map_height = len(octopus_map)
map_width = len(octopus_map[0])
total_flashes = 0
def process_flashed(octopus_map: [[int]], flashed_line: int, flashed_column: int, new_flashes_to_process) -> None:
current_value = octopus_map[flashed_line][flashed_column]
octopus_map[flashed_line][flashed_column] += 1
if current_value == 9:
new_flashes_to_process.append((flashed_line, flashed_column))
def process_flash(octopus_map: [[int]], flash_line: int, flash_column: int, map_height: int, map_width: int,
new_flashes_to_process) -> None:
north_available = flash_line > 0
south_available = flash_line < (map_height - 1)
west_available = flash_column > 0
east_available = flash_column < (map_width - 1)
if north_available:
process_flashed(octopus_map, flash_line - 1, flash_column, new_flashes_to_process)
if south_available:
process_flashed(octopus_map, flash_line + 1, flash_column, new_flashes_to_process)
if west_available:
process_flashed(octopus_map, flash_line, flash_column - 1, new_flashes_to_process)
if east_available:
process_flashed(octopus_map, flash_line, flash_column + 1, new_flashes_to_process)
if north_available and west_available:
process_flashed(octopus_map, flash_line - 1, flash_column - 1, new_flashes_to_process)
if north_available and east_available:
process_flashed(octopus_map, flash_line - 1, flash_column + 1, new_flashes_to_process)
if south_available and west_available:
process_flashed(octopus_map, flash_line + 1, flash_column - 1, new_flashes_to_process)
if south_available and east_available:
process_flashed(octopus_map, flash_line + 1, flash_column + 1, new_flashes_to_process)
def process_step(octopus_map: [[int]], map_height: int, map_width: int) -> int:
flashes_to_process = []
for line_index in range(0, map_height):
for column_index in range(0, map_width):
octopus_map[line_index][column_index] += 1
if octopus_map[line_index][column_index] == 10:
flashes_to_process.append((line_index, column_index))
while len(flashes_to_process) > 0:
new_flashes_to_process = []
for flash_to_process in flashes_to_process:
process_flash(octopus_map, flash_to_process[0], flash_to_process[1], map_height, map_width,
new_flashes_to_process)
flashes_to_process = new_flashes_to_process
flashes_number = 0
for line_index in range(0, map_height):
for column_index in range(0, map_width):
if octopus_map[line_index][column_index] > 9:
flashes_number += 1
octopus_map[line_index][column_index] = 0
return flashes_number
for step in range(0, 100):
current_flashes = process_step(octopus_map, map_height, map_width)
total_flashes += current_flashes
print(total_flashes)
Part two
import sys
octopus_map = list(map(lambda line: list(map(int, line.strip())), open(sys.argv[1]).readlines()))
map_height = len(octopus_map)
map_width = len(octopus_map[0])
total_flashes = 0
def process_flashed(octopus_map: [[int]], flashed_line: int, flashed_column: int, new_flashes_to_process) -> None:
current_value = octopus_map[flashed_line][flashed_column]
octopus_map[flashed_line][flashed_column] += 1
if current_value == 9:
new_flashes_to_process.append((flashed_line, flashed_column))
def process_flash(octopus_map: [[int]], flash_line: int, flash_column: int, map_height: int, map_width: int,
new_flashes_to_process) -> None:
north_available = flash_line > 0
south_available = flash_line < (map_height - 1)
west_available = flash_column > 0
east_available = flash_column < (map_width - 1)
if north_available:
process_flashed(octopus_map, flash_line - 1, flash_column, new_flashes_to_process)
if south_available:
process_flashed(octopus_map, flash_line + 1, flash_column, new_flashes_to_process)
if west_available:
process_flashed(octopus_map, flash_line, flash_column - 1, new_flashes_to_process)
if east_available:
process_flashed(octopus_map, flash_line, flash_column + 1, new_flashes_to_process)
if north_available and west_available:
process_flashed(octopus_map, flash_line - 1, flash_column - 1, new_flashes_to_process)
if north_available and east_available:
process_flashed(octopus_map, flash_line - 1, flash_column + 1, new_flashes_to_process)
if south_available and west_available:
process_flashed(octopus_map, flash_line + 1, flash_column - 1, new_flashes_to_process)
if south_available and east_available:
process_flashed(octopus_map, flash_line + 1, flash_column + 1, new_flashes_to_process)
def process_step(octopus_map: [[int]], map_height: int, map_width: int) -> int:
flashes_to_process = []
for line_index in range(0, map_height):
for column_index in range(0, map_width):
octopus_map[line_index][column_index] += 1
if octopus_map[line_index][column_index] == 10:
flashes_to_process.append((line_index, column_index))
while len(flashes_to_process) > 0:
new_flashes_to_process = []
for flash_to_process in flashes_to_process:
process_flash(octopus_map, flash_to_process[0], flash_to_process[1], map_height, map_width,
new_flashes_to_process)
flashes_to_process = new_flashes_to_process
flashes_number = 0
for line_index in range(0, map_height):
for column_index in range(0, map_width):
if octopus_map[line_index][column_index] > 9:
flashes_number += 1
octopus_map[line_index][column_index] = 0
return flashes_number == (map_height * map_width)
current_step = 1
while True:
if process_step(octopus_map, map_height, map_width):
print(current_step)
exit(0)
current_step+= 1
Day 12
Part one
import sys
caves_map = {}
for path in open(sys.argv[1]).readlines():
caves = path.strip().split('-')
from_cave = caves[0]
to_cave = caves[1]
if from_cave in caves_map:
caves_map[from_cave].append(to_cave)
else:
caves_map[from_cave] = [to_cave]
if to_cave in caves_map:
caves_map[to_cave].append(from_cave)
else:
caves_map[to_cave] = [from_cave]
pathes_reaching_the_end = 0
pathes_to_visit = [['start']]
while len(pathes_to_visit) != 0:
next_pathes_to_visit = []
for current_path in pathes_to_visit:
for target in caves_map[current_path[-1]]:
if target == 'end':
pathes_reaching_the_end += 1
elif target.islower():
if target not in current_path:
next_pathes_to_visit.append(current_path + [target])
elif target.isupper():
next_pathes_to_visit.append(current_path + [target])
else:
raise Exception(target)
pathes_to_visit = next_pathes_to_visit
print(pathes_reaching_the_end)
Part two
import sys
caves_map = {}
for path in open(sys.argv[1]).readlines():
caves = path.strip().split('-')
from_cave = caves[0]
to_cave = caves[1]
if from_cave in caves_map:
caves_map[from_cave].append(to_cave)
else:
caves_map[from_cave] = [to_cave]
if to_cave in caves_map:
caves_map[to_cave].append(from_cave)
else:
caves_map[to_cave] = [from_cave]
class Path:
def __init__(self, path: [str], small_cave_double_visit: str|None):
self.path = path
self.small_cave_double_visit = small_cave_double_visit
pathes_reaching_the_end = 0
pathes_to_visit = [Path(['start'], None)]
while len(pathes_to_visit) != 0:
next_pathes_to_visit = []
for current_path in pathes_to_visit:
for target in caves_map[current_path.path[-1]]:
if target == 'end':
pathes_reaching_the_end += 1
elif target == 'start':
pass
elif target.islower():
if target not in current_path.path:
next_pathes_to_visit.append(Path(current_path.path + [target], current_path.small_cave_double_visit))
elif current_path.small_cave_double_visit is not None:
pass
elif current_path.path.count(target) == 1:
next_pathes_to_visit.append(Path(current_path.path + [target], target))
elif target.isupper():
next_pathes_to_visit.append(Path(current_path.path + [target], current_path.small_cave_double_visit))
else:
raise Exception(target)
pathes_to_visit = next_pathes_to_visit
print(pathes_reaching_the_end)
Day 13
Part one
import sys
import re
page = open(sys.argv[1]).readlines()
dots = set()
last_dot_line_index = 0
for dot_line in page:
last_dot_line_index += 1
if dot_line == "\n":
break
dot_coordinates = list(map(int, dot_line.split(',')))
dots.add((dot_coordinates[0], dot_coordinates[1]))
fold_regex = re.compile(r"^fold along (?P<axis>[xy])=(?P<value>\d+)$")
def fold_y(fold_value: int, dots):
result = set()
for dot in dots:
dot_column, dot_line = dot
if dot_line < fold_value:
result.add(dot)
elif dot_line > fold_value:
result.add((dot_column, (2 * fold_value) - dot_line))
return result
def fold_x(fold_value: int, dots):
result = set()
for dot in dots:
dot_column, dot_line = dot
if dot_column < fold_value:
result.add(dot)
elif dot_column > fold_value:
result.add(((2 * fold_value) - dot_column, dot_line))
return result
fold_line = page[last_dot_line_index]
match = fold_regex.search(fold_line)
fold_axis = match.group('axis')
fold_value = int(match.group('value'))
match fold_axis:
case 'x':
dots = fold_x(fold_value, dots)
case 'y':
dots = fold_y(fold_value, dots)
case _:
raise Exception(fold_axis)
print(len(list(dots)))
Part two
import sys
import re
page = open(sys.argv[1]).readlines()
dots = set()
last_dot_line_index = 0
for dot_line in page:
last_dot_line_index += 1
if dot_line == "\n":
break
dot_coordinates = list(map(int, dot_line.split(',')))
dots.add((dot_coordinates[0], dot_coordinates[1]))
fold_regex = re.compile(r"^fold along (?P<axis>[xy])=(?P<value>\d+)$")
def fold_y(fold_value: int, dots):
result = set()
for dot in dots:
dot_column, dot_line = dot
if dot_line < fold_value:
result.add(dot)
elif dot_line > fold_value:
result.add((dot_column, (2 * fold_value) - dot_line))
return result
def fold_x(fold_value: int, dots):
result = set()
for dot in dots:
dot_column, dot_line = dot
if dot_column < fold_value:
result.add(dot)
elif dot_column > fold_value:
result.add(((2 * fold_value) - dot_column, dot_line))
return result
for fold_index in range(last_dot_line_index, len(page)):
fold_line = page[fold_index]
match = fold_regex.search(fold_line)
fold_axis = match.group('axis')
fold_value = int(match.group('value'))
match fold_axis:
case 'x':
dots = fold_x(fold_value, dots)
case 'y':
dots = fold_y(fold_value, dots)
case _:
raise Exception(fold_axis)
max_column = max(map(lambda dot: dot[0], dots))
max_line = max(map(lambda dot: dot[1], dots))
for line_index in range(0, max_line + 1):
for column_index in range(0, max_column + 1):
print('#' if (column_index, line_index) in dots else ' ', sep=' ', end='')
print()
Day 14
Part one
import sys
import re
instructions = open(sys.argv[1]).readlines()
current_value = instructions[0].strip()
possible_elements = set(current_value)
rule_regex = re.compile(r"^(?P<from>[A-Z]{2}) -> (?P<to>[A-Z])$")
rules = {}
for rule in instructions[2:(len(instructions))]:
match = rule_regex.search(rule)
from_rule = match.group('from')
to_rule = match.group('to')
rules[from_rule] = to_rule
possible_elements.update(from_rule)
possible_elements.update(to_rule)
print(current_value)
for step in range(0,2):
next_value = []
for char_index in range(0, len(current_value) - 1):
current_pair = ''.join(current_value[char_index:char_index+2])
next_value.append(current_value[char_index])
applied_rule = rules[current_pair]
if applied_rule is not None:
next_value.append(applied_rule)
next_value.append(current_value[-1])
current_value = next_value
print(current_value)
cardinalities = list(map(lambda e: current_value.count(e), possible_elements))
print(max(cardinalities) - min(cardinalities))
Part two
import sys
import re
instructions = open(sys.argv[1]).readlines()
initial_value = instructions[0].strip()
possible_elements = set(initial_value)
rule_regex = re.compile(r"^(?P<from>[A-Z]{2}) -> (?P<to>[A-Z])$")
rules = {}
for rule in instructions[2:(len(instructions))]:
match = rule_regex.search(rule)
from_rule = match.group('from')
to_rule = match.group('to')
rules[from_rule] = to_rule
possible_elements.update(from_rule)
possible_elements.update(to_rule)
def insert_value(value, number, pairs):
if value in pairs:
pairs[value] += number
else:
pairs[value] = number
current_pairs = {}
current_cardinalities = {}
for char_index in range(0, len(initial_value) - 1):
current_pair = initial_value[char_index:char_index + 2]
insert_value(current_pair, 1, current_pairs)
insert_value(initial_value[char_index], 1, current_cardinalities)
insert_value(initial_value[-1], 1, current_cardinalities)
def insert_value(value: str, number: int, pairs: dict) -> None:
if value in pairs:
pairs[value] += number
else:
pairs[value] = number
for step in range(0, 40):
next_pairs = {}
next_cardinalities = current_cardinalities.copy()
for value, number in current_pairs.items():
if value in rules:
rule_target = rules[value]
from_pair = f"{value[0]}{rule_target}"
to_pair = f"{rule_target}{value[1]}"
insert_value(rule_target, number, next_cardinalities)
insert_value(from_pair, number, next_pairs)
insert_value(to_pair, number, next_pairs)
else:
insert_value(value, number, next_pairs)
current_pairs = next_pairs
current_cardinalities = next_cardinalities
print(max(current_cardinalities.values()) - min(current_cardinalities.values()))
Day 15
Part one
import sys
chiton_map = list(map(lambda line: list(map(int, line.strip())), open(sys.argv[1]).readlines()))
map_height = len(chiton_map)
map_width = len(chiton_map[0])
visited_positions = {(0, 0): 0}
current_positions = set()
current_positions.add((0, 0))
minimal_risk = 0
def check_position(current_risk, target_line, target_column, next_positions):
target_position = (target_line, target_column)
target_risk = current_risk + chiton_map[target_line][target_column]
if target_position in visited_positions:
if target_risk < visited_positions[target_position]:
visited_positions[target_position] = target_risk
next_positions.add(target_position)
else:
visited_positions[target_position] = target_risk
next_positions.add(target_position)
while len(current_positions) > 0:
next_positions = set()
for current_position in current_positions:
current_line, current_column = current_position
current_risk = visited_positions[current_position]
if current_line > 0:
check_position(current_risk, current_line - 1, current_column, next_positions)
if current_column > 0:
check_position(current_risk, current_line, current_column - 1, next_positions)
if current_line < (map_height - 1):
check_position(current_risk, current_line + 1, current_column, next_positions)
if current_column < (map_width - 1):
check_position(current_risk, current_line, current_column + 1, next_positions)
current_positions = next_positions
print(visited_positions[(map_height - 1, map_width - 1)])
Part two
import sys
chiton_map = list(map(lambda line: list(map(int, line.strip())), open(sys.argv[1]).readlines()))
initial_map_height = len(chiton_map)
initial_map_width = len(chiton_map[0])
def process_risk(value: int) -> int:
return value if value < 10 else 1
for map_index in range(1, 5):
for current_line in chiton_map:
for tile_index in range(0, initial_map_width):
current_line.append(process_risk((current_line[tile_index + ((map_index - 1) * initial_map_width)] + 1)))
for map_index in range(1, 5):
for current_line_index in range(0, initial_map_height):
chiton_map.append(list(map(lambda c: process_risk(c + 1), chiton_map[current_line_index + ((map_index - 1) * initial_map_height)])))
map_height = initial_map_height * 5
map_width = initial_map_width * 5
visited_positions = {(0, 0): 0}
current_positions = set()
current_positions.add((0, 0))
minimal_risk = 0
def check_position(current_risk, target_line, target_column, next_positions):
target_position = (target_line, target_column)
target_risk = current_risk + chiton_map[target_line][target_column]
if target_position in visited_positions:
if target_risk < visited_positions[target_position]:
visited_positions[target_position] = target_risk
next_positions.add(target_position)
else:
visited_positions[target_position] = target_risk
next_positions.add(target_position)
while len(current_positions) > 0:
next_positions = set()
for current_position in current_positions:
current_line, current_column = current_position
current_risk = visited_positions[current_position]
if current_line > 0:
check_position(current_risk, current_line - 1, current_column, next_positions)
if current_column > 0:
check_position(current_risk, current_line, current_column - 1, next_positions)
if current_line < (map_height - 1):
check_position(current_risk, current_line + 1, current_column, next_positions)
if current_column < (map_width - 1):
check_position(current_risk, current_line, current_column + 1, next_positions)
current_positions = next_positions
print(visited_positions[(map_height - 1, map_width - 1)])
Day 16
Part one
import sys
hex_sequence = open(sys.argv[1]).readlines()[0].strip()
sequence = bin(int(hex_sequence, 16))[2:].zfill(len(hex_sequence) * 4)
class Packet:
PACKET_VERSION_SIZE = 3
PACKET_TYPE_ID_SIZE = 3
PACKET_HEADER_SIZE = PACKET_VERSION_SIZE + PACKET_TYPE_ID_SIZE
PACKET_TYPE_LITERAL_VALUE = 4
def __init__(self, starting_position: int, version: int):
print(f"Starting to read a {self.__class__.__name__}")
self.starting_position = starting_position
self.version = version
@staticmethod
def read_binary(from_index: int, size: int) -> int:
read_content = sequence[from_index:(from_index + size)]
print(f"Reading {sequence[0:from_index]}[{read_content}]{sequence[from_index + size:]}")
return int(read_content, 2)
@staticmethod
def parse_packet(starting_position: int):
version = Packet.read_binary(starting_position, Packet.PACKET_VERSION_SIZE)
type_id = Packet.read_binary(starting_position + Packet.PACKET_VERSION_SIZE, Packet.PACKET_TYPE_ID_SIZE)
print(f"Starting to read a packet at {starting_position} with version {version} and type {type_id}")
match type_id:
case Packet.PACKET_TYPE_LITERAL_VALUE:
return LiteralValuePacket(starting_position, version)
case _:
return OperatorPacket(starting_position, version)
class LiteralValuePacket(Packet):
def __init__(self, starting_position: int, version: int):
Packet.__init__(self, starting_position, version)
self.last_position = starting_position + Packet.PACKET_HEADER_SIZE
value_bits = []
while sequence[self.last_position] == '1':
value_bits.extend(sequence[(self.last_position + 1):(self.last_position + 5)])
self.last_position += 5
value_bits.extend(sequence[(self.last_position + 1):(self.last_position + 5)])
self.value = int(''.join(value_bits), 2)
self.last_position += 5
print(f"\tValue is {self.value}")
print(f"Ending reading LiteralValuePacket, last position is {self.last_position}")
def total(self) -> int:
return self.version
class OperatorPacket(Packet):
SUB_PACKETS_LENGTH_TYPE_SIZE = 1
SUB_PACKETS_LENGTH_SIZE = 15
SUB_PACKETS_NUMBER_SIZE = 11
def __init__(self, starting_position: int, version: int):
Packet.__init__(self, starting_position, version)
length_type_id = Packet.read_binary(
starting_position + Packet.PACKET_HEADER_SIZE,
OperatorPacket.SUB_PACKETS_LENGTH_TYPE_SIZE)
print(f"\tLength type is {length_type_id}")
self.sub_packets = []
match length_type_id:
case 0:
sub_packets_size = Packet.read_binary(
starting_position + Packet.PACKET_HEADER_SIZE + OperatorPacket.SUB_PACKETS_LENGTH_TYPE_SIZE,
OperatorPacket.SUB_PACKETS_LENGTH_SIZE)
print(f"Sub-packets size is {sub_packets_size}")
current_sub_packet_position = starting_position + Packet.PACKET_HEADER_SIZE + OperatorPacket.SUB_PACKETS_LENGTH_TYPE_SIZE + OperatorPacket.SUB_PACKETS_LENGTH_SIZE
self.last_position = current_sub_packet_position + sub_packets_size
while current_sub_packet_position < self.last_position:
sub_packet = Packet.parse_packet(current_sub_packet_position)
self.sub_packets.append(sub_packet)
current_sub_packet_position = sub_packet.last_position
case 1:
sub_packets_number = Packet.read_binary(
starting_position + Packet.PACKET_HEADER_SIZE + OperatorPacket.SUB_PACKETS_LENGTH_TYPE_SIZE,
OperatorPacket.SUB_PACKETS_NUMBER_SIZE)
print(f"Sub-packets number is {sub_packets_number}")
current_sub_packet_position = starting_position + Packet.PACKET_HEADER_SIZE + OperatorPacket.SUB_PACKETS_LENGTH_TYPE_SIZE + OperatorPacket.SUB_PACKETS_NUMBER_SIZE
for packet_index in range(0, sub_packets_number):
sub_packet = Packet.parse_packet(current_sub_packet_position)
self.sub_packets.append(sub_packet)
current_sub_packet_position = sub_packet.last_position
self.last_position = self.sub_packets[-1].last_position
case _:
raise Exception(length_type_id)
print(f"Ending reading OperatorPacket, last position is {self.last_position}")
def total(self) -> int:
return self.version + sum(map(lambda p: p.total(), self.sub_packets))
main_packet = Packet.parse_packet(0)
print(main_packet.total())
Part two
import sys
import functools
hex_sequence = sys.argv[1].strip()
sequence = bin(int(hex_sequence, 16))[2:].zfill(len(hex_sequence) * 4)
class Packet:
PACKET_VERSION_SIZE = 3
PACKET_TYPE_ID_SIZE = 3
PACKET_HEADER_SIZE = PACKET_VERSION_SIZE + PACKET_TYPE_ID_SIZE
PACKET_TYPES = {}
def __init__(self, starting_position: int, version: int):
self.starting_position = starting_position
self.version = version
@staticmethod
def read_binary(from_index: int, size: int) -> int:
read_content = sequence[from_index:(from_index + size)]
return int(read_content, 2)
@staticmethod
def parse_packet(starting_position: int):
version = Packet.read_binary(starting_position, Packet.PACKET_VERSION_SIZE)
type_id = Packet.read_binary(starting_position + Packet.PACKET_VERSION_SIZE, Packet.PACKET_TYPE_ID_SIZE)
if type_id in Packet.PACKET_TYPES:
return Packet.PACKET_TYPES[type_id](starting_position, version)
else:
raise Exception(type_id)
class LiteralValuePacket(Packet):
def __init__(self, starting_position: int, version: int):
Packet.__init__(self, starting_position, version)
self.last_position = starting_position + Packet.PACKET_HEADER_SIZE
value_bits = []
while sequence[self.last_position] == '1':
value_bits.extend(sequence[(self.last_position + 1):(self.last_position + 5)])
self.last_position += 5
value_bits.extend(sequence[(self.last_position + 1):(self.last_position + 5)])
self.value = int(''.join(value_bits), 2)
self.last_position += 5
print(self.total())
def total(self) -> int:
return self.value
class OperatorPacket(Packet):
SUB_PACKETS_LENGTH_TYPE_SIZE = 1
SUB_PACKETS_LENGTH_SIZE = 15
SUB_PACKETS_NUMBER_SIZE = 11
def __init__(self, starting_position: int, version: int):
Packet.__init__(self, starting_position, version)
length_type_id = Packet.read_binary(
starting_position + Packet.PACKET_HEADER_SIZE,
OperatorPacket.SUB_PACKETS_LENGTH_TYPE_SIZE)
self.sub_packets = []
match length_type_id:
case 0:
sub_packets_size = Packet.read_binary(
starting_position + Packet.PACKET_HEADER_SIZE + OperatorPacket.SUB_PACKETS_LENGTH_TYPE_SIZE,
OperatorPacket.SUB_PACKETS_LENGTH_SIZE)
current_sub_packet_position = starting_position + Packet.PACKET_HEADER_SIZE + OperatorPacket.SUB_PACKETS_LENGTH_TYPE_SIZE + OperatorPacket.SUB_PACKETS_LENGTH_SIZE
self.last_position = current_sub_packet_position + sub_packets_size
while current_sub_packet_position < self.last_position:
sub_packet = Packet.parse_packet(current_sub_packet_position)
self.sub_packets.append(sub_packet)
current_sub_packet_position = sub_packet.last_position
case 1:
sub_packets_number = Packet.read_binary(
starting_position + Packet.PACKET_HEADER_SIZE + OperatorPacket.SUB_PACKETS_LENGTH_TYPE_SIZE,
OperatorPacket.SUB_PACKETS_NUMBER_SIZE)
current_sub_packet_position = starting_position + Packet.PACKET_HEADER_SIZE + OperatorPacket.SUB_PACKETS_LENGTH_TYPE_SIZE + OperatorPacket.SUB_PACKETS_NUMBER_SIZE
for packet_index in range(0, sub_packets_number):
sub_packet = Packet.parse_packet(current_sub_packet_position)
self.sub_packets.append(sub_packet)
current_sub_packet_position = sub_packet.last_position
self.last_position = self.sub_packets[-1].last_position
case _:
raise Exception(length_type_id)
def total(self) -> int:
return self.version + sum(map(lambda p: p.total(), self.sub_packets))
def sub_packets_values(self) -> map:
return map(lambda p: p.total(), self.sub_packets)
class SumPacket(OperatorPacket):
def total(self) -> int:
return sum(self.sub_packets_values())
class ProductPacket(OperatorPacket):
def total(self) -> int:
return functools.reduce(lambda a, b: a * b, self.sub_packets_values())
class MinimumPacket(OperatorPacket):
def total(self) -> int:
return min(self.sub_packets_values())
class MaximumPacket(OperatorPacket):
def total(self) -> int:
return max(self.sub_packets_values())
class TestPacket(OperatorPacket):
def sub_packets_values(self):
if len(self.sub_packets) != 2:
raise Exception(self.sub_packets)
return self.sub_packets[0].total(), self.sub_packets[1].total()
class GreaterThanPacket(TestPacket):
def total(self) -> int:
sub_packet_1_value, sub_packet_2_value = self.sub_packets_values()
return 1 if sub_packet_1_value > sub_packet_2_value else 0
class LessThanPacket(TestPacket):
def total(self) -> int:
sub_packet_1_value, sub_packet_2_value = self.sub_packets_values()
return 1 if sub_packet_1_value < sub_packet_2_value else 0
class EqualToPacket(TestPacket):
def total(self) -> int:
sub_packet_1_value, sub_packet_2_value = self.sub_packets_values()
return 1 if sub_packet_1_value == sub_packet_2_value else 0
Packet.PACKET_TYPES[0] = SumPacket
Packet.PACKET_TYPES[1] = ProductPacket
Packet.PACKET_TYPES[2] = MinimumPacket
Packet.PACKET_TYPES[3] = MaximumPacket
Packet.PACKET_TYPES[4] = LiteralValuePacket
Packet.PACKET_TYPES[5] = GreaterThanPacket
Packet.PACKET_TYPES[6] = LessThanPacket
Packet.PACKET_TYPES[7] = EqualToPacket
main_packet = Packet.parse_packet(0)
print(main_packet.total())
Day 17
Part one
import re
import sys
from enum import Enum, auto
input = open(sys.argv[1]).readlines()[0].strip()
line_regex = re.compile(r"^target area: x=(?P<x_min>-?\d+)..(?P<x_max>-?\d+), y=(?P<y_min>-?\d+)..(?P<y_max>-?\d+)$")
match = line_regex.search(input)
x_min = int(match.group('x_min'))
x_max = int(match.group('x_max'))
y_min = int(match.group('y_min'))
y_max = int(match.group('y_max'))
class Status(Enum):
IN_FLY = auto()
IN_TARGET_AREA = auto()
LOST = auto()
def calculate_trajectory(launch_x: int, launch_y: int):
current_x_velocity = launch_x
current_y_velocity = launch_y
current_x_position = 0
current_y_position = 0
max_y = 0
while True:
status = check_position(current_x_position, current_y_position)
if status != Status.IN_FLY:
return status, max_y
current_x_position += current_x_velocity
current_y_position += current_y_velocity
if current_x_velocity > 0:
current_x_velocity -= 1
elif current_x_velocity < 0:
current_x_velocity += 1
current_y_velocity -= 1
max_y = max(max_y, current_y_position)
def check_position(x: int, y: int) -> Status:
if y < y_min:
return Status.LOST
elif y > y_max:
return Status.IN_FLY
elif x_min <= x <= x_max:
return Status.IN_TARGET_AREA
else:
return Status.IN_FLY
def calculate_trajectories(x: int):
print(f"calculate_trajectories {x}")
max_y = 0
for y in range(-1000, 1000):
current_status, current_max_y = calculate_trajectory(x, y)
if current_status == Status.IN_TARGET_AREA:
max_y = max(max_y, current_max_y)
return max_y
max_y = 0
for x in range(1, x_max + 1):
max_y = max(max_y, calculate_trajectories(x))
print(max_y)
Part two
import re
import sys
from enum import Enum, auto
input = open(sys.argv[1]).readlines()[0].strip()
line_regex = re.compile(r"^target area: x=(?P<x_min>-?\d+)..(?P<x_max>-?\d+), y=(?P<y_min>-?\d+)..(?P<y_max>-?\d+)$")
match = line_regex.search(input)
x_min = int(match.group('x_min'))
x_max = int(match.group('x_max'))
y_min = int(match.group('y_min'))
y_max = int(match.group('y_max'))
class Status(Enum):
IN_FLY = auto()
IN_TARGET_AREA = auto()
LOST = auto()
def calculate_trajectory(launch_x: int, launch_y: int):
current_x_velocity = launch_x
current_y_velocity = launch_y
current_x_position = 0
current_y_position = 0
while True:
status = check_position(current_x_position, current_y_position)
if status != Status.IN_FLY:
return status
current_x_position += current_x_velocity
current_y_position += current_y_velocity
if current_x_velocity > 0:
current_x_velocity -= 1
elif current_x_velocity < 0:
current_x_velocity += 1
current_y_velocity -= 1
def check_position(x: int, y: int) -> Status:
if y < y_min:
return Status.LOST
elif y > y_max:
return Status.IN_FLY
elif x_min <= x <= x_max:
return Status.IN_TARGET_AREA
else:
return Status.IN_FLY
total = 0
for x in range(1, x_max + 1):
for y in range(-1000, 1000):
current_status = calculate_trajectory(x, y)
if current_status == Status.IN_TARGET_AREA:
total += 1
print(total)
Day 18
Part one
import math
import sys
import json
from enum import Enum, auto
lines = list(map(lambda l: json.loads(l), open(sys.argv[1]).readlines()))
class ExplosionStatus(Enum):
NO = auto()
JUST_EXPLODED = auto()
EXPLODED = auto()
def explode(values: [], level: int):
if isinstance(values, int):
return False, None, None
elif (level > 4) \
and isinstance(values, list) \
and isinstance(values[0], int) \
and isinstance(values[1], int):
return ExplosionStatus.JUST_EXPLODED, values[0], values[1]
else:
for value_index in range(0, len(values)):
value = values[value_index]
status, left_value, right_value = explode(value, level + 1)
if (status == ExplosionStatus.JUST_EXPLODED) or (status == ExplosionStatus.EXPLODED):
if status == ExplosionStatus.JUST_EXPLODED:
values[value_index] = 0
if (left_value is not None) and (value_index > 0):
if isinstance(values[value_index - 1], int):
values[value_index - 1] += left_value
else:
left_values = values[value_index - 1]
while not isinstance(left_values[- 1], int):
left_values = left_values[- 1]
left_values[-1] += left_value
left_value = None
if (right_value is not None) and (value_index < (len(values) - 1)):
if isinstance(values[value_index + 1], int):
values[value_index + 1] += right_value
else:
right_values = values[value_index + 1]
while not isinstance(right_values[0], int):
right_values = right_values[0]
right_values[0] += right_value
right_value = None
return ExplosionStatus.EXPLODED, left_value, right_value
return ExplosionStatus.NO, None, None
def split(values: []) -> bool:
for value_index in range(0, len(values)):
value = values[value_index]
if isinstance(value, int):
if value >= 10:
values[value_index] = [math.floor(value / 2), math.ceil(value / 2)]
return True
elif split(value):
return True
return False
def process(values: []):
while True:
status, _, _ = explode(values, 1)
if status != ExplosionStatus.NO:
return True
else:
return split(values)
def magniture(values: []):
left_value = values[0]
if isinstance(left_value, list):
left_value = magniture(left_value)
right_value = values[1]
if isinstance(right_value, list):
right_value = magniture(right_value)
return (left_value * 3) + (2 * right_value)
current_values = lines[0]
while process(current_values):
pass
for line_index in range(1, len(lines)):
current_values = [current_values, lines[line_index]]
while process(current_values):
pass
print(magniture(current_values))
Part two
import math
import sys
import json
import copy
from enum import Enum, auto
lines = list(map(lambda l: json.loads(l), open(sys.argv[1]).readlines()))
class ExplosionStatus(Enum):
NO = auto()
JUST_EXPLODED = auto()
EXPLODED = auto()
def explode(values: [], level: int):
if isinstance(values, int):
return False, None, None
elif (level > 4) \
and isinstance(values, list) \
and isinstance(values[0], int) \
and isinstance(values[1], int):
return ExplosionStatus.JUST_EXPLODED, values[0], values[1]
else:
for value_index in range(0, len(values)):
value = values[value_index]
status, left_value, right_value = explode(value, level + 1)
if (status == ExplosionStatus.JUST_EXPLODED) or (status == ExplosionStatus.EXPLODED):
if status == ExplosionStatus.JUST_EXPLODED:
values[value_index] = 0
if (left_value is not None) and (value_index > 0):
if isinstance(values[value_index - 1], int):
values[value_index - 1] += left_value
else:
left_values = values[value_index - 1]
while not isinstance(left_values[- 1], int):
left_values = left_values[- 1]
left_values[-1] += left_value
left_value = None
if (right_value is not None) and (value_index < (len(values) - 1)):
if isinstance(values[value_index + 1], int):
values[value_index + 1] += right_value
else:
right_values = values[value_index + 1]
while not isinstance(right_values[0], int):
right_values = right_values[0]
right_values[0] += right_value
right_value = None
return ExplosionStatus.EXPLODED, left_value, right_value
return ExplosionStatus.NO, None, None
def split(values: []) -> bool:
for value_index in range(0, len(values)):
value = values[value_index]
if isinstance(value, int):
if value >= 10:
values[value_index] = [math.floor(value / 2), math.ceil(value / 2)]
return True
elif split(value):
return True
return False
def process(values: []):
while True:
status, _, _ = explode(values, 1)
if status != ExplosionStatus.NO:
return True
else:
return split(values)
def magniture(values: []):
left_value = values[0]
if isinstance(left_value, list):
left_value = magniture(left_value)
right_value = values[1]
if isinstance(right_value, list):
right_value = magniture(right_value)
return (left_value * 3) + (2 * right_value)
max_magniture = 0
for line_index_1 in range(0, len(lines) - 1):
for line_index_2 in range(line_index_1 + 1, len(lines)):
current_values = [copy.deepcopy(lines[line_index_1]), copy.deepcopy(lines[line_index_2])]
while process(current_values):
pass
current_magniture = magniture(current_values)
current_values = [copy.deepcopy(lines[line_index_2]), copy.deepcopy(lines[line_index_1])]
while process(current_values):
pass
current_magniture = magniture(current_values)
max_magniture = max(max_magniture, current_magniture)
print(max_magniture)
Day 19
Part one
import re
import sys
class Scanner:
def __init__(self, index):
self.index = index
self.beacons = []
self.distances = []
def compute_distances(self):
permutations = self.beacons_permutations()
for permutation_index in range(0, len(permutations)):
permutation_value = permutations[permutation_index]
for index_1 in range(0, len(self.beacons)):
starting_beacon = permutation_value[index_1]
calculated_values = set()
for index_2 in range(0, len(self.beacons)):
if index_1 != index_2:
target_beacon = permutation_value[index_2]
calculated_values.add(
(
target_beacon[0] - starting_beacon[0],
target_beacon[1] - starting_beacon[1],
target_beacon[2] - starting_beacon[2]
)
)
self.distances.append((starting_beacon, permutation_index, calculated_values))
def __repr__(self) -> str:
return f"{self.index} {self.beacons}"
def beacons_permutations(self) -> [[tuple]]:
if self.index == 0:
self.beacons.sort()
return [self.beacons]
result = []
for i in range(0, 24):
result.append([])
for beacon in self.beacons:
x, y, z = beacon
values = [
(x, y, z),
(z, y, -x),
(-x, y, -z),
(-z, y, x),
(-x, -y, z),
(-z, -y, -x),
(x, -y, -z),
(z, -y, x),
(x, -z, y),
(y, -z, -x),
(-x, -z, -y),
(-y, -z, x),
(x, z, -y),
(-y, z, -x),
(-x, z, y),
(y, z, x),
(z, x, y),
(y, x, -z),
(-z, x, -y),
(-y, x, z),
(-z, -x, y),
(y, -x, z),
(z, -x, -y),
(-y, -x, -z)
]
for i in range(0, 24):
result[i].append(values[i])
for i in range(0, 24):
result[i].sort()
return result
scanners = []
scanner_regex = re.compile(r"^--- scanner (?P<scanner_id>\d+) ---$")
beacon_regex = re.compile(r"^(?P<x>-?\d+),(?P<y>-?\d+),(?P<z>-?\d+)$")
for line in open(sys.argv[1]).readlines():
line = line.strip()
scanner_match = scanner_regex.search(line)
if scanner_match:
scanners.append(Scanner(int(scanner_match.group('scanner_id'))))
else:
beacon_match = beacon_regex.search(line)
if beacon_match:
scanners[-1].beacons.append(
(int(beacon_match.group('x')), int(beacon_match.group('y')), int(beacon_match.group('z')))
)
for scanner in scanners:
scanner.compute_distances()
def check_pair(scanner_0, target_permutation_index_0, scanner_1):
for distance_0 in scanner_0.distances:
starting_beacon_0, permutation_index_0, calculated_values_0 = distance_0
if target_permutation_index_0 == permutation_index_0:
for distance_1 in scanner_1.distances:
starting_beacon_1, permutation_index_1, calculated_values_1 = distance_1
if len(calculated_values_0.intersection(calculated_values_1)) >= 11:
return distance_0, distance_1
return None
def search_match(scanners, found_scanners, beacons):
for found_scanner in found_scanners:
for searched_scanner_index in range(0, len(scanners)):
searched_scanner = scanners[searched_scanner_index]
check_pair_result = check_pair(found_scanner[1], found_scanner[2], searched_scanner)
if check_pair_result is not None:
distance_0, distance_1 = check_pair_result
origin = (
distance_0[0][0] - distance_1[0][0] + found_scanner[0][0],
distance_0[0][1] - distance_1[0][1] + found_scanner[0][1],
distance_0[0][2] - distance_1[0][2] + found_scanner[0][2],
)
beacons.add((distance_1[0][0] + origin[0],distance_1[0][1] + origin[1], distance_1[0][2] + origin[2]))
for beacon in distance_1[2]:
beacons.add((beacon[0] + distance_1[0][0] + origin[0], beacon[1] + distance_1[0][1] + origin[1], beacon[2] + distance_1[0][2] + origin[2]))
found_scanners.append(
(
origin,
searched_scanner,
distance_1[1])
)
scanners.pop(searched_scanner_index)
return True
return False
scanner_0 = scanners.pop(0)
found_scanners = [((0, 0, 0), scanner_0, 0)]
beacons = set(scanner_0.beacons)
while search_match(scanners, found_scanners, beacons):
pass
print(len(beacons))
Part two
import re
import sys
class Scanner:
def __init__(self, index):
self.index = index
self.beacons = []
self.distances = []
def compute_distances(self):
permutations = self.beacons_permutations()
for permutation_index in range(0, len(permutations)):
permutation_value = permutations[permutation_index]
for index_1 in range(0, len(self.beacons)):
starting_beacon = permutation_value[index_1]
calculated_values = set()
for index_2 in range(0, len(self.beacons)):
if index_1 != index_2:
target_beacon = permutation_value[index_2]
calculated_values.add(
(
target_beacon[0] - starting_beacon[0],
target_beacon[1] - starting_beacon[1],
target_beacon[2] - starting_beacon[2]
)
)
self.distances.append((starting_beacon, permutation_index, calculated_values))
def __repr__(self) -> str:
return f"{self.index} {self.beacons}"
def beacons_permutations(self) -> [[tuple]]:
if self.index == 0:
self.beacons.sort()
return [self.beacons]
result = []
for i in range(0, 24):
result.append([])
for beacon in self.beacons:
x, y, z = beacon
values = [
(x, y, z),
(z, y, -x),
(-x, y, -z),
(-z, y, x),
(-x, -y, z),
(-z, -y, -x),
(x, -y, -z),
(z, -y, x),
(x, -z, y),
(y, -z, -x),
(-x, -z, -y),
(-y, -z, x),
(x, z, -y),
(-y, z, -x),
(-x, z, y),
(y, z, x),
(z, x, y),
(y, x, -z),
(-z, x, -y),
(-y, x, z),
(-z, -x, y),
(y, -x, z),
(z, -x, -y),
(-y, -x, -z)
]
for i in range(0, 24):
result[i].append(values[i])
for i in range(0, 24):
result[i].sort()
return result
scanners = []
scanner_regex = re.compile(r"^--- scanner (?P<scanner_id>\d+) ---$")
beacon_regex = re.compile(r"^(?P<x>-?\d+),(?P<y>-?\d+),(?P<z>-?\d+)$")
for line in open(sys.argv[1]).readlines():
line = line.strip()
scanner_match = scanner_regex.search(line)
if scanner_match:
scanners.append(Scanner(int(scanner_match.group('scanner_id'))))
else:
beacon_match = beacon_regex.search(line)
if beacon_match:
scanners[-1].beacons.append(
(int(beacon_match.group('x')), int(beacon_match.group('y')), int(beacon_match.group('z')))
)
for scanner in scanners:
scanner.compute_distances()
def check_pair(scanner_0, target_permutation_index_0, scanner_1):
for distance_0 in scanner_0.distances:
starting_beacon_0, permutation_index_0, calculated_values_0 = distance_0
if target_permutation_index_0 == permutation_index_0:
for distance_1 in scanner_1.distances:
starting_beacon_1, permutation_index_1, calculated_values_1 = distance_1
if len(calculated_values_0.intersection(calculated_values_1)) >= 11:
return distance_0, distance_1
return None
def search_match(scanners, found_scanners):
for found_scanner in found_scanners:
for searched_scanner_index in range(0, len(scanners)):
searched_scanner = scanners[searched_scanner_index]
check_pair_result = check_pair(found_scanner[1], found_scanner[2], searched_scanner)
if check_pair_result is not None:
distance_0, distance_1 = check_pair_result
origin = (
distance_0[0][0] - distance_1[0][0] + found_scanner[0][0],
distance_0[0][1] - distance_1[0][1] + found_scanner[0][1],
distance_0[0][2] - distance_1[0][2] + found_scanner[0][2],
)
found_scanners.append(
(
origin,
searched_scanner,
distance_1[1])
)
scanners.pop(searched_scanner_index)
return True
return False
scanner_0 = scanners.pop(0)
found_scanners = [((0, 0, 0), scanner_0, 0)]
while search_match(scanners, found_scanners):
pass
max_manhattan = 0
for index_0 in range(0, len(found_scanners)):
scanner_distance_0 = found_scanners[index_0][0]
for index_1 in range(0, len(found_scanners)):
if index_0 != index_1:
scanner_distance_1 = found_scanners[index_1][0]
max_manhattan = max(max_manhattan,
abs(scanner_distance_0[0] - scanner_distance_1[0]) + abs(scanner_distance_0[1] - scanner_distance_1[1]) + abs(scanner_distance_0[2] - scanner_distance_1[2])
)
print(max_manhattan)
Day 20
Part one
import sys
input = open(sys.argv[1]).readlines()
algorithm = list(map(lambda c: False if (c == '#') else True, input[0].strip()))
data = dict()
initial_points = set()
initial_columns = len(input[2].strip())
initial_lines = len(input) - 2
for c in range(0, initial_columns):
for l in range(0, initial_lines):
data[(0, l, c)] = True if (input[2 + l][c] == '#') else False
def calculate_point(generation: int, line: int, column: int):
if not (generation, line, column) in data:
if generation == 0:
return False
else:
output_pixel_value = 0
for line_index in [line - 1, line, line + 1]:
for column_index in [column - 1, column, column + 1]:
output_pixel_value *= 2
if calculate_point(generation - 1, line_index, column_index):
output_pixel_value += 1
result = False if algorithm[output_pixel_value] else True
data[(generation, line, column)] = result
return result
else:
return data[(generation, line, column)]
generations = 2
result = 0
for l in range (-generations, initial_lines + generations + 1):
for c in range (-generations, initial_columns + generations + 1):
if calculate_point(generations, l, c):
result += 1
print(result)
Part two
import sys
input = open(sys.argv[1]).readlines()
algorithm = list(map(lambda c: False if (c == '#') else True, input[0].strip()))
data = dict()
initial_points = set()
initial_columns = len(input[2].strip())
initial_lines = len(input) - 2
for c in range(0, initial_columns):
for l in range(0, initial_lines):
data[(0, l, c)] = True if (input[2 + l][c] == '#') else False
def calculate_point(generation: int, line: int, column: int):
if not (generation, line, column) in data:
if generation == 0:
return False
else:
output_pixel_value = 0
for line_index in [line - 1, line, line + 1]:
for column_index in [column - 1, column, column + 1]:
output_pixel_value *= 2
if calculate_point(generation - 1, line_index, column_index):
output_pixel_value += 1
result = False if algorithm[output_pixel_value] else True
data[(generation, line, column)] = result
return result
else:
return data[(generation, line, column)]
generations = 50
result = 0
for l in range (-generations, initial_lines + generations + 1):
for c in range (-generations, initial_columns + generations + 1):
if calculate_point(generations, l, c):
result += 1
print(result)
Day 21
Part one
import sys
input = list(map(lambda l: int(l.strip().split(' ')[-1]), open(sys.argv[1]).readlines()))
player_positions = [input[0], input[1]]
player_scores = [0, 0]
def next_move(player_index: int, move_size: int):
player_positions[player_index] += move_size
while player_positions[player_index] > 10:
player_positions[player_index] -= 10
player_scores[player_index] += player_positions[player_index]
dice_number = 0
while True:
move_size = ((dice_number + 1) * 3) + 3
next_move(0, move_size)
dice_number += 3
if player_scores[0] >= 1000:
print(player_scores[1] * dice_number)
exit(0)
move_size = ((dice_number + 1) * 3) + 3
next_move(1, move_size)
dice_number += 3
if player_scores[1] >= 1000:
print(player_scores[0] * dice_number)
exit(0)
print(f"{player_positions} {player_scores} {dice_number}")
Part two
import functools
import sys
input = list(map(lambda l: int(l.strip().split(' ')[-1]), open(sys.argv[1]).readlines()))
starting_positions = [input[0], input[1]]
DICES_THROWS = [3, 4, 4, 4, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 7, 8, 8, 8, 9]
@functools.cache
def solve_situation(player_score_1, player_score_2, player_position_1, player_position_2):
player_wins_1 = 0
player_wins_2 = 0
for player_move_1 in DICES_THROWS:
new_player_position_1 = player_position_1 + player_move_1
while new_player_position_1 > 10:
new_player_position_1 -= 10
new_player_score_1 = player_score_1 + new_player_position_1
if new_player_score_1 >= 21:
player_wins_1 += 1
else:
solved_situation = solve_situation(
player_score_2,
new_player_score_1,
player_position_2,
new_player_position_1,
)
player_wins_1 += solved_situation[1]
player_wins_2 += solved_situation[0]
return player_wins_1, player_wins_2
print(max(solve_situation(0, 0, starting_positions[0], starting_positions[1])))
Day 22
Part one
import re
import sys
grid = set()
instruction_regex = re.compile(
r"^(?P<status>on|off) x=(?P<x_min>-?\d+)..(?P<x_max>-?\d+),y=(?P<y_min>-?\d+)..(?P<y_max>-?\d+),z=(?P<z_min>-?\d+)..(?P<z_max>-?\d+)$")
for instruction in open(sys.argv[1]).readlines():
parsed_instruction = instruction_regex.search(instruction)
status = parsed_instruction.group('status')
x_min = int(parsed_instruction.group('x_min'))
x_max = int(parsed_instruction.group('x_max'))
y_min = int(parsed_instruction.group('y_min'))
y_max = int(parsed_instruction.group('y_max'))
z_min = int(parsed_instruction.group('z_min'))
z_max = int(parsed_instruction.group('z_max'))
for x in range(-50, 51):
if x_min <= x <= x_max:
for y in range(-50, 51):
if y_min <= y <= y_max:
for z in range(-50, 51):
if z_min <= z <= z_max:
if status == 'on':
grid.add((x, y, z))
else:
if (x, y, z) in grid:
grid.remove((x, y, z))
print(len(grid))
Part two
import re
import sys
grid = set()
instruction_regex = re.compile(
r"^(?P<status>on|off) x=(?P<x_min>-?\d+)..(?P<x_max>-?\d+),y=(?P<y_min>-?\d+)..(?P<y_max>-?\d+),z=(?P<z_min>-?\d+)..(?P<z_max>-?\d+)$")
class Cuboid:
def __init__(self, x_min: int, x_max: int, y_min: int, y_max: int, z_min: int, z_max: int, status: bool):
self.x_min = x_min
self.x_max = x_max
self.y_min = y_min
self.y_max = y_max
self.z_min = z_min
self.z_max = z_max
self.status = status
def intersect(self, other) -> bool:
if not (self.x_min <= other.x_max and self.x_max >= other.x_min):
return False
if not (self.y_min <= other.y_max and self.y_max >= other.y_min):
return False
if not (self.z_min <= other.z_max and self.z_max >= other.z_min):
return False
return True
def intersection(self, other):
return Cuboid(
max(self.x_min, other.x_min),
min(self.x_max, other.x_max),
max(self.y_min, other.y_min),
min(self.y_max, other.y_max),
max(self.z_min, other.z_min),
min(self.z_max, other.z_max),
not other.status)
def volume(self) -> int:
return (self.x_max - self.x_min + 1) * (self.y_max - self.y_min + 1) * (self.z_max - self.z_min + 1)
known_cuboids = []
for instruction in open(sys.argv[1]).readlines():
parsed_instruction = instruction_regex.search(instruction)
status = parsed_instruction.group('status')
x_min = int(parsed_instruction.group('x_min'))
x_max = int(parsed_instruction.group('x_max'))
y_min = int(parsed_instruction.group('y_min'))
y_max = int(parsed_instruction.group('y_max'))
z_min = int(parsed_instruction.group('z_min'))
z_max = int(parsed_instruction.group('z_max'))
current_cuboid = Cuboid(x_min, x_max, y_min, y_max, z_min, z_max, status == 'on')
current_intersections = []
for cuboid in known_cuboids:
if current_cuboid.intersect(cuboid):
intersection = current_cuboid.intersection(cuboid)
current_intersections.append(intersection)
known_cuboids += current_intersections
if status == 'on':
known_cuboids.append(current_cuboid)
result = 0
for current_cuboid in known_cuboids:
current_volume = current_cuboid.volume()
result += current_volume * (1 if current_cuboid.status else -1)
print(result)
Day 23
Part one
import sys
from enum import Enum, auto
from typing import NewType, Tuple
# #############
# #01.2.3.4.56#
# ###7#9#1#3###
# #8#0#2#4#
# #########
ItemType = NewType('ItemType', int)
ITEM_AMBER = ItemType(0)
ITEM_BRONZE = ItemType(1)
ITEM_COPPER = ItemType(2)
ITEM_DESERT = ItemType(3)
ITEM_EMPTY = ItemType(4)
Position = NewType('Position', int)
Move = Tuple[
ItemType, ItemType, ItemType, ItemType, ItemType, ItemType, ItemType, ItemType, ItemType, ItemType, ItemType, ItemType, ItemType, ItemType]
def slice_move(current_move: Move, min_position: Position, max_position: Position) -> Move:
current_move = list(current_move)
return tuple(
current_move[:min_position] +
current_move[max_position:(max_position + 1)] +
current_move[(min_position + 1):max_position] +
current_move[min_position:(min_position + 1)] +
current_move[max_position + 1:]
)
ITEM_TYPE_TO_ENERGY: dict[ItemType, int] = {
ITEM_AMBER: 1,
ITEM_BRONZE: 10,
ITEM_COPPER: 100,
ITEM_DESERT: 1000
}
CHAR_TO_ITEM_TYPE: dict[str, ItemType] = {
'A': ITEM_AMBER,
'B': ITEM_BRONZE,
'C': ITEM_COPPER,
'D': ITEM_DESERT,
'.': ITEM_EMPTY,
}
puzzle = open(sys.argv[1]).readlines()
initial_position: Move = tuple(
list(
map(
lambda c: CHAR_TO_ITEM_TYPE[c],
[
puzzle[1][1],
puzzle[1][2],
puzzle[1][4],
puzzle[1][6],
puzzle[1][8],
puzzle[1][10],
puzzle[1][11],
puzzle[2][3],
puzzle[3][3],
puzzle[2][5],
puzzle[3][5],
puzzle[2][7],
puzzle[3][7],
puzzle[2][9],
puzzle[3][9]]
)
)
)
TARGET_POSITION: Move = tuple(
([ITEM_EMPTY] * 7) + ([ITEM_AMBER] * 2) + ([ITEM_BRONZE] * 2) + ([ITEM_COPPER] * 2) + ([ITEM_DESERT] * 2))
print(initial_position)
known_moves: dict[Move, int] = dict()
known_moves[initial_position] = 0
min_energy = 0
max_energy = 0
min_solution_energy: int | None = None
moves_to_explores: dict[int, list[Move]] = dict()
moves_to_explores[0] = [initial_position]
class CanMoveInStatus(Enum):
TOP_ROOM = auto()
BOTTOM_ROOM = auto()
NO = auto()
TOP_ROOM_INDEX: dict[ItemType, Position] = {
ITEM_AMBER: Position(7), ITEM_BRONZE: Position(9), ITEM_COPPER: Position(11), ITEM_DESERT: Position(13)
}
BOTTOM_ROOM_INDEX: dict[ItemType, Position] = {
ITEM_AMBER: Position(8), ITEM_BRONZE: Position(10), ITEM_COPPER: Position(12), ITEM_DESERT: Position(14)
}
MOVE_RIGHT_TO_CHAMBER_MAX_POSITION: dict[ItemType, Position] = {
ITEM_AMBER: Position(1),
ITEM_BRONZE: Position(2),
ITEM_COPPER: Position(3),
ITEM_DESERT: Position(4)
}
MOVE_LEFT_TO_CHAMBER_MIN_POSITION: dict[ItemType, Position] = {
ITEM_AMBER: Position(2),
ITEM_BRONZE: Position(3),
ITEM_COPPER: Position(4),
ITEM_DESERT: Position(5)
}
def can_move_in(current_move: Move, item_type: ItemType) -> CanMoveInStatus:
top_content = current_move[TOP_ROOM_INDEX[item_type]]
bottom_content = current_move[BOTTOM_ROOM_INDEX[item_type]]
if top_content != ITEM_EMPTY:
return CanMoveInStatus.NO
elif bottom_content == ITEM_EMPTY:
return CanMoveInStatus.BOTTOM_ROOM
elif bottom_content == item_type:
return CanMoveInStatus.TOP_ROOM
else:
return CanMoveInStatus.NO
NUMBER_OF_MOVE_TO_TOP_ROOM: dict[ItemType, list[Position]] = {
ITEM_AMBER: [Position(3), Position(2), Position(2), Position(4), Position(6), Position(8), Position(9)],
ITEM_BRONZE: [Position(5), Position(4), Position(2), Position(2), Position(4), Position(6), Position(7)],
ITEM_COPPER: [Position(7), Position(6), Position(4), Position(2), Position(2), Position(4), Position(5)],
ITEM_DESERT: [Position(9), Position(9), Position(6), Position(4), Position(2), Position(2), Position(3)],
}
def move_right_to_chamber(current_move: Move, item_type: ItemType) -> Position | None:
for item_index in range(MOVE_RIGHT_TO_CHAMBER_MAX_POSITION[item_type], -1, -1):
if current_move[item_index] == item_type:
return Position(item_index)
elif current_move[item_index] != ITEM_EMPTY:
return None
return None
def move_left_to_chamber(current_move: Move, item_type: ItemType) -> Position | None:
for item_index in range(MOVE_LEFT_TO_CHAMBER_MIN_POSITION[item_type], 7):
if current_move[item_index] == item_type:
return Position(item_index)
elif current_move[item_index] != ITEM_EMPTY:
return None
return None
def process_move(move: Move, energy: int) -> None:
global max_energy
global known_moves
global moves_to_explores
if (move not in known_moves) or (known_moves[move] > energy):
print(f"\t\t\t\t\tNew move {move} for {energy}")
known_moves[move] = energy
if energy in moves_to_explores:
moves_to_explores[energy].append(move)
else:
moves_to_explores[energy] = [move]
max_energy = max(max_energy, energy)
def try_to_move_in(current_move: Move, current_energy: int, item_type: ItemType):
global min_solution_energy
global max_energy
move_in_status = can_move_in(current_move, item_type)
if move_in_status != CanMoveInStatus.NO:
move_right_index = move_right_to_chamber(current_move, item_type)
if move_right_index is not None:
number_of_moves = NUMBER_OF_MOVE_TO_TOP_ROOM[item_type][move_right_index]
target_index = TOP_ROOM_INDEX[item_type] if (move_in_status == CanMoveInStatus.TOP_ROOM) else \
BOTTOM_ROOM_INDEX[item_type]
new_move = slice_move(current_move, move_right_index, target_index)
if move_in_status == CanMoveInStatus.BOTTOM_ROOM:
number_of_moves += 1
new_energy = current_energy + (number_of_moves * ITEM_TYPE_TO_ENERGY[item_type])
if new_move == TARGET_POSITION:
if (min_solution_energy is None) or (min_solution_energy > new_energy):
min_solution_energy = new_energy
max_energy = new_energy
print(f"Found better solution for {new_energy}")
else:
process_move(new_move, new_energy)
move_left_index = move_left_to_chamber(current_move, item_type)
if move_left_index is not None:
number_of_moves = NUMBER_OF_MOVE_TO_TOP_ROOM[item_type][move_left_index]
target_index = TOP_ROOM_INDEX[item_type] if (move_in_status == CanMoveInStatus.TOP_ROOM) else \
BOTTOM_ROOM_INDEX[item_type]
new_move = slice_move(current_move, move_left_index, target_index)
if move_in_status == CanMoveInStatus.BOTTOM_ROOM:
number_of_moves += 1
new_energy = current_energy + (number_of_moves * ITEM_TYPE_TO_ENERGY[item_type])
if new_move == TARGET_POSITION:
if (min_solution_energy is None) or (min_solution_energy > new_energy):
min_solution_energy = new_energy
max_energy = new_energy
print(f"Found better solution for {new_energy}")
else:
process_move(new_move, new_energy)
def try_to_move_out(current_move: Move, current_energy: int, item_type: ItemType):
top_room_index = TOP_ROOM_INDEX[item_type]
top_room_item_type = current_move[top_room_index]
bottom_room_index = BOTTOM_ROOM_INDEX[item_type]
bottom_room_item_type = current_move[bottom_room_index]
if ((top_room_item_type != ITEM_EMPTY) and (top_room_item_type != item_type)) or \
((top_room_item_type == item_type) and (bottom_room_item_type != item_type)):
print(f"\t\tCan move from top room")
target_index: Position
for target_index in range(MOVE_RIGHT_TO_CHAMBER_MAX_POSITION[item_type], -1, -1):
if current_move[target_index] != ITEM_EMPTY:
break
print(f"\t\t\t\tCan move left to {target_index}")
number_of_moves = NUMBER_OF_MOVE_TO_TOP_ROOM[item_type][target_index]
new_energy = current_energy + (number_of_moves * ITEM_TYPE_TO_ENERGY[top_room_item_type])
new_move = slice_move(current_move, target_index, top_room_index)
process_move(new_move, new_energy)
for target_index in range(MOVE_LEFT_TO_CHAMBER_MIN_POSITION[item_type], 7):
if current_move[target_index] != ITEM_EMPTY:
break
print(f"\t\t\t\tCan move right to {target_index}")
number_of_moves = NUMBER_OF_MOVE_TO_TOP_ROOM[item_type][target_index]
new_energy = current_energy + (number_of_moves * ITEM_TYPE_TO_ENERGY[top_room_item_type])
new_move = slice_move(current_move, target_index, top_room_index)
process_move(new_move, new_energy)
else:
if (top_room_item_type == ITEM_EMPTY) and (bottom_room_item_type != ITEM_EMPTY) and (
bottom_room_item_type != item_type):
print(f"\t\tCan move from bottom room")
target_index: Position
for target_index in range(MOVE_RIGHT_TO_CHAMBER_MAX_POSITION[item_type], -1, -1):
if current_move[target_index] != ITEM_EMPTY:
break
print(f"\t\t\t\tCan move left to {target_index}")
number_of_moves = NUMBER_OF_MOVE_TO_TOP_ROOM[item_type][target_index] + 1
new_energy = current_energy + (number_of_moves * ITEM_TYPE_TO_ENERGY[bottom_room_item_type])
new_move = slice_move(current_move, target_index, bottom_room_index)
process_move(new_move, new_energy)
for target_index in range(MOVE_LEFT_TO_CHAMBER_MIN_POSITION[item_type], 7):
if current_move[target_index] != ITEM_EMPTY:
break
print(f"\t\t\t\tCan move right to {target_index}")
number_of_moves = NUMBER_OF_MOVE_TO_TOP_ROOM[item_type][target_index] + 1
new_energy = current_energy + (number_of_moves * ITEM_TYPE_TO_ENERGY[bottom_room_item_type])
new_move = slice_move(current_move, target_index, bottom_room_index)
process_move(new_move, new_energy)
def next_move() -> None:
global min_energy
global max_energy
global moves_to_explores
global min_solution_energy
for current_energy in range(min_energy, max_energy + 1):
#print(f"Trying {current_energy}")
min_energy = current_energy
if (min_solution_energy is not None) and (current_energy >= min_solution_energy):
print(f"Solution found: {min_solution_energy}")
exit(0)
if current_energy in moves_to_explores:
moves_with_right_cost = moves_to_explores[current_energy]
for current_move in moves_with_right_cost:
print(f"Process {current_move} for {current_energy}")
for item_type in [ITEM_AMBER, ITEM_BRONZE, ITEM_COPPER, ITEM_DESERT]:
print(f"\tTrying to move in {item_type}")
try_to_move_in(current_move, current_energy, item_type)
for item_type in [ITEM_AMBER, ITEM_BRONZE, ITEM_COPPER, ITEM_DESERT]:
print(f"\tTrying to move out from {item_type}")
try_to_move_out(current_move, current_energy, item_type)
moves_to_explores.pop(current_energy)
while True:
next_move()
Part two
import sys
from heapq import heappush, heappop
from typing import NewType, List, Any
puzzle = open(sys.argv[1]).readlines()
CHAMBER_SIZE = len(puzzle) - 3
# #############
# #01.2.3.4.56#
# ###7#9#1#3###
# #8#0#2#4#
# #########
ItemType = NewType('ItemType', int)
ITEM_AMBER = ItemType(0)
ITEM_BRONZE = ItemType(1)
ITEM_COPPER = ItemType(2)
ITEM_DESERT = ItemType(3)
ITEM_EMPTY = ItemType(4)
Position = NewType('Position', int)
Move: list[ItemType] = []
def move_to_key(move: Move) -> str:
return ''.join(map(str, move))
def slice_move(current_move: Move, min_position: Position, max_position: Position) -> Move:
return current_move[:min_position] + \
current_move[max_position:(max_position + 1)] + \
current_move[(min_position + 1):max_position] + \
current_move[min_position:(min_position + 1)] + \
current_move[max_position + 1:]
ITEM_TYPE_TO_ENERGY: dict[ItemType, int] = {
ITEM_AMBER: 1,
ITEM_BRONZE: 10,
ITEM_COPPER: 100,
ITEM_DESERT: 1000
}
CHAR_TO_ITEM_TYPE: dict[str, ItemType] = {
'A': ITEM_AMBER,
'B': ITEM_BRONZE,
'C': ITEM_COPPER,
'D': ITEM_DESERT,
'.': ITEM_EMPTY,
}
initial_position_string = [
puzzle[1][1],
puzzle[1][2],
puzzle[1][4],
puzzle[1][6],
puzzle[1][8],
puzzle[1][10],
puzzle[1][11],
]
for t in range(0, 4):
for c in range(0, CHAMBER_SIZE):
initial_position_string.append(puzzle[2 + c][3 + (t * 2)])
initial_position: Move = list(
map(
lambda c: CHAR_TO_ITEM_TYPE[c],
initial_position_string
)
)
TARGET_POSITION: Move = (
[ITEM_EMPTY] * 7) + \
([ITEM_AMBER] * CHAMBER_SIZE) + \
([ITEM_BRONZE] * CHAMBER_SIZE) + \
([ITEM_COPPER] * CHAMBER_SIZE) + \
([ITEM_DESERT] * CHAMBER_SIZE)
print(initial_position)
known_moves: dict[Move, int] = dict()
known_moves[move_to_key(initial_position)] = 0
min_solution_energy: int | None = None
moves_to_explores: list[tuple[int, Move]] = []
heappush(moves_to_explores, (0, initial_position))
TOP_ROOM_INDEX: dict[ItemType, Position] = {
ITEM_AMBER: Position(7),
ITEM_BRONZE: Position(7 + CHAMBER_SIZE),
ITEM_COPPER: Position(7 + (2 * CHAMBER_SIZE)),
ITEM_DESERT: Position(7 + (3 * CHAMBER_SIZE))
}
MOVE_RIGHT_TO_CHAMBER_MAX_POSITION: dict[ItemType, Position] = {
ITEM_AMBER: Position(1),
ITEM_BRONZE: Position(2),
ITEM_COPPER: Position(3),
ITEM_DESERT: Position(4)
}
MOVE_LEFT_TO_CHAMBER_MIN_POSITION: dict[ItemType, Position] = {
ITEM_AMBER: Position(2),
ITEM_BRONZE: Position(3),
ITEM_COPPER: Position(4),
ITEM_DESERT: Position(5)
}
def can_move_in_chamber(current_move: Move, item_type: ItemType) -> Position | None:
for room_index in range(CHAMBER_SIZE - 1, -1, -1):
room_content = current_move[TOP_ROOM_INDEX[item_type] + room_index]
if room_content == ITEM_EMPTY:
return Position(room_index)
elif room_content != item_type:
return None
return None
NUMBER_OF_MOVE_TO_TOP_ROOM: dict[ItemType, list[Position]] = {
ITEM_AMBER: [Position(3), Position(2), Position(2), Position(4), Position(6), Position(8), Position(9)],
ITEM_BRONZE: [Position(5), Position(4), Position(2), Position(2), Position(4), Position(6), Position(7)],
ITEM_COPPER: [Position(7), Position(6), Position(4), Position(2), Position(2), Position(4), Position(5)],
ITEM_DESERT: [Position(9), Position(8), Position(6), Position(4), Position(2), Position(2), Position(3)],
}
def move_right_to_chamber(current_move: Move, item_type: ItemType) -> Position | None:
for item_index in range(MOVE_RIGHT_TO_CHAMBER_MAX_POSITION[item_type], -1, -1):
if current_move[item_index] == item_type:
return Position(item_index)
elif current_move[item_index] != ITEM_EMPTY:
return None
return None
def move_left_to_chamber(current_move: Move, item_type: ItemType) -> Position | None:
for item_index in range(MOVE_LEFT_TO_CHAMBER_MIN_POSITION[item_type], 7):
if current_move[item_index] == item_type:
return Position(item_index)
elif current_move[item_index] != ITEM_EMPTY:
return None
return None
def process_move(move: Move, energy: int) -> None:
global max_energy
global known_moves
global moves_to_explores
move_key = move_to_key(move)
if (move_key not in known_moves) or (known_moves[move_key] > energy):
print(f"\t\t\t\t\tNew move {move} for {energy}")
known_moves[move_key] = energy
heappush(moves_to_explores, (energy, move))
def try_to_move_in(current_move: Move, current_energy: int, item_type: ItemType):
global min_solution_energy
global max_energy
move_in_index = can_move_in_chamber(current_move, item_type)
if move_in_index is not None:
move_right_index = move_right_to_chamber(current_move, item_type)
if move_right_index is not None:
number_of_moves = NUMBER_OF_MOVE_TO_TOP_ROOM[item_type][move_right_index] + move_in_index
target_index = TOP_ROOM_INDEX[item_type] + move_in_index
new_move = slice_move(current_move, move_right_index, target_index)
new_energy = current_energy + (number_of_moves * ITEM_TYPE_TO_ENERGY[item_type])
if new_move == TARGET_POSITION:
if (min_solution_energy is None) or (min_solution_energy > new_energy):
min_solution_energy = new_energy
max_energy = new_energy
print(f"Found better solution for {new_energy}")
else:
process_move(new_move, new_energy)
move_left_index = move_left_to_chamber(current_move, item_type)
if move_left_index is not None:
number_of_moves = NUMBER_OF_MOVE_TO_TOP_ROOM[item_type][move_left_index] + move_in_index
target_index = TOP_ROOM_INDEX[item_type] + move_in_index
new_move = slice_move(current_move, move_left_index, target_index)
new_energy = current_energy + (number_of_moves * ITEM_TYPE_TO_ENERGY[item_type])
if new_move == TARGET_POSITION:
if (min_solution_energy is None) or (min_solution_energy > new_energy):
min_solution_energy = new_energy
max_energy = new_energy
print(f"Found better solution for {new_energy}")
else:
process_move(new_move, new_energy)
def try_to_move_out(current_move: Move, current_energy: int, item_type: ItemType):
top_room_index = TOP_ROOM_INDEX[item_type]
lower_item_want_to_get_out = False
for room_index in range(CHAMBER_SIZE - 1, -1, -1):
room_content = current_move[top_room_index + room_index]
if (room_content == item_type) and (not lower_item_want_to_get_out):
pass
elif room_content == ITEM_EMPTY:
return
else:
lower_item_want_to_get_out = True
can_move_out = True
for over_room_index in range(0, room_index):
if current_move[top_room_index + over_room_index] != ITEM_EMPTY:
can_move_out = False
if can_move_out:
print(f"\t\tCan move from room index {room_index}")
target_index: Position
for target_index in range(MOVE_RIGHT_TO_CHAMBER_MAX_POSITION[item_type], -1, -1):
if current_move[target_index] != ITEM_EMPTY:
break
print(f"\t\t\t\tCan move left to {target_index}")
number_of_moves = NUMBER_OF_MOVE_TO_TOP_ROOM[item_type][target_index] + room_index
new_energy = current_energy + (number_of_moves * ITEM_TYPE_TO_ENERGY[room_content])
new_move = slice_move(current_move, target_index, top_room_index + room_index)
process_move(new_move, new_energy)
for target_index in range(MOVE_LEFT_TO_CHAMBER_MIN_POSITION[item_type], 7):
if current_move[target_index] != ITEM_EMPTY:
break
print(f"\t\t\t\tCan move right to {target_index}")
number_of_moves = NUMBER_OF_MOVE_TO_TOP_ROOM[item_type][target_index] + room_index
new_energy = current_energy + (number_of_moves * ITEM_TYPE_TO_ENERGY[room_content])
new_move = slice_move(current_move, target_index, top_room_index + room_index)
process_move(new_move, new_energy)
def next_move() -> None:
global moves_to_explores
global min_solution_energy
while len(moves_to_explores) > 0:
current_energy, current_move = heappop(moves_to_explores)
if (min_solution_energy is not None) and (current_energy >= min_solution_energy):
print(f"Solution found: {min_solution_energy}")
exit(0)
print(f"Process {current_move} for {current_energy}")
for item_type in [ITEM_AMBER, ITEM_BRONZE, ITEM_COPPER, ITEM_DESERT]:
print(f"\tTrying to move in chamber {item_type}")
try_to_move_in(current_move, current_energy, item_type)
print(f"\tTrying to move out from chamber {item_type}")
try_to_move_out(current_move, current_energy, item_type)
while True:
next_move()
# 43815 too high
# 43814 ?!
Day 24
Part one
import re
puzzle = open('input.txt').readlines()
input_index = 0
constants_values = {
'w': 0,
'x': 0,
'y': 0,
'z': 0,
}
def has_constant(variable):
return (variable in constants_values) and (constants_values[variable] is not None)
def flush_constant(f, variable):
if has_constant(variable):
f.write(f"{variable} = {constants_values[variable]} # Flush\n")
constants_values[variable] = None
line_index = 1
def write(f, content):
f.write(f"{content}\n")
with open('input.txt.py', 'w') as f:
f.write('import sys\n')
f.write('import math\n')
f.write('\n')
f.write('input_number = sys.argv[1]\n')
f.write('\n')
for line in puzzle:
f.write(f'# {line_index}: {line.strip()} {constants_values}\n')
line = line.strip().split(' ')
if len(line) == 0:
continue
match line[0]:
case 'inp':
constants_values[line[1]] = f'int(input_number[{input_index}])'
input_index += 1
case 'add':
if line[2] == '0':
pass
elif has_constant(line[2]):
if has_constant(line[1]):
if constants_values[line[1]] == 0:
constants_values[line[1]] = constants_values[line[2]]
else:
constants_values[line[1]] = f"({constants_values[line[1]]} + {constants_values[line[2]]})"
else:
write(f, f"{line[1]} = {constants_values[line[1]]} + {constants_values[line[2]]}")
constants_values[line[1]] = None
elif has_constant(line[1]) and line[2].isnumeric():
if constants_values[line[1]] == 0:
if line[2].isnumeric():
constants_values[line[1]] = int(line[2])
else:
constants_values[line[1]] = line[2]
else:
constants_values[line[1]] = f"({constants_values[line[1]]} + {line[2]})"
elif constants_values[line[1]] == 0:
constants_values[line[1]] = None
flush_constant(f, line[2])
write(f, f"{line[1]} = {line[2]}")
constants_values[line[1]] = None
else:
flush_constant(f, line[1])
flush_constant(f, line[2])
write(f, f"{line[1]} += {line[2]}")
constants_values[line[1]] = None
case 'mul':
if has_constant(line[1]) and (constants_values[line[1]] == 0):
pass
elif has_constant(line[2]) and (constants_values[line[2]] == 1):
pass
elif line[2] == '0':
constants_values[line[1]] = 0
elif has_constant(line[1]) and isinstance(constants_values[line[1]], int) and has_constant(line[2]) and isinstance(constants_values[line[2]], int):
constants_values[line[1]] = constants_values[line[1]] * constants_values[line[2]]
elif has_constant(line[1]):
flush_constant(f, line[2])
write(f, f"{line[1]} = {constants_values[line[1]]} * {line[2]}")
constants_values[line[1]] = None
else:
flush_constant(f, line[1])
flush_constant(f, line[2])
write(f, f"{line[1]} *= {line[2]}")
case 'div':
if line[2] != '1':
flush_constant(f, line[1])
flush_constant(f, line[2])
if line[2] != '1':
write(f, f"{line[1]} = math.ceil({line[1]} / {line[2]})")
case 'mod':
if line[2] == '1':
pass
elif has_constant(line[1]):
if isinstance(constants_values[line[1]], int) and line[2].isnumeric():
constants_values[line[1]] = int(constants_values[line[1]]) % int(line[2])
else:
write(f, f"{line[1]} = {constants_values[line[1]]} % {line[2]}")
constants_values[line[1]] = None
else:
flush_constant(f, line[1])
flush_constant(f, line[2])
write(f, f"{line[1]} = {line[1]} % {line[2]}")
case 'eql':
if has_constant(line[1]):
if has_constant(line[2]):
if (re.compile(r"^int\(input_number\[(\d+)\]\)$").match(constants_values[line[2]]) is not None) \
and isinstance(constants_values[line[1]], int) \
and (constants_values[line[1]] > 10):
constants_values[line[1]] = 0
else:
write(f,
f"{line[1]} = 1 if ({constants_values[line[1]]} == {constants_values[line[2]]}) else 0")
constants_values[line[1]] = None
else:
if line[2].isnumeric() and (constants_values[line[1]] == int(line[2])):
constants_values[line[1]] = 1
else:
write(f, f"{line[1]} = 1 if ({constants_values[line[1]]} == {line[2]}) else 0")
constants_values[line[1]] = None
else:
if has_constant(line[2]):
write(f, f"{line[1]} = 1 if ({line[1]} == {constants_values[line[2]]}) else 0")
else:
write(f, f"{line[1]} = 1 if ({line[1]} == {line[2]}) else 0")
constants_values[line[1]] = None
case _:
raise Exception(line)
line_index += 1
f.write('\n')
f.write('print(z)\n')
Day 25
Part one
import copy
import sys
puzzle = list(map(lambda l: list(l.strip()), open(sys.argv[1]).readlines()))
puzzle_width = len(puzzle[0])
puzzle_height = len(puzzle)
step_index = 0
while True:
step_index += 1
number_of_moves = 0
new_puzzle = copy.deepcopy(puzzle)
for line in range(0, puzzle_height):
for column in range(0, puzzle_width - 1):
current_cell_content = puzzle[line][column]
target_cell_content = puzzle[line][column + 1]
if (current_cell_content == '>') and (target_cell_content == '.'):
new_puzzle[line][column] = '.'
new_puzzle[line][column + 1] = '>'
number_of_moves += 1
current_cell_content = puzzle[line][puzzle_width - 1]
target_cell_content = puzzle[line][0]
if (current_cell_content == '>') and (target_cell_content == '.'):
new_puzzle[line][puzzle_width - 1] = '.'
new_puzzle[line][0] = '>'
number_of_moves += 1
puzzle = new_puzzle
new_puzzle = copy.deepcopy(puzzle)
for line in range(0, puzzle_height - 1):
for column in range(0, puzzle_width):
current_cell_content = puzzle[line][column]
target_cell_content = puzzle[line + 1][column]
if (current_cell_content == 'v') and (target_cell_content == '.'):
new_puzzle[line][column] = '.'
new_puzzle[line + 1][column] = 'v'
number_of_moves += 1
for column in range(0, puzzle_width):
current_cell_content = puzzle[puzzle_height - 1][column]
target_cell_content = puzzle[0][column]
if (current_cell_content == 'v') and (target_cell_content == '.'):
new_puzzle[puzzle_height - 1][column] = '.'
new_puzzle[0][column] = 'v'
number_of_moves += 1
if number_of_moves == 0:
print(step_index)
exit(0)
puzzle = new_puzzle