├── 2022 ├── .DS_Store ├── day01 │ ├── day1_pt1.py │ └── day1_pt2.py ├── day02 │ ├── day2_pt1.py │ └── day2_pt2.py ├── day03 │ ├── day3_pt1.py │ └── day3_pt2.py ├── day04 │ ├── day4_pt1.py │ └── day4_pt2.py ├── day05 │ ├── day5_pt1.py │ └── day5_pt2.py ├── day06 │ ├── day6_pt1.py │ └── day6_pt2.py ├── day07 │ ├── day7_pt1.py │ └── day7_pt2.py ├── day08 │ ├── day8_pt1.py │ └── day8_pt2.py ├── day09 │ ├── day9_pt1.py │ └── day9_pt2.py ├── day10 │ ├── day10_pt1.py │ └── day10_pt2.py ├── day11 │ ├── day11_pt1.py │ └── day11_pt2.py ├── day12 │ ├── day12_pt1.py │ └── day12_pt2.py └── day13 │ └── day13_pt1.py ├── 2023 ├── day01 │ ├── day1_pt1.py │ └── day1_pt2.py ├── day02 │ ├── day2_pt1.py │ └── day2_pt2.py ├── day03 │ ├── day3_pt1.py │ └── day3_pt2.py ├── day04 │ ├── day4_pt1.py │ └── day4_pt2.py ├── day05 │ ├── day5_pt1.py │ └── day5_pt2.py ├── day06 │ ├── day6_pt1.py │ └── day6_pt2.py ├── day07 │ ├── day7_pt1.py │ └── day7_pt2.py ├── day08 │ ├── day8_pt1.py │ └── day8_pt2.py ├── day09 │ ├── day9_pt1.py │ └── day9_pt2.py ├── day10 │ ├── day10_pt1.py │ └── day10_pt2.py ├── day11 │ └── day11_pt1_2.py ├── day12 │ └── day12_pt1.py ├── day13 │ ├── day13_pt1.py │ └── day13_pt2.py ├── day14 │ ├── day14_pt1.py │ └── day14_pt2.py ├── day15 │ ├── day15_pt1.py │ └── day15_pt2.py ├── day16 │ ├── day16_pt1.py │ └── day16_pt2.py ├── day17 │ └── day17_pt1.py ├── day18 │ ├── day18_pt1.py │ └── day18_pt2.py ├── day20 │ └── day20_pt1.py └── day21 │ ├── day21_pt1.py │ └── day21_pt2.py ├── 2024 ├── day01_pt1 │ └── main.go ├── day01_pt2 │ └── main.go ├── day01_python │ ├── day01_pt1.py │ └── day01_pt2.py ├── day02_pt1 │ └── main.go ├── day02_pt2 │ └── main.go ├── day03_pt1 │ └── main.go ├── day03_pt2 │ └── main.go ├── day04_pt1 │ └── main.go ├── day04_pt2 │ └── main.go ├── day05_pt1 │ └── main.go ├── day05_pt2 │ └── main.go ├── day06_pt1 │ └── main.go ├── day06_pt2 │ └── main.go ├── day07_pt1 │ └── main.go ├── day07_pt2 │ └── main.go ├── day08_pt1 │ └── main.go ├── day08_pt2 │ └── main.go ├── day09_pt1 │ └── main.go ├── day09_pt2 │ └── main.go ├── day10_pt1 │ └── main.go ├── day10_pt2 │ └── main.go ├── day11_pt1_pt2 │ └── main.go ├── day12_pt1 │ └── main.go ├── day12_pt2 │ └── main.go ├── day13_pt1 │ └── main.go ├── day13_pt2 │ └── main.go ├── day14_pt1 │ └── main.go ├── day14_pt2 │ └── main.go ├── day15_pt1 │ └── main.go ├── day15_pt2 │ └── main.go ├── day16_pt1 │ └── main.go ├── day16_pt2 │ └── main.go ├── day17_pt1 │ └── main.go ├── day17_pt2 │ └── main.go ├── day18_pt2 │ └── main.go ├── day19_pt1 │ └── main.go ├── day19_pt2 │ └── main.go ├── day20_pt1 │ └── main.go ├── day20_pt2 │ └── main.go ├── day21 │ └── main.go ├── day22_pt1 │ └── main.go ├── day22_pt2 │ └── main.go ├── day23_pt1 │ └── main.go ├── day23_pt2 │ └── main.go ├── day24_pt1 │ └── main.go ├── day24_pt2 │ └── main.go └── day25_pt1 │ └── main.go ├── .DS_Store ├── .gitignore └── README.md /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mskozlova/advent_of_code/1025d79f7b77cd7ab84fab1cd88bce368a509497/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.txt -------------------------------------------------------------------------------- /2022/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mskozlova/advent_of_code/1025d79f7b77cd7ab84fab1cd88bce368a509497/2022/.DS_Store -------------------------------------------------------------------------------- /2022/day01/day1_pt1.py: -------------------------------------------------------------------------------- 1 | max_cal = 0 2 | 3 | with open("input.txt", "r") as file: 4 | current_cal = 0 5 | for line in file.readlines(): 6 | if line.strip() == "": 7 | max_cal = max(max_cal, current_cal) 8 | current_cal = 0 9 | else: 10 | current_cal += int(line.strip()) 11 | max_cal = max(max_cal, current_cal) 12 | 13 | print(max_cal) 14 | -------------------------------------------------------------------------------- /2022/day01/day1_pt2.py: -------------------------------------------------------------------------------- 1 | max_cal = [] 2 | 3 | 4 | def recalc_max_cal(max_cal, new_value): 5 | if len(max_cal) < 3: 6 | max_cal.append(new_value) 7 | return sorted(max_cal, key=lambda x: -x) 8 | 9 | if new_value <= max_cal[-1]: 10 | return max_cal 11 | 12 | max_cal.pop() 13 | max_cal.append(new_value) 14 | 15 | return sorted(max_cal, key=lambda x: -x) 16 | 17 | 18 | with open("input.txt", "r") as file: 19 | current_cal = 0 20 | for line in file.readlines(): 21 | if line.strip() == "": 22 | max_cal = recalc_max_cal(max_cal, current_cal) 23 | current_cal = 0 24 | else: 25 | current_cal += int(line.strip()) 26 | max_cal = recalc_max_cal(max_cal, current_cal) 27 | 28 | print(sum(max_cal)) 29 | -------------------------------------------------------------------------------- /2022/day02/day2_pt1.py: -------------------------------------------------------------------------------- 1 | def play_game(code1, code2): 2 | if code1 == "A" and code2 == "Z": 3 | return 0 4 | if code1 == "A" and code2 == "X": 5 | return 3 6 | if code1 == "A" and code2 == "Y": 7 | return 6 8 | if code1 == "B" and code2 == "X": 9 | return 0 10 | if code1 == "B" and code2 == "Y": 11 | return 3 12 | if code1 == "B" and code2 == "Z": 13 | return 6 14 | if code1 == "C" and code2 == "Y": 15 | return 0 16 | if code1 == "C" and code2 == "Z": 17 | return 3 18 | if code1 == "C" and code2 == "X": 19 | return 6 20 | 21 | 22 | code_2_points = {"X": 1, "Y": 2, "Z": 3} 23 | 24 | with open("input.txt", "r") as file: 25 | score = 0 26 | for line in file.readlines(): 27 | code1, code2 = line.strip().split(" ") 28 | outcome_score = play_game(code1, code2) 29 | figure_score = code_2_points[code2] 30 | score += outcome_score + figure_score 31 | 32 | print(score) 33 | -------------------------------------------------------------------------------- /2022/day02/day2_pt2.py: -------------------------------------------------------------------------------- 1 | def get_figure(code1, code2): 2 | if code1 == "A" and code2 == "Z": 3 | return "B" 4 | if code1 == "A" and code2 == "X": 5 | return "C" 6 | if code1 == "A" and code2 == "Y": 7 | return "A" 8 | if code1 == "B" and code2 == "X": 9 | return "A" 10 | if code1 == "B" and code2 == "Y": 11 | return "B" 12 | if code1 == "B" and code2 == "Z": 13 | return "C" 14 | if code1 == "C" and code2 == "Y": 15 | return "C" 16 | if code1 == "C" and code2 == "Z": 17 | return "A" 18 | if code1 == "C" and code2 == "X": 19 | return "B" 20 | 21 | 22 | code_2_points = { 23 | "X": 0, 24 | "Y": 3, 25 | "Z": 6, 26 | } 27 | 28 | figure_2_points = {"A": 1, "B": 2, "C": 3} 29 | 30 | with open("input.txt", "r") as file: 31 | score = 0 32 | for line in file.readlines(): 33 | code1, code2 = line.strip().split(" ") 34 | figure_score = figure_2_points[get_figure(code1, code2)] 35 | outcome_score = code_2_points[code2] 36 | score += outcome_score + figure_score 37 | 38 | print(score) 39 | -------------------------------------------------------------------------------- /2022/day03/day3_pt1.py: -------------------------------------------------------------------------------- 1 | priorities = 0 2 | 3 | 4 | def find_error_symbol(half1, half2): 5 | return set(half1).intersection(set(half2)).pop() 6 | 7 | 8 | sym_2_priority = dict( 9 | [ 10 | (chr(ord_num + ord("a")), priority) 11 | for ord_num, priority in zip(range(26), range(1, 27)) 12 | ] 13 | + [ 14 | (chr(ord_num + ord("A")), priority + 26) 15 | for ord_num, priority in zip(range(26), range(1, 27)) 16 | ], 17 | ) 18 | 19 | print(sym_2_priority) 20 | 21 | with open("input.txt", "r") as file: 22 | for line in file.readlines(): 23 | new_line = line.strip() 24 | sym = find_error_symbol( 25 | new_line[: int(len(new_line) / 2)], new_line[int(len(new_line) / 2) :] 26 | ) 27 | priorities += sym_2_priority[sym] 28 | 29 | print(priorities) 30 | -------------------------------------------------------------------------------- /2022/day03/day3_pt2.py: -------------------------------------------------------------------------------- 1 | priorities = 0 2 | 3 | 4 | def find_badge_symbol(sack1, sack2, sack3): 5 | return set(sack1).intersection(set(sack2)).intersection(set(sack3)).pop() 6 | 7 | 8 | sym_2_priority = dict( 9 | [ 10 | (chr(ord_num + ord("a")), priority) 11 | for ord_num, priority in zip(range(26), range(1, 27)) 12 | ] 13 | + [ 14 | (chr(ord_num + ord("A")), priority + 26) 15 | for ord_num, priority in zip(range(26), range(1, 27)) 16 | ], 17 | ) 18 | 19 | with open("input.txt", "r") as file: 20 | three_sacks = [] 21 | 22 | for line in file.readlines(): 23 | new_line = line.strip() 24 | three_sacks.append(new_line) 25 | 26 | if len(three_sacks) == 3: 27 | sym = find_badge_symbol(*three_sacks) 28 | three_sacks = [] 29 | 30 | priorities += sym_2_priority[sym] 31 | 32 | print(priorities) 33 | -------------------------------------------------------------------------------- /2022/day04/day4_pt1.py: -------------------------------------------------------------------------------- 1 | n_intervals = 0 2 | 3 | with open("input.txt", "r") as file: 4 | for line in file.readlines(): 5 | int1, int2 = line.strip().split(",") 6 | int1_start, int1_end = list(map(int, int1.split("-"))) 7 | int2_start, int2_end = list(map(int, int2.split("-"))) 8 | 9 | if (int1_start >= int2_start and int1_end <= int2_end) or ( 10 | int1_start <= int2_start and int1_end >= int2_end 11 | ): 12 | n_intervals += 1 13 | 14 | print(n_intervals) 15 | -------------------------------------------------------------------------------- /2022/day04/day4_pt2.py: -------------------------------------------------------------------------------- 1 | n_intervals = 0 2 | 3 | with open("input.txt", "r") as file: 4 | for line in file.readlines(): 5 | int1, int2 = line.strip().split(",") 6 | int1_start, int1_end = list(map(int, int1.split("-"))) 7 | int2_start, int2_end = list(map(int, int2.split("-"))) 8 | 9 | if (int1_start >= int2_start and int1_start <= int2_end) or ( 10 | int1_start <= int2_start and int1_end >= int2_start 11 | ): 12 | n_intervals += 1 13 | 14 | print(n_intervals) 15 | -------------------------------------------------------------------------------- /2022/day05/day5_pt1.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def get_stacks(lines): 5 | number_of_stacks = max(map(int, lines[-1].split())) 6 | stacks = [[] for _ in range(number_of_stacks)] 7 | 8 | print(number_of_stacks) 9 | 10 | for line in lines[:-1]: 11 | try: 12 | for i in range(number_of_stacks): 13 | symbol_pos = i * 4 + 1 14 | if line[symbol_pos].isalpha(): 15 | stacks[i].append(line[symbol_pos]) 16 | except IndexError: 17 | continue 18 | 19 | return [stack[::-1] for stack in stacks] 20 | 21 | 22 | def parse_move(line): 23 | number = int(re.findall("move ([\d]+)", line)[0]) 24 | move = ( 25 | int(re.findall("from ([\d]+)", line)[0]) - 1, 26 | int(re.findall("to ([\d]+)", line)[0]) - 1, 27 | ) 28 | return number, move 29 | 30 | 31 | def apply_move(stacks, move): 32 | box = stacks[move[0]].pop() 33 | stacks[move[1]].append(box) 34 | 35 | 36 | def get_upper_layer(stacks): 37 | return "".join(stack[-1] for stack in stacks) 38 | 39 | 40 | with open("input.txt", "r") as file: 41 | stack_lines = [] 42 | stack_finished = False 43 | stacks = None 44 | 45 | for line in file.readlines(): 46 | if line.strip() == "": 47 | stack_finished = True 48 | stacks = get_stacks(stack_lines) 49 | print(stacks) 50 | elif not stack_finished: 51 | stack_lines.append(line) 52 | else: 53 | number, move = parse_move(line.strip()) 54 | for _ in range(number): 55 | apply_move(stacks, move) 56 | 57 | print(get_upper_layer(stacks)) 58 | -------------------------------------------------------------------------------- /2022/day05/day5_pt2.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | 4 | def get_stacks(lines): 5 | number_of_stacks = max(map(int, lines[-1].split())) 6 | stacks = [[] for _ in range(number_of_stacks)] 7 | 8 | print(number_of_stacks) 9 | 10 | for line in lines[:-1]: 11 | try: 12 | for i in range(number_of_stacks): 13 | symbol_pos = i * 4 + 1 14 | if line[symbol_pos].isalpha(): 15 | stacks[i].append(line[symbol_pos]) 16 | except IndexError: 17 | continue 18 | 19 | return [stack[::-1] for stack in stacks] 20 | 21 | 22 | def parse_move(line): 23 | number = int(re.findall("move ([\d]+)", line)[0]) 24 | move = ( 25 | int(re.findall("from ([\d]+)", line)[0]) - 1, 26 | int(re.findall("to ([\d]+)", line)[0]) - 1, 27 | ) 28 | return number, move 29 | 30 | 31 | def apply_move(stacks, move, number): 32 | stacks[move[1]].extend(stacks[move[0]][-number:]) 33 | stacks[move[0]] = stacks[move[0]][:-number] 34 | 35 | 36 | def get_upper_layer(stacks): 37 | return "".join(stack[-1] for stack in stacks) 38 | 39 | 40 | with open("input.txt", "r") as file: 41 | stack_lines = [] 42 | stack_finished = False 43 | stacks = None 44 | 45 | for line in file.readlines(): 46 | if line.strip() == "": 47 | stack_finished = True 48 | stacks = get_stacks(stack_lines) 49 | print(stacks) 50 | elif not stack_finished: 51 | stack_lines.append(line) 52 | else: 53 | number, move = parse_move(line.strip()) 54 | apply_move(stacks, move, number) 55 | 56 | print(get_upper_layer(stacks)) 57 | -------------------------------------------------------------------------------- /2022/day06/day6_pt1.py: -------------------------------------------------------------------------------- 1 | with open("input.txt", "r") as file: 2 | row = file.readline() 3 | 4 | for i in range(len(row) - 4): 5 | if len(set(row[i : i + 4])) == 4: 6 | print(i + 4) 7 | break 8 | -------------------------------------------------------------------------------- /2022/day06/day6_pt2.py: -------------------------------------------------------------------------------- 1 | with open("input.txt", "r") as file: 2 | row = file.readline() 3 | 4 | for i in range(len(row) - 14): 5 | if len(set(row[i : i + 14])) == 14: 6 | print(i + 14) 7 | break 8 | -------------------------------------------------------------------------------- /2022/day07/day7_pt1.py: -------------------------------------------------------------------------------- 1 | directories = dict() 2 | stack = [] 3 | 4 | with open("input.txt", "r") as file: 5 | for line in file.readlines(): 6 | if line.startswith("$ cd"): 7 | dir_name = line.strip()[5:] 8 | if dir_name == "..": 9 | stack.pop() 10 | elif dir_name == "/": 11 | stack = [] 12 | else: 13 | stack.append(dir_name) 14 | directories["/".join(stack)] = 0 15 | 16 | elif line.startswith("$ ls"): 17 | continue 18 | 19 | elif line.startswith("dir"): 20 | continue 21 | 22 | elif line[0].isdigit(): 23 | size, _ = line.split(" ") 24 | for i in range(len(stack)): 25 | directories["/".join(stack[: i + 1])] += int(size) 26 | 27 | else: 28 | print("unknown type of line: {}".format(line)) 29 | 30 | total_sum = 0 31 | for size in directories.values(): 32 | if size <= 100000: 33 | total_sum += size 34 | 35 | print(total_sum) 36 | -------------------------------------------------------------------------------- /2022/day07/day7_pt2.py: -------------------------------------------------------------------------------- 1 | directories = dict() 2 | stack = [] 3 | 4 | with open("input.txt", "r") as file: 5 | for line in file.readlines(): 6 | if line.startswith("$ cd"): 7 | dir_name = line.strip()[5:] 8 | if dir_name == "..": 9 | stack.pop() 10 | else: 11 | stack.append(dir_name) 12 | directories["/".join(stack)] = 0 13 | 14 | elif line.startswith("$ ls"): 15 | continue 16 | 17 | elif line.startswith("dir"): 18 | continue 19 | 20 | elif line[0].isdigit(): 21 | size, _ = line.split(" ") 22 | for i in range(len(stack)): 23 | directories["/".join(stack[: i + 1])] += int(size) 24 | 25 | else: 26 | print("unknown type of line: {}".format(line)) 27 | 28 | total_disk = 70000000 29 | needed_disk = 30000000 30 | 31 | occupied_disk = directories["/"] 32 | need_freed = needed_disk - (total_disk - occupied_disk) 33 | 34 | print("occupied: {}, need_freed: {}".format(occupied_disk, need_freed)) 35 | 36 | candidates = [(key, size) for key, size in directories.items() if size >= need_freed] 37 | print(sorted(candidates, key=lambda x: x[1])[0]) 38 | -------------------------------------------------------------------------------- /2022/day08/day8_pt1.py: -------------------------------------------------------------------------------- 1 | trees = [] 2 | 3 | with open("input.txt", "r") as file: 4 | for line in file.readlines(): 5 | trees.append(list(map(int, line.strip()))) 6 | 7 | visibility = [[0] * len(trees[0]) for _ in range(len(trees))] 8 | 9 | for row in range(len(trees)): 10 | max_row = -1 11 | for col in range(len(trees[0])): 12 | if trees[row][col] > max_row: 13 | max_row = trees[row][col] 14 | visibility[row][col] = 1 15 | 16 | max_row = -1 17 | for col in range(len(trees[0]) - 1, -1, -1): 18 | if trees[row][col] > max_row: 19 | max_row = trees[row][col] 20 | visibility[row][col] = 1 21 | 22 | for col in range(len(trees[0])): 23 | max_col = -1 24 | for row in range(len(trees)): 25 | if trees[row][col] > max_col: 26 | max_col = trees[row][col] 27 | visibility[row][col] = 1 28 | 29 | max_col = -1 30 | for row in range(len(trees) - 1, -1, -1): 31 | if trees[row][col] > max_col: 32 | max_col = trees[row][col] 33 | visibility[row][col] = 1 34 | 35 | total_visible = 0 36 | for row in visibility: 37 | total_visible += sum(row) 38 | 39 | print(total_visible) 40 | -------------------------------------------------------------------------------- /2022/day08/day8_pt2.py: -------------------------------------------------------------------------------- 1 | trees = [] 2 | 3 | with open("input.txt", "r") as file: 4 | for line in file.readlines(): 5 | trees.append(list(map(int, line.strip()))) 6 | 7 | for row in trees: 8 | print(row) 9 | print() 10 | 11 | print(len(trees), len(trees[0])) 12 | 13 | 14 | def count_visibility(x, y, direction): 15 | if direction == "up": 16 | for i in range(1, max(len(trees), len(trees[0])) + 1): 17 | print(direction, x, y, i) 18 | if x - i >= 0: 19 | if trees[x - i][y] < trees[x][y]: 20 | continue 21 | else: 22 | return i 23 | else: 24 | return i - 1 25 | elif direction == "down": 26 | for i in range(1, max(len(trees), len(trees[0])) + 1): 27 | print(direction, x, y, i) 28 | if x + i < len(trees): 29 | if trees[x + i][y] < trees[x][y]: 30 | continue 31 | else: 32 | return i 33 | else: 34 | return i - 1 35 | elif direction == "right": 36 | for i in range(1, max(len(trees), len(trees[0])) + 1): 37 | print(direction, x, y, i) 38 | if y + i < len(trees[0]): 39 | if trees[x][y + i] < trees[x][y]: 40 | continue 41 | else: 42 | return i 43 | else: 44 | return i - 1 45 | elif direction == "left": 46 | for i in range(1, max(len(trees), len(trees[0])) + 1): 47 | print(direction, x, y, i) 48 | if y - i >= 0: 49 | if trees[x][y - i] < trees[x][y]: 50 | continue 51 | else: 52 | return i 53 | else: 54 | return i - 1 55 | 56 | 57 | max_visibility = 0 58 | visibilities = [] 59 | 60 | for x in range(len(trees)): 61 | row = [] 62 | for y in range(len(trees[0])): 63 | current_visibility = ( 64 | count_visibility(x, y, "up") 65 | * count_visibility(x, y, "down") 66 | * count_visibility(x, y, "left") 67 | * count_visibility(x, y, "right") 68 | ) 69 | max_visibility = max(max_visibility, current_visibility) 70 | row.append( 71 | [ 72 | count_visibility(x, y, "up"), 73 | count_visibility(x, y, "down"), 74 | count_visibility(x, y, "left"), 75 | count_visibility(x, y, "right"), 76 | ] 77 | ) 78 | 79 | visibilities.append(row) 80 | 81 | for row in visibilities: 82 | print(row) 83 | print() 84 | 85 | print(max_visibility) 86 | -------------------------------------------------------------------------------- /2022/day09/day9_pt1.py: -------------------------------------------------------------------------------- 1 | def make_move(x_head, y_head, x_tail, y_tail, direction): 2 | if direction == "U": 3 | x_head += 1 4 | elif direction == "D": 5 | x_head -= 1 6 | elif direction == "L": 7 | y_head -= 1 8 | else: 9 | y_head += 1 10 | 11 | if abs(x_head - x_tail) <= 1 and abs(y_head - y_tail) <= 1: 12 | return x_head, y_head, x_tail, y_tail 13 | 14 | if x_head == x_tail and y_head > y_tail: 15 | y_tail += 1 16 | return x_head, y_head, x_tail, y_tail 17 | if x_head == x_tail and y_head < y_tail: 18 | y_tail -= 1 19 | return x_head, y_head, x_tail, y_tail 20 | if y_head == y_tail and x_head > x_tail: 21 | x_tail += 1 22 | return x_head, y_head, x_tail, y_tail 23 | if y_head == y_tail and x_head < x_tail: 24 | x_tail -= 1 25 | return x_head, y_head, x_tail, y_tail 26 | 27 | if x_head > x_tail and y_head > y_tail: 28 | x_tail += 1 29 | y_tail += 1 30 | return x_head, y_head, x_tail, y_tail 31 | if x_head > x_tail and y_head < y_tail: 32 | x_tail += 1 33 | y_tail -= 1 34 | return x_head, y_head, x_tail, y_tail 35 | if x_head < x_tail and y_head > y_tail: 36 | x_tail -= 1 37 | y_tail += 1 38 | return x_head, y_head, x_tail, y_tail 39 | if x_head < x_tail and y_head < y_tail: 40 | x_tail -= 1 41 | y_tail -= 1 42 | return x_head, y_head, x_tail, y_tail 43 | 44 | 45 | def draw_positions(x_head, y_head, x_tail, y_tail, max_coord): 46 | for i in range(max_coord): 47 | row = [] 48 | for j in range(max_coord): 49 | if max_coord - i - 1 == x_head and y_head == j: 50 | row.append("H") 51 | elif max_coord - i - 1 == x_tail and y_tail == j: 52 | row.append("T") 53 | else: 54 | row.append(".") 55 | print("".join(row)) 56 | print() 57 | 58 | 59 | positions = set() 60 | positions.add((0, 0)) 61 | 62 | x_head, y_head, x_tail, y_tail = 0, 0, 0, 0 63 | 64 | with open("input.txt", "r") as file: 65 | for line in file.readlines(): 66 | direction, num = line.strip().split(" ") 67 | num = int(num) 68 | 69 | print(direction, num) 70 | for _ in range(num): 71 | x_head, y_head, x_tail, y_tail = make_move( 72 | x_head, y_head, x_tail, y_tail, direction 73 | ) 74 | positions.add((x_tail, y_tail)) 75 | 76 | print(len(positions)) 77 | -------------------------------------------------------------------------------- /2022/day09/day9_pt2.py: -------------------------------------------------------------------------------- 1 | def move_head(rope, direction): 2 | if direction == "U": 3 | rope[0][0] += 1 4 | elif direction == "D": 5 | rope[0][0] -= 1 6 | elif direction == "L": 7 | rope[0][1] -= 1 8 | else: 9 | rope[0][1] += 1 10 | 11 | 12 | def make_move(rope, knot): 13 | if ( 14 | abs(rope[knot - 1][0] - rope[knot][0]) <= 1 15 | and abs(rope[knot - 1][1] - rope[knot][1]) <= 1 16 | ): 17 | return 18 | 19 | if rope[knot - 1][0] == rope[knot][0] and rope[knot - 1][1] > rope[knot][1]: 20 | rope[knot][1] += 1 21 | return 22 | if rope[knot - 1][0] == rope[knot][0] and rope[knot - 1][1] < rope[knot][1]: 23 | rope[knot][1] -= 1 24 | return 25 | if rope[knot - 1][1] == rope[knot][1] and rope[knot - 1][0] > rope[knot][0]: 26 | rope[knot][0] += 1 27 | return 28 | if rope[knot - 1][1] == rope[knot][1] and rope[knot - 1][0] < rope[knot][0]: 29 | rope[knot][0] -= 1 30 | return 31 | 32 | if rope[knot - 1][0] > rope[knot][0] and rope[knot - 1][1] > rope[knot][1]: 33 | rope[knot][0] += 1 34 | rope[knot][1] += 1 35 | return 36 | if rope[knot - 1][0] > rope[knot][0] and rope[knot - 1][1] < rope[knot][1]: 37 | rope[knot][0] += 1 38 | rope[knot][1] -= 1 39 | return 40 | if rope[knot - 1][0] < rope[knot][0] and rope[knot - 1][1] > rope[knot][1]: 41 | rope[knot][0] -= 1 42 | rope[knot][1] += 1 43 | return 44 | if rope[knot - 1][0] < rope[knot][0] and rope[knot - 1][1] < rope[knot][1]: 45 | rope[knot][0] -= 1 46 | rope[knot][1] -= 1 47 | return 48 | 49 | 50 | def draw_positions(rope, max_coord): 51 | for i in range(max_coord): 52 | row = [] 53 | for j in range(max_coord): 54 | for knot in range(len(rope)): 55 | if max_coord - i - 1 == rope[knot][0] and rope[knot][1] == j: 56 | if knot == 0: 57 | row.append("H") 58 | elif knot == 9: 59 | row.append("T") 60 | else: 61 | row.append(str(knot)) 62 | else: 63 | row.append(".") 64 | print("".join(row)) 65 | print() 66 | 67 | 68 | positions = set() 69 | positions.add((0, 0)) 70 | 71 | rope = [[0, 0] for _ in range(10)] 72 | 73 | with open("input.txt", "r") as file: 74 | for line in file.readlines(): 75 | direction, num = line.strip().split(" ") 76 | num = int(num) 77 | 78 | print(direction, num) 79 | for _ in range(num): 80 | move_head(rope, direction) 81 | for knot in range(1, len(rope)): 82 | make_move(rope, knot) 83 | positions.add(tuple(rope[-1])) 84 | 85 | print(len(positions)) 86 | -------------------------------------------------------------------------------- /2022/day10/day10_pt1.py: -------------------------------------------------------------------------------- 1 | target_ticks = [20, 60, 100, 140, 180, 220] 2 | tick_vals = [1] 3 | 4 | with open("input.txt", "r") as file: 5 | for line in file.readlines(): 6 | if line.startswith("noop"): 7 | tick_vals.append(tick_vals[-1]) 8 | else: 9 | command, value = line.strip().split(" ") 10 | tick_vals.extend([tick_vals[-1], tick_vals[-1] + int(value)]) 11 | 12 | if len(tick_vals) > 221: 13 | break 14 | 15 | print(sum([tick_vals[tick - 1] * tick for tick in target_ticks])) 16 | -------------------------------------------------------------------------------- /2022/day10/day10_pt2.py: -------------------------------------------------------------------------------- 1 | target_ticks = [20, 60, 100, 140, 180, 220] 2 | tick_vals = [1] 3 | 4 | with open("input.txt", "r") as file: 5 | for line in file.readlines(): 6 | if line.startswith("noop"): 7 | tick_vals.append(tick_vals[-1]) 8 | else: 9 | command, value = line.strip().split(" ") 10 | tick_vals.extend([tick_vals[-1], tick_vals[-1] + int(value)]) 11 | 12 | if len(tick_vals) > 6 * 40 + 1: 13 | break 14 | 15 | print(tick_vals) 16 | 17 | pixels = [] 18 | for i in range(6): 19 | for j in range(40): 20 | if abs(tick_vals[i * 40 + j] - j) <= 1: 21 | pixels.append("£") 22 | else: 23 | pixels.append(".") 24 | 25 | for i in range(6): 26 | print("".join(pixels[i * 40 : (i + 1) * 40])) 27 | -------------------------------------------------------------------------------- /2022/day11/day11_pt1.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from itertools import islice 3 | from math import floor 4 | 5 | 6 | class Monkey: 7 | def __init__(self, lines): 8 | lines = [line.strip() for line in lines] 9 | self.list = list(map(int, lines[1][16:].split(", "))) 10 | self.rule = self.__parse_rule(lines[2]) 11 | self.test = int(lines[3][19:]) 12 | self.if_true = int(lines[4][-1]) 13 | self.if_false = int(lines[5][-1]) 14 | self.throw_cnt = 0 15 | 16 | def __parse_rule(self, rule_line): 17 | operation = rule_line[21] 18 | second_number = None 19 | 20 | if not rule_line.endswith("old"): 21 | second_number = int(rule_line[23:]) 22 | 23 | if operation == "*": 24 | if second_number is None: 25 | return lambda old: old * old 26 | else: 27 | return lambda old: old * second_number 28 | elif operation == "+": 29 | return lambda old: old + second_number 30 | 31 | def __throw(self, item): 32 | operation_res = self.rule(item) 33 | worry_lvl = int(floor(operation_res / 3)) 34 | 35 | if worry_lvl % self.test == 0: 36 | return self.if_true, worry_lvl 37 | return self.if_false, worry_lvl 38 | 39 | def round(self): 40 | throws = defaultdict(list) 41 | for item in self.list: 42 | monkey_idx, worry_lvl = self.__throw(item) 43 | throws[monkey_idx].append(worry_lvl) 44 | self.throw_cnt += 1 45 | 46 | self.list = [] 47 | 48 | return throws 49 | 50 | def receive(self, items): 51 | self.list.extend(items) 52 | 53 | 54 | n = 7 # Or whatever chunk size you want 55 | monkeys = [] 56 | with open("input.txt", "r") as f: 57 | for n_lines in iter(lambda: tuple(islice(f, n)), ()): 58 | monkeys.append(Monkey(n_lines)) 59 | 60 | for i in range(20): 61 | for j, monkey in enumerate(monkeys): 62 | print(f"Round {i}, monkey {j}") 63 | throws = monkey.round() 64 | for mid, items in throws.items(): 65 | monkeys[mid].receive(items) 66 | 67 | for j, monkey in enumerate(monkeys): 68 | print(f"ROUND {i}, MONKEY {j}, ITEMS {monkey.list}") 69 | 70 | for monkey in monkeys: 71 | print(monkey.throw_cnt) 72 | 73 | 74 | top_monkeys = sorted(monkeys, key=lambda m: -m.throw_cnt) 75 | print(top_monkeys[0].throw_cnt * top_monkeys[1].throw_cnt) 76 | -------------------------------------------------------------------------------- /2022/day11/day11_pt2.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from itertools import islice 3 | from math import floor, lcm 4 | 5 | 6 | class Monkey: 7 | def __init__(self, lines): 8 | lines = [line.strip() for line in lines] 9 | self.list = list(map(int, lines[1][16:].split(", "))) 10 | self.rule = self.__parse_rule(lines[2]) 11 | self.test = int(lines[3][19:]) 12 | self.if_true = int(lines[4][-1]) 13 | self.if_false = int(lines[5][-1]) 14 | self.throw_cnt = 0 15 | 16 | def __parse_rule(self, rule_line): 17 | operation = rule_line[21] 18 | second_number = None 19 | 20 | if not rule_line.endswith("old"): 21 | second_number = int(rule_line[23:]) 22 | 23 | if operation == "*": 24 | if second_number is None: 25 | return lambda old: old * old 26 | else: 27 | return lambda old: old * second_number 28 | elif operation == "+": 29 | return lambda old: old + second_number 30 | 31 | def __throw(self, item, monkey_lcm): 32 | operation_res = self.rule(item) 33 | worry_lvl = operation_res % monkey_lcm 34 | 35 | if worry_lvl % self.test == 0: 36 | return self.if_true, worry_lvl 37 | return self.if_false, worry_lvl 38 | 39 | def round(self, monkey_lcm): 40 | throws = defaultdict(list) 41 | for item in self.list: 42 | monkey_idx, worry_lvl = self.__throw(item, monkey_lcm) 43 | throws[monkey_idx].append(worry_lvl) 44 | self.throw_cnt += 1 45 | 46 | self.list = [] 47 | 48 | return throws 49 | 50 | def receive(self, items): 51 | self.list.extend(items) 52 | 53 | 54 | n = 7 # Or whatever chunk size you want 55 | monkeys = [] 56 | with open("input.txt", "r") as f: 57 | for n_lines in iter(lambda: tuple(islice(f, n)), ()): 58 | monkeys.append(Monkey(n_lines)) 59 | 60 | monkey_lcm = lcm(*[monkey.test for monkey in monkeys]) 61 | 62 | for i in range(10000): 63 | for j, monkey in enumerate(monkeys): 64 | print(f"Round {i}, monkey {j}") 65 | throws = monkey.round(monkey_lcm) 66 | for mid, items in throws.items(): 67 | monkeys[mid].receive(items) 68 | 69 | for j, monkey in enumerate(monkeys): 70 | print(f"ROUND {i}, MONKEY {j}, ITEMS {monkey.list}") 71 | 72 | for monkey in monkeys: 73 | print(monkey.throw_cnt) 74 | 75 | 76 | top_monkeys = sorted(monkeys, key=lambda m: -m.throw_cnt) 77 | print(top_monkeys[0].throw_cnt * top_monkeys[1].throw_cnt) 78 | -------------------------------------------------------------------------------- /2022/day12/day12_pt1.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | with open("input.txt", "r") as file: 4 | heightmap = [line.strip() for line in file.readlines()] 5 | 6 | 7 | def find_sym(heightmap, sym): 8 | for i, line in enumerate(heightmap): 9 | for j, map_sym in enumerate(line): 10 | if map_sym == sym: 11 | return i, j 12 | 13 | 14 | def is_neighbor_height(h1, h2): 15 | h1 = "a" if h1 == "S" else h1 16 | h1 = "z" if h1 == "E" else h1 17 | h2 = "a" if h2 == "S" else h2 18 | h2 = "z" if h2 == "E" else h2 19 | return ord(h1) - ord(h2) >= -1 20 | 21 | 22 | def get_neighbors(heightmap, visited, point): 23 | potential = [ 24 | [point[0] - 1, point[1]], 25 | [point[0] + 1, point[1]], 26 | [point[0], point[1] + 1], 27 | [point[0], point[1] - 1], 28 | ] 29 | neighbors = [] 30 | for p in potential: 31 | if tuple(p) not in visited: 32 | if p[0] >= 0 and p[0] < len(heightmap): 33 | if p[1] >= 0 and p[1] < len(heightmap[0]): 34 | if is_neighbor_height( 35 | heightmap[point[0]][point[1]], heightmap[p[0]][p[1]] 36 | ): 37 | neighbors.append(p) 38 | visited.add(tuple(p)) 39 | 40 | return neighbors 41 | 42 | 43 | start_coords = find_sym(heightmap, "S") 44 | end_coords = find_sym(heightmap, "E") 45 | 46 | print(start_coords, end_coords) 47 | 48 | queue = deque() 49 | visited = set() 50 | 51 | queue.append((0, start_coords)) 52 | visited.add(start_coords) 53 | 54 | while len(queue) > 0: 55 | step, point = queue.popleft() 56 | print(step, point) 57 | 58 | if tuple(point) == end_coords: 59 | print(step) 60 | break 61 | 62 | neighbors = get_neighbors(heightmap, visited, point) 63 | 64 | for n in neighbors: 65 | queue.append((step + 1, n)) 66 | -------------------------------------------------------------------------------- /2022/day12/day12_pt2.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | with open("input.txt", "r") as file: 4 | heightmap = [line.strip() for line in file.readlines()] 5 | 6 | 7 | def find_sym(heightmap, sym): 8 | res = [] 9 | for i, line in enumerate(heightmap): 10 | for j, map_sym in enumerate(line): 11 | if map_sym == sym: 12 | res.append((i, j)) 13 | return res 14 | 15 | 16 | def is_neighbor_height(h1, h2): 17 | h1 = "a" if h1 == "S" else h1 18 | h1 = "z" if h1 == "E" else h1 19 | h2 = "a" if h2 == "S" else h2 20 | h2 = "z" if h2 == "E" else h2 21 | return ord(h1) - ord(h2) >= -1 22 | 23 | 24 | def get_neighbors(heightmap, visited, point): 25 | potential = [ 26 | [point[0] - 1, point[1]], 27 | [point[0] + 1, point[1]], 28 | [point[0], point[1] + 1], 29 | [point[0], point[1] - 1], 30 | ] 31 | neighbors = [] 32 | for p in potential: 33 | if tuple(p) not in visited: 34 | if p[0] >= 0 and p[0] < len(heightmap): 35 | if p[1] >= 0 and p[1] < len(heightmap[0]): 36 | if is_neighbor_height( 37 | heightmap[point[0]][point[1]], heightmap[p[0]][p[1]] 38 | ): 39 | neighbors.append(p) 40 | visited.add(tuple(p)) 41 | 42 | return neighbors 43 | 44 | 45 | start_coords_list = find_sym(heightmap, "S") 46 | start_coords_list.extend(find_sym(heightmap, "a")) 47 | end_coords = find_sym(heightmap, "E")[0] 48 | 49 | print(start_coords_list, end_coords) 50 | 51 | min_path = 320000000000 52 | 53 | for start_coords in start_coords_list: 54 | print(start_coords) 55 | queue = deque() 56 | visited = set() 57 | 58 | queue.append((0, start_coords)) 59 | visited.add(start_coords) 60 | 61 | while len(queue) > 0: 62 | step, point = queue.popleft() 63 | 64 | if tuple(point) == end_coords: 65 | print(step) 66 | min_path = min(min_path, step) 67 | break 68 | 69 | neighbors = get_neighbors(heightmap, visited, point) 70 | 71 | for n in neighbors: 72 | queue.append((step + 1, n)) 73 | 74 | print(min_path) 75 | -------------------------------------------------------------------------------- /2022/day13/day13_pt1.py: -------------------------------------------------------------------------------- 1 | import json 2 | from itertools import zip_longest 3 | 4 | 5 | def compare(lst1, lst2): 6 | for l1, l2 in zip_longest(lst1, lst2, fillvalue=None): 7 | if l1 is None: 8 | return True 9 | elif l2 is None: 10 | return False 11 | elif isinstance(l1, int) and isinstance(l2, int): 12 | if l1 > l2: 13 | return False 14 | elif l1 < l2: 15 | return True 16 | elif isinstance(l1, list) and isinstance(l2, list): 17 | if not compare(l1, l2): 18 | return False 19 | elif isinstance(l1, int) and isinstance(l2, list): 20 | if not compare([l1], l2): 21 | return False 22 | else: 23 | if not compare(l1, [l2]): 24 | return False 25 | 26 | return True 27 | 28 | 29 | correct_cnt = 0 30 | 31 | 32 | with open("input.txt", "r") as file: 33 | active_lines = [] 34 | for line in file.readlines(): 35 | if len(active_lines) < 2: 36 | active_lines.append(json.loads(line)) 37 | else: 38 | for l in active_lines: 39 | print(l) 40 | if compare(*active_lines): 41 | correct_cnt += 1 42 | print(compare(*active_lines), "\n") 43 | active_lines = [] 44 | 45 | print(correct_cnt) 46 | -------------------------------------------------------------------------------- /2023/day01/day1_pt1.py: -------------------------------------------------------------------------------- 1 | calibration_sum = 0 2 | 3 | with open("input.txt") as file: 4 | for line in file.readlines(): 5 | digit_symbols = list(filter(lambda x: x.isdigit(), line)) 6 | calibration_sum += int(digit_symbols[0] + digit_symbols[-1]) 7 | 8 | print(calibration_sum) 9 | -------------------------------------------------------------------------------- /2023/day01/day1_pt2.py: -------------------------------------------------------------------------------- 1 | calibration_sum = 0 2 | 3 | digits_encoding = { 4 | "one": 1, 5 | "two": 2, 6 | "three": 3, 7 | "four": 4, 8 | "five": 5, 9 | "six": 6, 10 | "seven": 7, 11 | "eight": 8, 12 | "nine": 9, 13 | } 14 | 15 | 16 | def get_digits(line): 17 | digits = [] 18 | 19 | for i, sym in enumerate(line): 20 | if sym.isdigit(): 21 | digits.append(int(sym)) 22 | 23 | for encoding, digit in digits_encoding.items(): 24 | if line[i:].startswith(encoding): 25 | digits.append(digit) 26 | 27 | return digits 28 | 29 | 30 | with open("input.txt") as file: 31 | for line in file.readlines(): 32 | digits = get_digits(line) 33 | calibration_sum += digits[0] * 10 + digits[-1] 34 | 35 | print(calibration_sum) 36 | -------------------------------------------------------------------------------- /2023/day02/day2_pt1.py: -------------------------------------------------------------------------------- 1 | # only 12 red cubes, 13 green cubes, and 14 blue cubes? 2 | max_target = { 3 | "red": 12, 4 | "blue": 14, 5 | "green": 13, 6 | } 7 | 8 | 9 | def parse_step(step): 10 | colors = map(lambda x: x.strip(), step.split(",")) 11 | parsed = dict() 12 | 13 | for color_info in colors: 14 | count, color = color_info.split(" ") 15 | parsed[color] = int(count) 16 | 17 | return parsed 18 | 19 | 20 | def parse_game(line): 21 | # example: "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green" 22 | return { 23 | "id": int(line[5 : line.rindex(":")]), 24 | "steps": [parse_step(step) for step in line[line.rindex(":") + 1 :].split(";")], 25 | } 26 | 27 | 28 | def is_game_possible(game): 29 | for step in game["steps"]: 30 | for color, count in step.items(): 31 | if count > max_target[color]: 32 | return False 33 | return True 34 | 35 | 36 | sum_possible_ids = 0 37 | 38 | with open("input.txt", "r") as file: 39 | for line in file.readlines(): 40 | game = parse_game(line) 41 | if is_game_possible(game): 42 | sum_possible_ids += game["id"] 43 | 44 | print(sum_possible_ids) 45 | -------------------------------------------------------------------------------- /2023/day02/day2_pt2.py: -------------------------------------------------------------------------------- 1 | all_colors = ["red", "green", "blue"] 2 | 3 | 4 | def parse_step(step): 5 | colors = map(lambda x: x.strip(), step.split(",")) 6 | parsed = dict() 7 | 8 | for color_info in colors: 9 | count, color = color_info.split(" ") 10 | parsed[color] = int(count) 11 | 12 | return parsed 13 | 14 | 15 | def parse_game(line): 16 | # example: "Game 1: 3 blue, 4 red; 1 red, 2 green, 6 blue; 2 green" 17 | return { 18 | "id": int(line[5 : line.rindex(":")]), 19 | "steps": [parse_step(step) for step in line[line.rindex(":") + 1 :].split(";")], 20 | } 21 | 22 | 23 | def get_min_power(game): 24 | power = 1 25 | for color in all_colors: 26 | power *= max([step.get(color, 0) for step in game["steps"]]) 27 | return power 28 | 29 | 30 | sum_power = 0 31 | 32 | with open("input.txt", "r") as file: 33 | for line in file.readlines(): 34 | game = parse_game(line) 35 | sum_power += get_min_power(game) 36 | 37 | print(sum_power) 38 | -------------------------------------------------------------------------------- /2023/day03/day3_pt1.py: -------------------------------------------------------------------------------- 1 | def get_number_coords(line, line_num): 2 | numbers = [] 3 | current_number_start = None 4 | 5 | for i, sym in enumerate(line): 6 | if sym.isdigit(): 7 | if current_number_start is None: 8 | current_number_start = i 9 | 10 | elif current_number_start is not None: 11 | numbers.append( 12 | { 13 | "row": line_num, 14 | "col": current_number_start, 15 | "length": i - current_number_start, 16 | "number": int(line[current_number_start:i]), 17 | } 18 | ) 19 | current_number_start = None 20 | 21 | if current_number_start is not None: 22 | numbers.append( 23 | { 24 | "row": line_num, 25 | "col": current_number_start, 26 | "length": len(line) - current_number_start, 27 | "number": int(line[current_number_start:]), 28 | } 29 | ) 30 | 31 | return numbers 32 | 33 | 34 | def check_symbol_adjucency(row, col, length, lines): 35 | adj_coords = [] 36 | adj_coords.extend( 37 | [(row - 1, col_num) for col_num in range(col - 1, col + length + 1)] 38 | ) 39 | adj_coords.extend([(row, col - 1), (row, col + length)]) 40 | adj_coords.extend( 41 | [(row + 1, col_num) for col_num in range(col - 1, col + length + 1)] 42 | ) 43 | 44 | adj_coords = list( 45 | filter( 46 | lambda c: c[0] >= 0 47 | and c[1] >= 0 48 | and c[0] < len(lines) 49 | and c[1] < len(lines[0]), 50 | adj_coords, 51 | ) 52 | ) 53 | 54 | for r, c in adj_coords: 55 | if lines[r][c] != "." and not lines[r][c].isdigit(): 56 | return True 57 | 58 | return False 59 | 60 | 61 | with open("input.txt", "r") as file: 62 | lines = [line.strip() for line in file.readlines()] 63 | 64 | coords = [] 65 | for i, line in enumerate(lines): 66 | coords.extend(get_number_coords(line, i)) 67 | 68 | adj_number_sum = 0 69 | for number_info in coords: 70 | if check_symbol_adjucency( 71 | number_info["row"], number_info["col"], number_info["length"], lines 72 | ): 73 | adj_number_sum += number_info["number"] 74 | 75 | print(adj_number_sum) 76 | -------------------------------------------------------------------------------- /2023/day03/day3_pt2.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | 4 | def get_number_coords(line, line_num): 5 | numbers = [] 6 | current_number_start = None 7 | 8 | for i, sym in enumerate(line): 9 | if sym.isdigit(): 10 | if current_number_start is None: 11 | current_number_start = i 12 | 13 | elif current_number_start is not None: 14 | numbers.append( 15 | { 16 | "row": line_num, 17 | "col": current_number_start, 18 | "length": i - current_number_start, 19 | "number": int(line[current_number_start:i]), 20 | } 21 | ) 22 | current_number_start = None 23 | 24 | if current_number_start is not None: 25 | numbers.append( 26 | { 27 | "row": line_num, 28 | "col": current_number_start, 29 | "length": len(line) - current_number_start, 30 | "number": int(line[current_number_start:]), 31 | } 32 | ) 33 | 34 | return numbers 35 | 36 | 37 | def get_adjucent_stars(row, col, length, number, lines): 38 | adj_coords = [] 39 | adj_coords.extend( 40 | [(row - 1, col_num) for col_num in range(col - 1, col + length + 1)] 41 | ) 42 | adj_coords.extend([(row, col - 1), (row, col + length)]) 43 | adj_coords.extend( 44 | [(row + 1, col_num) for col_num in range(col - 1, col + length + 1)] 45 | ) 46 | 47 | adj_coords = list( 48 | filter( 49 | lambda c: c[0] >= 0 50 | and c[1] >= 0 51 | and c[0] < len(lines) 52 | and c[1] < len(lines[0]), 53 | adj_coords, 54 | ) 55 | ) 56 | 57 | adj_stars = [] 58 | for r, c in adj_coords: 59 | if lines[r][c] == "*": 60 | adj_stars.append((r, c, number)) 61 | return adj_stars 62 | 63 | 64 | def get_total_gear_ratio(adj_stars): 65 | stars = defaultdict(list) 66 | total_ratio = 0 67 | 68 | for row, col, number in adj_stars: 69 | stars[(row, col)].append(number) 70 | 71 | for numbers in stars.values(): 72 | if len(numbers) == 2: 73 | total_ratio += (numbers[0] * numbers[1]) 74 | 75 | return total_ratio 76 | 77 | 78 | with open("input.txt", "r") as file: 79 | lines = [line.strip() for line in file.readlines()] 80 | 81 | coords = [] 82 | for i, line in enumerate(lines): 83 | coords.extend(get_number_coords(line, i)) 84 | 85 | adj_stars = [] 86 | for number_info in coords: 87 | adj_stars.extend(get_adjucent_stars( 88 | number_info["row"], 89 | number_info["col"], 90 | number_info["length"], 91 | number_info["number"], 92 | lines 93 | )) 94 | 95 | print(get_total_gear_ratio(adj_stars)) 96 | -------------------------------------------------------------------------------- /2023/day04/day4_pt1.py: -------------------------------------------------------------------------------- 1 | def parse_card(line): 2 | number_lists = line[line.rindex(":") + 1 :].split("|") 3 | assert len(number_lists) == 2, f"Too many values after | split: {line}" 4 | 5 | winning_set = set(map(int, number_lists[0].strip().split())) 6 | our_set = set(map(int, number_lists[1].strip().split())) 7 | 8 | return winning_set, our_set 9 | 10 | 11 | total_points = 0 12 | 13 | with open("input.txt", "r") as file: 14 | for line in file.readlines(): 15 | winning_set, our_set = parse_card(line) 16 | n_wins = len(winning_set.intersection(our_set)) 17 | if n_wins > 0: 18 | total_points += 2 ** (n_wins - 1) 19 | 20 | print(total_points) 21 | -------------------------------------------------------------------------------- /2023/day04/day4_pt2.py: -------------------------------------------------------------------------------- 1 | def parse_card(line): 2 | number_lists = line[line.rindex(":") + 1 :].split("|") 3 | assert len(number_lists) == 2, f"Too many values after | split: {line}" 4 | 5 | winning_set = set(map(int, number_lists[0].strip().split())) 6 | our_set = set(map(int, number_lists[1].strip().split())) 7 | 8 | return winning_set, our_set 9 | 10 | 11 | total_cards = 0 12 | card_copies = dict() 13 | 14 | with open("input.txt", "r") as file: 15 | for i, line in enumerate(file.readlines()): 16 | winning_set, our_set = parse_card(line) 17 | n_wins = len(winning_set.intersection(our_set)) 18 | 19 | # first - how many copies of this card did we have? 20 | current_card = i + 1 21 | n_copies_current = card_copies.get(current_card, 1) 22 | 23 | total_cards += n_copies_current 24 | 25 | # second - how many copies this card generated further? 26 | for next_idx in range(current_card + 1, current_card + 1 + n_wins): 27 | card_copies[next_idx] = card_copies.get(next_idx, 1) + n_copies_current 28 | 29 | print(total_cards) 30 | -------------------------------------------------------------------------------- /2023/day05/day5_pt1.py: -------------------------------------------------------------------------------- 1 | def parse_seeds(line): 2 | return list(map(int, line[7:].strip().split())) 3 | 4 | 5 | def parse_maps(lines): 6 | maps = [] 7 | 8 | for line in lines: 9 | if line.strip() == "": 10 | continue 11 | 12 | if line.strip().endswith("map:"): 13 | maps.append([]) 14 | continue 15 | 16 | maps[-1].append(list(map(int, line.strip().split()))) 17 | 18 | for i, mp in enumerate(maps): 19 | maps[i] = sorted(mp, key=lambda x: x[1]) # source range start 20 | 21 | return maps 22 | 23 | 24 | def parse_file(file_name): 25 | with open(file_name, "r") as file: 26 | seeds = parse_seeds(file.readline()) 27 | maps = parse_maps(file.readlines()) 28 | return seeds, maps 29 | 30 | 31 | def get_location(seed, maps): 32 | current_idx = seed 33 | for mp in maps: 34 | for dest, source, range_len in mp: 35 | if ( 36 | source <= current_idx and current_idx < source + range_len 37 | ): # found mapping! 38 | idx = current_idx - source 39 | current_idx = dest + idx 40 | break 41 | return current_idx 42 | 43 | 44 | seeds, maps = parse_file("input.txt") 45 | locations = [get_location(seed, maps) for seed in seeds] 46 | print(min(locations)) 47 | -------------------------------------------------------------------------------- /2023/day05/day5_pt2.py: -------------------------------------------------------------------------------- 1 | def parse_seeds(line): 2 | seed_ranges = list(map(int, line[7:].strip().split())) 3 | return list(zip(seed_ranges[::2], seed_ranges[1::2])) 4 | 5 | 6 | def parse_maps(lines): 7 | maps = [] 8 | 9 | for line in lines: 10 | if line.strip() == "": 11 | continue 12 | 13 | if line.strip().endswith("map:"): 14 | maps.append([]) 15 | continue 16 | 17 | maps[-1].append(list(map(int, line.strip().split()))) 18 | 19 | for i, mp in enumerate(maps): 20 | maps[i] = sorted(mp, key=lambda x: x[1]) # source range start 21 | 22 | return maps 23 | 24 | 25 | def parse_file(file_name): 26 | with open(file_name, "r") as file: 27 | seeds = parse_seeds(file.readline()) 28 | maps = parse_maps(file.readlines()) 29 | return seeds, maps 30 | 31 | 32 | def intersect_segments(segment, rule_segments): 33 | rule_points = [] 34 | for rule_start, rule_length in rule_segments: 35 | rule_points.append(rule_start) 36 | rule_points.append(rule_start + rule_length) 37 | 38 | rule_points.append(segment[0]) 39 | rule_points.append(segment[0] + segment[1]) 40 | 41 | rule_points = sorted(list(set(rule_points))) 42 | 43 | result_segments = [] 44 | current_point = segment[0] 45 | segment_end = segment[0] + segment[1] 46 | for rule_point in rule_points: 47 | if rule_point > segment_end: 48 | break 49 | if rule_point > current_point and rule_point <= segment_end: 50 | result_segments.append((current_point, rule_point - current_point)) 51 | current_point = rule_point 52 | 53 | assert segment[1] == sum( 54 | l for _, l in result_segments 55 | ), f"segment: {segment}, rule_segments: {rule_segments}, result_segments: {result_segments}" 56 | 57 | return result_segments 58 | 59 | 60 | def find_destination_segments(segments, mapping): 61 | destination_segments = [] 62 | for segment_start, segment_length in segments: 63 | is_mapping_found = False 64 | for destination, source, length in mapping: 65 | if segment_start >= source and segment_start < source + length: 66 | destination_segments.append( 67 | [segment_start + destination - source, segment_length] 68 | ) 69 | is_mapping_found = True 70 | break 71 | if not is_mapping_found: 72 | destination_segments.append([segment_start, segment_length]) 73 | 74 | assert sum(l for _, l in segments) == sum( 75 | l for _, l in destination_segments 76 | ), f"segments: {segments}, mapping: {mapping}, destination_segments: {destination_segments}" 77 | return destination_segments 78 | 79 | 80 | def unify_segments(segments): 81 | segments = sorted(segments, key=lambda x: x[0]) 82 | 83 | unified_segments = [segments[0]] 84 | 85 | for segment in segments[1:]: 86 | if segment[0] < unified_segments[-1][0] + unified_segments[-1][1]: 87 | unified_segments[-1][1] = max( 88 | segment[0] + segment[1] - unified_segments[-1][0], segment[1] 89 | ) 90 | else: 91 | unified_segments.append(segment) 92 | 93 | return unified_segments 94 | 95 | 96 | print("Parsing the file...") 97 | current_segments, mappings = parse_file("input.txt") 98 | print("...parsed\n") 99 | 100 | print(current_segments) 101 | 102 | for i, mapping in enumerate(mappings): 103 | print(f"Processing mapping {i + 1} out of {len(mappings)}") 104 | print("current_segments:", current_segments) 105 | next_segments = [] 106 | for segment_start, segment_length in current_segments: 107 | print("segment:", segment_start, segment_length) 108 | 109 | intersected_segments = intersect_segments( 110 | [segment_start, segment_length], 111 | [[source, range_length] for _, source, range_length in mapping], 112 | ) 113 | print("intersected_segments:", intersected_segments) 114 | 115 | destination_segments = find_destination_segments(intersected_segments, mapping) 116 | print("destination_segments:", destination_segments) 117 | 118 | unified_segments = unify_segments(destination_segments) 119 | print("unified_segments:", unified_segments) 120 | 121 | next_segments.extend(unified_segments) 122 | 123 | current_segments = next_segments 124 | print("\t...done!\n") 125 | 126 | print(current_segments) 127 | print(min([start for start, _ in current_segments])) 128 | -------------------------------------------------------------------------------- /2023/day06/day6_pt1.py: -------------------------------------------------------------------------------- 1 | def parse_races(filename): 2 | with open(filename, "r") as file: 3 | lines = file.readlines() 4 | assert len(lines) == 2 5 | 6 | times = list(map(int, lines[0][5:].strip().split())) 7 | records = list(map(int, lines[1][9:].strip().split())) 8 | 9 | return list(zip(times, records)) 10 | 11 | 12 | def get_possible_distances(target_time): 13 | distances = [] 14 | for hold_time in range(target_time + 1): 15 | distances.append((target_time - hold_time) * hold_time) # speed = hold_time 16 | return distances 17 | 18 | 19 | races = parse_races("input.txt") 20 | total_ways_to_beat_record = 1 21 | 22 | for target_time, record_distance in races: 23 | possible_distances = get_possible_distances(target_time) 24 | n_ways_to_beat_record = sum( 25 | [int(distance > record_distance) for distance in possible_distances] 26 | ) 27 | total_ways_to_beat_record *= n_ways_to_beat_record 28 | 29 | print(total_ways_to_beat_record) 30 | -------------------------------------------------------------------------------- /2023/day06/day6_pt2.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | def parse_races(filename): 5 | with open(filename, "r") as file: 6 | lines = file.readlines() 7 | assert len(lines) == 2 8 | 9 | target_time = int("".join(lines[0][5:].strip().split())) 10 | record = int("".join(lines[1][9:].strip().split())) 11 | 12 | return target_time, record 13 | 14 | 15 | def get_quadratic_roots(a=1, b=1, c=1): 16 | return ( 17 | (-b - math.sqrt(b**2 - 4 * a * c)) / (2 * a), 18 | (-b + math.sqrt(b**2 - 4 * a * c)) / (2 * a), 19 | ) 20 | 21 | 22 | target_time, record = parse_races("input.txt") 23 | root1, root2 = get_quadratic_roots(b=-target_time, c=record) 24 | print(int(math.floor(root2)) + 1 - int(math.ceil(root1))) 25 | -------------------------------------------------------------------------------- /2023/day07/day7_pt1.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | card_labels = ["2", "3", "4", "5", "6", "7", "8", "9", "T", "J", "Q", "K", "A"] 4 | 5 | 6 | def parse_hand(line): 7 | data = line.strip().split() 8 | assert len(data) == 2 9 | 10 | return data[0], int(data[1]) 11 | 12 | 13 | def parse_input(file_name): 14 | hands = [] 15 | with open(file_name, "r") as file: 16 | for line in file.readlines(): 17 | hands.append(parse_hand(line)) 18 | return hands 19 | 20 | 21 | def get_hand_type(hand): 22 | card_counter = Counter(hand[0]) 23 | 24 | if len(card_counter) == 1: # five of a kind 25 | return 6 26 | 27 | if len(card_counter) == 5: # high card 28 | return 0 29 | 30 | if len(card_counter) == 2: 31 | if max(card_counter.values()) == 4: # four of a kind 32 | return 5 33 | 34 | if ( 35 | max(card_counter.values()) == 3 and min(card_counter.values()) == 2 36 | ): # full house 37 | return 4 38 | 39 | if max(card_counter.values()) == 3: # three of a kind 40 | return 3 41 | 42 | if ( 43 | card_counter.most_common()[0][1] == 2 and card_counter.most_common()[1][1] == 2 44 | ): # two pair 45 | return 2 46 | 47 | if ( 48 | card_counter.most_common()[0][1] == 2 and card_counter.most_common()[1][1] == 1 49 | ): # one pair 50 | return 1 51 | 52 | raise Exception(f"type not found for hand: {hand[0]}") 53 | 54 | 55 | def get_hand_code(hand): 56 | code = [] 57 | for symbol in hand[0]: 58 | code.append(hex(card_labels.index(symbol))) 59 | 60 | return "".join(code) 61 | 62 | 63 | hands = parse_input("input.txt") 64 | 65 | total_score = 0 66 | for i, hand in enumerate( 67 | sorted(hands, key=lambda hand: (get_hand_type(hand), get_hand_code(hand))) 68 | ): 69 | total_score += (i + 1) * hand[1] 70 | 71 | print(total_score) 72 | -------------------------------------------------------------------------------- /2023/day07/day7_pt2.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | card_labels = ["J", "2", "3", "4", "5", "6", "7", "8", "9", "T", "Q", "K", "A"] 4 | 5 | 6 | def parse_hand(line): 7 | data = line.strip().split() 8 | assert len(data) == 2 9 | 10 | return data[0], int(data[1]) 11 | 12 | 13 | def parse_input(file_name): 14 | hands = [] 15 | with open(file_name, "r") as file: 16 | for line in file.readlines(): 17 | hands.append(parse_hand(line)) 18 | return hands 19 | 20 | 21 | def get_hand_type(hand): 22 | card_counter = Counter(hand[0]) 23 | if "J" in hand[0]: 24 | j_number = card_counter.pop("J") 25 | else: 26 | j_number = 0 27 | 28 | # from best to worst: 29 | # five of a kind - 6 30 | # four of a kind - 5 31 | # full house - 4 32 | # three of a kind - 3 33 | # two pair - 2 34 | # one pair - 1 35 | # high card - 0 36 | 37 | if len(card_counter) <= 1: # five of a kind 38 | return 6 39 | 40 | if len(card_counter) == 5: # high card 41 | return 0 42 | 43 | if card_counter.most_common()[0][1] == 1: 44 | # j_number == 4 - then len(card_counter) == 1, already covered 45 | 46 | if j_number == 3: # four of a kind 47 | return 5 48 | 49 | if j_number == 2: # three of a kind 50 | return 3 51 | 52 | if j_number == 1: # one pair 53 | return 1 54 | 55 | # j_number == 0 - then len(card_counter) == 5, already covered 56 | 57 | if card_counter.most_common()[0][1] == 2: 58 | # j_number == 3 - then len(card_counter) == 1, already covered 59 | 60 | if j_number == 2: # four of a kind 61 | return 5 62 | 63 | if j_number == 1: 64 | if card_counter.most_common()[1][1] == 2: # full house 65 | return 4 66 | else: # three of a kind 67 | return 3 68 | 69 | if j_number == 0: 70 | if card_counter.most_common()[1][1] == 2: # two pair 71 | return 2 72 | else: # one pair 73 | return 1 74 | 75 | if card_counter.most_common()[0][1] == 3: 76 | # j_number == 2 - then len(card_counter) == 1, already covered 77 | 78 | if j_number == 1: # four of a kind 79 | return 5 80 | 81 | if j_number == 0: 82 | if card_counter.most_common()[1][1] == 2: # full house 83 | return 4 84 | else: # three of a kind 85 | return 3 86 | 87 | if card_counter.most_common()[0][1] == 4: # four of a kind 88 | # j_number == 1 - then len(card_counter) == 1, already covered 89 | return 5 90 | 91 | raise Exception(f"type not found for hand: {hand[0]}") 92 | 93 | 94 | def get_hand_code(hand): 95 | code = [] 96 | for symbol in hand[0]: 97 | code.append(hex(card_labels.index(symbol))) 98 | 99 | return "".join(code) 100 | 101 | 102 | hands = parse_input("input.txt") 103 | 104 | total_score = 0 105 | for i, hand in enumerate( 106 | sorted(hands, key=lambda hand: (get_hand_type(hand), get_hand_code(hand))) 107 | ): 108 | total_score += (i + 1) * hand[1] 109 | 110 | print(total_score) 111 | -------------------------------------------------------------------------------- /2023/day08/day8_pt1.py: -------------------------------------------------------------------------------- 1 | def parse_commands(line): 2 | return list(map(lambda x: 1 if x == "R" else 0, line.strip())) 3 | 4 | 5 | def parse_network(lines): 6 | network = dict() 7 | for line in lines: 8 | if len(line) <= 2: 9 | continue 10 | source, dest = line.split("=") 11 | source = source.strip() 12 | dest = dest.strip()[1:-1].split(", ") 13 | network[source] = dest 14 | 15 | return network 16 | 17 | 18 | def parse_input(file_name): 19 | with open(file_name, "r") as file: 20 | commands = parse_commands(file.readline()) 21 | network = parse_network(file.readlines()) 22 | return commands, network 23 | 24 | 25 | commands, network = parse_input("input.txt") 26 | current_node = "AAA" 27 | step_number = 0 28 | 29 | while current_node != "ZZZ": 30 | command = commands[step_number % len(commands)] 31 | current_node = network[current_node][command] 32 | step_number += 1 33 | 34 | print(step_number) 35 | -------------------------------------------------------------------------------- /2023/day08/day8_pt2.py: -------------------------------------------------------------------------------- 1 | def parse_commands(line): 2 | return list(map(lambda x: 1 if x == "R" else 0, line.strip())) 3 | 4 | 5 | def parse_network(lines): 6 | network = dict() 7 | for line in lines: 8 | if len(line) <= 2: 9 | continue 10 | source, dest = line.split("=") 11 | source = source.strip() 12 | dest = dest.strip()[1:-1].split(", ") 13 | network[source] = dest 14 | 15 | return network 16 | 17 | 18 | def parse_input(file_name): 19 | with open(file_name, "r") as file: 20 | commands = parse_commands(file.readline()) 21 | network = parse_network(file.readlines()) 22 | return commands, network 23 | 24 | 25 | def gcd_pair(a, b): 26 | while True: 27 | r = a % b 28 | if r == 0: 29 | break 30 | a = b 31 | b = r 32 | return b 33 | 34 | 35 | def lcm(numbers): 36 | current_number = numbers[0] 37 | for number in numbers[1:]: 38 | current_number = current_number * number / gcd_pair(current_number, number) 39 | return current_number 40 | 41 | 42 | commands, network = parse_input("input.txt") 43 | current_nodes = [node for node in network.keys() if node.endswith("A")] 44 | visited_nodes = [dict() for _ in range(len(current_nodes))] 45 | cycle_starts = [None] * len(current_nodes) 46 | cycle_lengths = [None] * len(current_nodes) 47 | z_steps = [[] for _ in range(len(current_nodes))] 48 | step_number = 0 49 | 50 | while True: 51 | command_index = step_number % len(commands) 52 | command = commands[command_index] 53 | current_nodes = [network[node][command] for node in current_nodes] 54 | step_number += 1 55 | 56 | for i, node in enumerate(current_nodes): 57 | if cycle_starts[i] is None and (node, command_index) in visited_nodes[i]: 58 | visited_step = visited_nodes[i][(node, command_index)] 59 | cycle_starts[i] = visited_step 60 | cycle_lengths[i] = step_number - visited_step 61 | else: 62 | visited_nodes[i][(node, command_index)] = step_number 63 | 64 | if node.endswith("Z"): 65 | z_steps[i].append(step_number) 66 | 67 | if all(start is not None for start in cycle_starts): 68 | break 69 | 70 | print(step_number) 71 | print(cycle_starts) 72 | print(cycle_lengths) 73 | print(z_steps) 74 | 75 | for z_step in z_steps: 76 | assert len(z_step) == 1 77 | 78 | print(lcm([z_step[0] for z_step in z_steps])) 79 | -------------------------------------------------------------------------------- /2023/day09/day9_pt1.py: -------------------------------------------------------------------------------- 1 | def parse_file(file_name): 2 | with open(file_name, "r") as file: 3 | return [list(map(int, line.strip().split())) for line in file.readlines()] 4 | 5 | 6 | def build_prediction_values(value_history): 7 | prediction_values = [] 8 | pyramid_last_layer = value_history 9 | 10 | while not all(value == 0 for value in pyramid_last_layer): 11 | prediction_values.append(pyramid_last_layer[-1]) 12 | pyramid_last_layer = [ 13 | value - prev_value 14 | for value, prev_value in zip( 15 | pyramid_last_layer[1:], pyramid_last_layer[:-1] 16 | ) 17 | ] 18 | 19 | return prediction_values 20 | 21 | 22 | def get_extrapolation(prediction_values): 23 | return prediction_values[0] + sum(prediction_values[1:]) 24 | 25 | 26 | values_histories = parse_file("input.txt") 27 | extrapolation_sum = 0 28 | 29 | for value_history in values_histories: 30 | prediction_values = build_prediction_values(value_history) 31 | extrapolation_sum += get_extrapolation(prediction_values) 32 | 33 | print(extrapolation_sum) 34 | -------------------------------------------------------------------------------- /2023/day09/day9_pt2.py: -------------------------------------------------------------------------------- 1 | def parse_file(file_name): 2 | with open(file_name, "r") as file: 3 | return [list(map(int, line.strip().split())) for line in file.readlines()] 4 | 5 | 6 | def build_prediction_values(value_history): 7 | prediction_values = [] 8 | pyramid_last_layer = value_history 9 | 10 | while not all(value == 0 for value in pyramid_last_layer): 11 | prediction_values.append(pyramid_last_layer[0]) 12 | pyramid_last_layer = [ 13 | value - prev_value 14 | for value, prev_value in zip( 15 | pyramid_last_layer[1:], pyramid_last_layer[:-1] 16 | ) 17 | ] 18 | 19 | return prediction_values 20 | 21 | 22 | def get_extrapolation(prediction_values): 23 | current_value = 0 24 | for value in prediction_values[::-1]: 25 | current_value = value - current_value 26 | return current_value 27 | 28 | 29 | values_histories = parse_file("input.txt") 30 | extrapolation_sum = 0 31 | 32 | for value_history in values_histories: 33 | prediction_values = build_prediction_values(value_history) 34 | extrapolation_value = get_extrapolation(prediction_values) 35 | extrapolation_sum += extrapolation_value 36 | 37 | print(extrapolation_sum) 38 | -------------------------------------------------------------------------------- /2023/day10/day10_pt1.py: -------------------------------------------------------------------------------- 1 | def parse_sketch(file_name): 2 | with open(file_name, "r") as file: 3 | return list(map(lambda x: x.strip(), file.readlines())) 4 | 5 | 6 | def find_start(sketch): 7 | for i, row in enumerate(sketch): 8 | for j, sym in enumerate(row): 9 | if sym == "S": 10 | return i, j 11 | 12 | 13 | # ["|", "-", "L", "J", "7", "F", "S"] 14 | tile_relationships = { 15 | "|": {(-1, 0), (1, 0)}, 16 | "-": {(0, 1), (0, -1)}, 17 | "L": {(-1, 0), (0, 1)}, 18 | "J": {(-1, 0), (0, -1)}, 19 | "7": {(1, 0), (0, -1)}, 20 | "F": {(1, 0), (0, 1)}, 21 | "S": {(-1, 0), (1, 0), (0, -1), (0, 1)}, 22 | ".": set(), 23 | } 24 | 25 | 26 | def revert_relationship(relationship): 27 | return tuple(-r for r in relationship) 28 | 29 | 30 | def find_next_tile(current_row, current_col, visited_tiles, sketch): 31 | adjacent_tiles = [(-1, 0), (1, 0), (0, -1), (0, 1)] 32 | adjacent_tiles = list( 33 | filter( 34 | lambda coords: current_row + coords[0] >= 0 35 | and current_row + coords[0] < len(sketch) 36 | and current_col + coords[1] >= 0 37 | and current_col + coords[1] < len(sketch[0]), 38 | adjacent_tiles, 39 | ) 40 | ) 41 | 42 | current_tile = sketch[current_row][current_col] 43 | next_tiles = [] 44 | for relationship in adjacent_tiles: 45 | next_row, next_col = ( 46 | current_row + relationship[0], 47 | current_col + relationship[1], 48 | ) 49 | next_tile = sketch[next_row][next_col] 50 | 51 | print(f"??? {current_tile} -> {next_tile}") 52 | print( 53 | f"from: {relationship}, {relationship in tile_relationships[current_tile]}" 54 | ) 55 | print( 56 | f"to: {revert_relationship(relationship)}, {revert_relationship(relationship) in tile_relationships[next_tile]}" 57 | ) 58 | print() 59 | 60 | if ( 61 | relationship in tile_relationships[current_tile] 62 | and revert_relationship(relationship) in tile_relationships[next_tile] 63 | ): 64 | next_tiles.append((next_row, next_col)) 65 | 66 | assert ( 67 | len(next_tiles) == 2 68 | ), f"found {len(next_tiles)} neighbour tiles for {(current_row, current_col)}: {next_tiles}" 69 | 70 | next_tiles = list(filter(lambda x: x not in visited_tiles, next_tiles)) 71 | 72 | assert (len(next_tiles) == 2 and current_tile == "S") or len(next_tiles) <= 1, ( 73 | f"Unexpected number of neighbour tiles for tile {(current_row, current_col)}: {len(next_tiles)}, {next_tiles}" 74 | + f"\nVisited tiles: {visited_tiles}" 75 | ) 76 | 77 | if len(next_tiles) == 0: # we reached the start 78 | return None, None 79 | return next_tiles[0] 80 | 81 | 82 | sketch = parse_sketch("input.txt") 83 | current_row, current_col = find_start(sketch) 84 | cycle_length = 0 85 | visited_tiles = set() 86 | visited_tiles.add((current_row, current_col)) 87 | 88 | while True: 89 | print(f"-------- STEP {cycle_length} ---------") 90 | print(f"current tile: {(current_row, current_col)}\n") 91 | current_row, current_col = find_next_tile( 92 | current_row, current_col, visited_tiles, sketch 93 | ) 94 | 95 | if current_row is None and current_col is None: 96 | break 97 | 98 | visited_tiles.add((current_row, current_col)) 99 | cycle_length += 1 100 | 101 | print((cycle_length + 1) // 2) 102 | -------------------------------------------------------------------------------- /2023/day11/day11_pt1_2.py: -------------------------------------------------------------------------------- 1 | EXPANTION_COEFF = 1000000 2 | 3 | 4 | def parse_universe(file_name): 5 | with open(file_name, "r") as file: 6 | return [line.strip() for line in file.readlines()] 7 | 8 | 9 | def generate_default_galaxy_coordinates(universe): 10 | coordinates = list() 11 | for i, row in enumerate(universe): 12 | for j, sym in enumerate(row): 13 | if sym == "#": 14 | coordinates.append([i, j]) 15 | return coordinates 16 | 17 | 18 | def adjust_coordinates(galaxies, axis=0): 19 | galaxies = sorted(galaxies, key=lambda x: x[axis]) 20 | empty_counter = 0 21 | prev_galaxy_coordinate = 0 22 | for i, galaxy in enumerate(galaxies): 23 | print(i, galaxy, prev_galaxy_coordinate, empty_counter) 24 | coordinate = galaxy[axis] 25 | if coordinate - prev_galaxy_coordinate > 1: 26 | empty_counter += (EXPANTION_COEFF - 1) * ( 27 | coordinate - prev_galaxy_coordinate - 1 28 | ) 29 | galaxies[i][axis] += empty_counter 30 | prev_galaxy_coordinate = coordinate 31 | return galaxies 32 | 33 | 34 | def get_pair_distances(galaxies): 35 | total_distance = 0 36 | for i, rhs_galaxy in enumerate(galaxies): 37 | for lhs_galaxy in galaxies[:i]: 38 | total_distance += abs(lhs_galaxy[0] - rhs_galaxy[0]) 39 | total_distance += abs(lhs_galaxy[1] - rhs_galaxy[1]) 40 | return total_distance 41 | 42 | 43 | universe = parse_universe("input.txt") 44 | galaxies = generate_default_galaxy_coordinates(universe) 45 | galaxies = adjust_coordinates(galaxies, axis=0) 46 | galaxies = adjust_coordinates(galaxies, axis=1) 47 | print(get_pair_distances(galaxies)) 48 | -------------------------------------------------------------------------------- /2023/day12/day12_pt1.py: -------------------------------------------------------------------------------- 1 | def parse_spring_area(file_name): 2 | areas = [] 3 | damaged_info = [] 4 | with open(file_name, "r") as file: 5 | for line in file.readlines(): 6 | area, damaged_groups = line.strip().split(" ") 7 | areas.append(area) 8 | damaged_groups = list(map(int, damaged_groups.split(","))) 9 | damaged_info.append(damaged_groups) 10 | return areas, damaged_info 11 | 12 | 13 | def count_options(current_area_pos, current_damaged_group, is_inside_group, inside_group_pos): 14 | global cache_dict, area_row, damaged_groups 15 | 16 | print(current_area_pos, current_damaged_group, is_inside_group, inside_group_pos) 17 | 18 | if current_area_pos == 0 and current_damaged_group == 0 and inside_group_pos == 0: 19 | return 1 20 | elif current_area_pos == 0: 21 | return 0 22 | 23 | if inside_group_pos < 0: 24 | return 0 25 | 26 | if current_damaged_group < 0: 27 | return 0 28 | 29 | if area_row[current_area_pos - 1] == ".": 30 | if is_inside_group and inside_group_pos != 0: 31 | return 0 32 | 33 | return count_options(current_area_pos - 1, current_damaged_group, False, 0) 34 | 35 | if area_row[current_area_pos - 1] == "#": 36 | if not is_inside_group: 37 | return count_options(current_area_pos - 1, current_damaged_group - 1, True, damaged_groups[current_damaged_group - 1]) 38 | else: 39 | return count_options(current_area_pos - 1, current_damaged_group, True, inside_group_pos - 1) 40 | 41 | if area_row[current_area_pos - 1] == "?": 42 | n_options = 0 43 | 44 | # like . 45 | if (is_inside_group and inside_group_pos == 0) or not is_inside_group: 46 | n_options += count_options(current_area_pos - 1, current_damaged_group, False, 0) 47 | 48 | # like # 49 | if is_inside_group and inside_group_pos > 0: 50 | n_options += count_options(current_area_pos - 1, current_damaged_group, True, inside_group_pos - 1) 51 | 52 | if not is_inside_group: 53 | n_options += count_options(current_area_pos - 1, current_damaged_group - 1, True, damaged_groups[current_damaged_group - 1]) 54 | 55 | return n_options 56 | 57 | 58 | spring_area, damaged_info = parse_spring_area("input.txt") 59 | total_combos = 0 60 | for area_row, damaged_groups in zip(spring_area, damaged_info): 61 | print(area_row, damaged_groups) 62 | print(len(area_row)) 63 | cache_dict = dict() 64 | row_options = count_options(len(area_row) - 1, len(damaged_groups) - 1, False, 0) 65 | total_combos += row_options 66 | print(f"row_options: {row_options}") 67 | print(total_combos) 68 | -------------------------------------------------------------------------------- /2023/day13/day13_pt1.py: -------------------------------------------------------------------------------- 1 | def parse_patterns(file_name): 2 | current_pattern = [] 3 | with open(file_name, "r") as file: 4 | for line in file.readlines(): 5 | if line.strip() == "": 6 | yield current_pattern 7 | current_pattern = [] 8 | else: 9 | current_pattern.append(line.strip()) 10 | yield current_pattern 11 | 12 | 13 | def find_col_symmetry(pattern): 14 | total_cols = len(pattern[0]) 15 | 16 | columns = [[pattern[i][j] for i in range(len(pattern))] for j in range(total_cols)] 17 | 18 | for symmetry_col in range(total_cols - 1): 19 | is_symmetrical = True 20 | for i in range(min(symmetry_col + 1, total_cols - symmetry_col - 1)): 21 | if columns[symmetry_col - i] != columns[symmetry_col + 1 + i]: 22 | is_symmetrical = False 23 | break 24 | 25 | if is_symmetrical: 26 | return symmetry_col 27 | 28 | return None 29 | 30 | 31 | def find_row_symmetry(pattern): 32 | total_rows = len(pattern) 33 | 34 | for symmetry_row in range(total_rows - 1): 35 | is_symmetrical = True 36 | for i in range(min(symmetry_row + 1, total_rows - symmetry_row - 1)): 37 | if pattern[symmetry_row - i] != pattern[symmetry_row + 1 + i]: 38 | is_symmetrical = False 39 | break 40 | 41 | if is_symmetrical: 42 | return symmetry_row 43 | 44 | return None 45 | 46 | 47 | patterns = parse_patterns("input.txt") 48 | sum_pattern_notes = 0 49 | 50 | for pattern in patterns: 51 | if find_col_symmetry(pattern) is not None: 52 | sum_pattern_notes += find_col_symmetry(pattern) + 1 53 | else: 54 | assert find_row_symmetry(pattern) is not None, "\n".join(pattern) 55 | sum_pattern_notes += 100 * (find_row_symmetry(pattern) + 1) 56 | 57 | print(sum_pattern_notes) 58 | -------------------------------------------------------------------------------- /2023/day13/day13_pt2.py: -------------------------------------------------------------------------------- 1 | def parse_patterns(file_name): 2 | current_pattern = [] 3 | with open(file_name, "r") as file: 4 | for line in file.readlines(): 5 | if line.strip() == "": 6 | yield current_pattern 7 | current_pattern = [] 8 | else: 9 | current_pattern.append(line.strip()) 10 | yield current_pattern 11 | 12 | 13 | def find_symmetry(arrays): 14 | n_arrays = len(arrays) 15 | 16 | for symmetry_i in range(n_arrays - 1): 17 | n_smudges = 0 18 | for i in range(min(symmetry_i + 1, n_arrays - symmetry_i - 1)): 19 | for lhs_sym, rhs_sym in zip( 20 | arrays[symmetry_i - i], arrays[symmetry_i + 1 + i] 21 | ): 22 | if lhs_sym != rhs_sym: 23 | n_smudges += 1 24 | 25 | if n_smudges == 1: 26 | return symmetry_i 27 | 28 | return None 29 | 30 | 31 | patterns = parse_patterns("input.txt") 32 | sum_pattern_notes = 0 33 | 34 | for pattern in patterns: 35 | columns = [ 36 | [pattern[i][j] for i in range(len(pattern))] for j in range(len(pattern[0])) 37 | ] 38 | if find_symmetry(columns) is not None: 39 | sum_pattern_notes += find_symmetry(columns) + 1 40 | else: 41 | assert find_symmetry(pattern) is not None, "\n".join(pattern) 42 | sum_pattern_notes += 100 * (find_symmetry(pattern) + 1) 43 | 44 | print(sum_pattern_notes) 45 | -------------------------------------------------------------------------------- /2023/day14/day14_pt1.py: -------------------------------------------------------------------------------- 1 | def parse_map(file_name): 2 | with open(file_name, "r") as file: 3 | return [line.strip() for line in file.readlines()] 4 | 5 | 6 | def get_column_load(map, column_num): 7 | total_rows = len(map) 8 | last_occupied_row = -1 9 | load = 0 10 | 11 | for row in range(total_rows): 12 | symbol = map[row][column_num] 13 | 14 | if symbol == ".": 15 | pass 16 | elif symbol == "O": 17 | last_occupied_row += 1 18 | load += total_rows - last_occupied_row 19 | else: # symbol == "#" 20 | last_occupied_row = row 21 | 22 | return load 23 | 24 | 25 | map = parse_map("input.txt") 26 | total_load = 0 27 | for column_num in range(len(map[0])): 28 | total_load += get_column_load(map, column_num) 29 | 30 | print(total_load) 31 | -------------------------------------------------------------------------------- /2023/day14/day14_pt2.py: -------------------------------------------------------------------------------- 1 | def parse_map(file_name): 2 | with open(file_name, "r") as file: 3 | return [list(line.strip()) for line in file.readlines()] 4 | 5 | 6 | def move_column_north(map, column_num): 7 | new_column = [] 8 | total_rows = len(map) 9 | last_occupied_row = -1 10 | load = 0 11 | 12 | for row in range(total_rows): 13 | symbol = map[row][column_num] 14 | 15 | if symbol == ".": 16 | pass 17 | elif symbol == "O": 18 | last_occupied_row += 1 19 | load += total_rows - last_occupied_row 20 | new_column.append("O") 21 | else: # symbol == "#" 22 | while len(new_column) < row: 23 | new_column.append(".") 24 | 25 | new_column.append("#") 26 | last_occupied_row = row 27 | 28 | while len(new_column) < total_rows: 29 | new_column.append(".") 30 | 31 | for row_num in range(total_rows): 32 | map[row_num][column_num] = new_column[row_num] 33 | 34 | return load 35 | 36 | 37 | def move_column_south(map, column_num): 38 | new_column = [] 39 | total_rows = len(map) 40 | last_occupied_row = total_rows 41 | load = 0 42 | 43 | for row_num in range(total_rows - 1, -1, -1): 44 | symbol = map[row_num][column_num] 45 | 46 | if symbol == ".": 47 | pass 48 | elif symbol == "O": 49 | last_occupied_row -= 1 50 | load += total_rows - last_occupied_row 51 | new_column.append("O") 52 | else: # symbol == "#" 53 | while len(new_column) < total_rows - row_num - 1: 54 | new_column.append(".") 55 | 56 | new_column.append("#") 57 | last_occupied_row = row_num 58 | 59 | while len(new_column) < total_rows: 60 | new_column.append(".") 61 | 62 | new_column = new_column[::-1] 63 | 64 | for row_num in range(total_rows): 65 | map[row_num][column_num] = new_column[row_num] 66 | 67 | return load 68 | 69 | 70 | def move_row_west(map, row_num): 71 | new_row = [] 72 | total_cols = len(map[0]) 73 | last_occupied_col = -1 74 | load = 0 75 | 76 | for col_num in range(total_cols): 77 | symbol = map[row_num][col_num] 78 | 79 | if symbol == ".": 80 | pass 81 | elif symbol == "O": 82 | last_occupied_col += 1 83 | load += total_cols - last_occupied_col 84 | new_row.append("O") 85 | else: # symbol == "#" 86 | while len(new_row) < col_num: 87 | new_row.append(".") 88 | 89 | new_row.append("#") 90 | last_occupied_col = col_num 91 | 92 | while len(new_row) < total_cols: 93 | new_row.append(".") 94 | 95 | for col_num in range(total_cols): 96 | map[row_num][col_num] = new_row[col_num] 97 | 98 | return load 99 | 100 | 101 | def move_row_east(map, row_num): 102 | new_row = [] 103 | total_cols = len(map[0]) 104 | last_occupied_col = total_cols 105 | load = 0 106 | 107 | for col_num in range(total_cols - 1, -1, -1): 108 | symbol = map[row_num][col_num] 109 | 110 | if symbol == ".": 111 | pass 112 | elif symbol == "O": 113 | last_occupied_col -= 1 114 | load += total_cols - last_occupied_col 115 | new_row.append("O") 116 | else: # symbol == "#" 117 | while len(new_row) < total_cols - col_num - 1: 118 | new_row.append(".") 119 | 120 | new_row.append("#") 121 | last_occupied_col = col_num 122 | 123 | while len(new_row) < total_cols: 124 | new_row.append(".") 125 | 126 | new_row = new_row[::-1] 127 | 128 | for col_num in range(total_cols): 129 | map[row_num][col_num] = new_row[col_num] 130 | 131 | return load 132 | 133 | 134 | def calculate_load(map): 135 | total_load = 0 136 | for row in range(len(map)): 137 | load = len(map) - row 138 | n_boulders = sum(int(sym == "O") for sym in map[row]) 139 | total_load += load * n_boulders 140 | return total_load 141 | 142 | 143 | def print_map(map): 144 | for row in map: 145 | print("".join(row)) 146 | print() 147 | 148 | 149 | def rotate_cycle(map): 150 | for col in range(len(map[0])): 151 | move_column_north(map, col) 152 | 153 | for row in range(len(map[0])): 154 | move_row_west(map, row) 155 | 156 | for col in range(len(map)): 157 | move_column_south(map, col) 158 | 159 | for row in range(len(map[0])): 160 | move_row_east(map, row) 161 | 162 | 163 | def collapse_map(map): 164 | return "".join("".join(row) for row in map) 165 | 166 | 167 | map = parse_map("input.txt") 168 | map_last_occurence = dict() 169 | total_steps = 1000000000 170 | current_step = 1 171 | has_skipped = False 172 | 173 | while current_step < total_steps: 174 | print(f"current step: {current_step}") 175 | rotate_cycle(map) 176 | 177 | if collapse_map(map) not in map_last_occurence: 178 | map_last_occurence[collapse_map(map)] = current_step 179 | current_step += 1 180 | elif has_skipped: 181 | current_step += 1 182 | continue 183 | else: 184 | cycle_length = current_step - map_last_occurence[collapse_map(map)] 185 | print(f"cycle length: {cycle_length}") 186 | current_step += (total_steps - current_step) // cycle_length * cycle_length 187 | has_skipped = True 188 | 189 | print(calculate_load(map)) 190 | -------------------------------------------------------------------------------- /2023/day15/day15_pt1.py: -------------------------------------------------------------------------------- 1 | def parse_sequence(file_name): 2 | with open(file_name, "r") as file: 3 | return file.readline().strip().split(",") 4 | 5 | 6 | def hash(string): 7 | current_value = 0 8 | for symbol in string: 9 | current_value += ord(symbol) 10 | current_value = (current_value * 17) % 256 11 | return current_value 12 | 13 | 14 | sequence = parse_sequence("input.txt") 15 | print(sum(hash(string) for string in sequence)) 16 | -------------------------------------------------------------------------------- /2023/day15/day15_pt2.py: -------------------------------------------------------------------------------- 1 | class Lens: 2 | def __init__(self, label, focal_length=None): 3 | self.label = label 4 | self.focal_length = focal_length 5 | self.box = self._hash(self.label) 6 | 7 | def _hash(self, label): 8 | current_value = 0 9 | for symbol in label: 10 | current_value += ord(symbol) 11 | current_value = (current_value * 17) % 256 12 | return current_value 13 | 14 | 15 | class BoxSet: 16 | def __init__(self, n=256): 17 | self.boxes = [[] for _ in range(n)] 18 | 19 | def add_lens(self, lens): 20 | for i, box_lens in enumerate(self.boxes[lens.box]): 21 | if box_lens.label == lens.label: 22 | self.boxes[lens.box][i].focal_length = lens.focal_length 23 | return 24 | 25 | self.boxes[lens.box].append(lens) 26 | 27 | def remove_lens(self, lens): 28 | box_number, label = lens.box, lens.label 29 | 30 | for i, box_lens in enumerate(self.boxes[box_number]): 31 | if box_lens.label == label: 32 | self.boxes[box_number].pop(i) 33 | return 34 | 35 | def get_config(self): 36 | config_number = 0 37 | for box_number, box in enumerate(self.boxes): 38 | for lense_number, lense in enumerate(box): 39 | config_number += ( 40 | (1 + box_number) * (1 + lense_number) * lense.focal_length 41 | ) 42 | return config_number 43 | 44 | 45 | def parse_sequence(file_name): 46 | with open(file_name, "r") as file: 47 | sequence = file.readline().strip().split(",") 48 | 49 | commands = [] 50 | for command in sequence: 51 | if "=" in command: 52 | label, focal_length = command.split("=") 53 | commands.append(("=", Lens(label, int(focal_length)))) 54 | else: 55 | label = command[:-1] 56 | commands.append(("-", Lens(label))) 57 | return commands 58 | 59 | 60 | sequence = parse_sequence("input.txt") 61 | box_set = BoxSet() 62 | 63 | for command, lens in sequence: 64 | if command == "=": 65 | box_set.add_lens(lens) 66 | else: 67 | box_set.remove_lens(lens) 68 | 69 | print(box_set.get_config()) 70 | -------------------------------------------------------------------------------- /2023/day16/day16_pt1.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict, deque 2 | 3 | 4 | def parse_contraption(file_name): 5 | with open(file_name, "r") as file: 6 | return [line.strip() for line in file.readlines()] 7 | 8 | 9 | def get_next_moves(row, col, incoming_direction, contraption): 10 | total_rows, total_cols = len(contraption), len(contraption[0]) 11 | current_symbol = contraption[row][col] 12 | next_moves = [] 13 | 14 | if current_symbol == ".": 15 | if incoming_direction == "r": 16 | next_moves = [(row, col + 1, incoming_direction)] 17 | 18 | if incoming_direction == "l": 19 | next_moves = [(row, col - 1, incoming_direction)] 20 | 21 | if incoming_direction == "d": 22 | next_moves = [(row + 1, col, incoming_direction)] 23 | 24 | if incoming_direction == "u": 25 | next_moves = [(row - 1, col, incoming_direction)] 26 | 27 | if current_symbol == "-": 28 | if incoming_direction == "r": 29 | next_moves = [(row, col + 1, incoming_direction)] 30 | 31 | if incoming_direction == "l": 32 | next_moves = [(row, col - 1, incoming_direction)] 33 | 34 | if incoming_direction in ("u", "d"): 35 | next_moves = [(row, col + 1, "r"), (row, col - 1, "l")] 36 | 37 | if current_symbol == "|": 38 | if incoming_direction == "d": 39 | next_moves = [(row + 1, col, incoming_direction)] 40 | 41 | if incoming_direction == "u": 42 | next_moves = [(row - 1, col, incoming_direction)] 43 | 44 | if incoming_direction in ("r", "l"): 45 | next_moves = [(row + 1, col, "d"), (row - 1, col, "u")] 46 | 47 | if current_symbol == "/": 48 | if incoming_direction == "r": 49 | next_moves = [(row - 1, col, "u")] 50 | 51 | if incoming_direction == "l": 52 | next_moves = [(row + 1, col, "d")] 53 | 54 | if incoming_direction == "u": 55 | next_moves = [(row, col + 1, "r")] 56 | 57 | if incoming_direction == "d": 58 | next_moves = [(row, col - 1, "l")] 59 | 60 | if current_symbol == "\\": 61 | if incoming_direction == "r": 62 | next_moves = [(row + 1, col, "d")] 63 | 64 | if incoming_direction == "l": 65 | next_moves = [(row - 1, col, "u")] 66 | 67 | if incoming_direction == "u": 68 | next_moves = [(row, col - 1, "l")] 69 | 70 | if incoming_direction == "d": 71 | next_moves = [(row, col + 1, "r")] 72 | 73 | next_moves = list( 74 | filter( 75 | lambda x: x[0] >= 0 76 | and x[0] < total_rows 77 | and x[1] >= 0 78 | and x[1] < total_cols, 79 | next_moves, 80 | ) 81 | ) 82 | 83 | return next_moves 84 | 85 | 86 | contraption = parse_contraption("input.txt") 87 | visited_tiles = defaultdict(set) 88 | visited_tiles[(0, 0)].add("r") # direction of incoming light 89 | 90 | queue = deque() 91 | queue.append((0, 0, "r")) 92 | 93 | while len(queue) > 0: 94 | current_row, current_col, current_direction = queue.popleft() 95 | next_moves = get_next_moves( 96 | current_row, current_col, current_direction, contraption 97 | ) 98 | 99 | for next_row, next_col, next_direction in next_moves: 100 | if ((next_row, next_col) not in visited_tiles) or ( 101 | next_direction not in visited_tiles[(next_row, next_col)] 102 | ): 103 | queue.append((next_row, next_col, next_direction)) 104 | visited_tiles[(next_row, next_col)].add(next_direction) 105 | 106 | print(len(visited_tiles)) 107 | -------------------------------------------------------------------------------- /2023/day16/day16_pt2.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict, deque 2 | 3 | 4 | def parse_contraption(file_name): 5 | with open(file_name, "r") as file: 6 | return [line.strip() for line in file.readlines()] 7 | 8 | 9 | def get_next_moves(row, col, incoming_direction, contraption): 10 | total_rows, total_cols = len(contraption), len(contraption[0]) 11 | current_symbol = contraption[row][col] 12 | next_moves = [] 13 | 14 | if current_symbol == ".": 15 | if incoming_direction == "r": 16 | next_moves = [(row, col + 1, incoming_direction)] 17 | 18 | if incoming_direction == "l": 19 | next_moves = [(row, col - 1, incoming_direction)] 20 | 21 | if incoming_direction == "d": 22 | next_moves = [(row + 1, col, incoming_direction)] 23 | 24 | if incoming_direction == "u": 25 | next_moves = [(row - 1, col, incoming_direction)] 26 | 27 | if current_symbol == "-": 28 | if incoming_direction == "r": 29 | next_moves = [(row, col + 1, incoming_direction)] 30 | 31 | if incoming_direction == "l": 32 | next_moves = [(row, col - 1, incoming_direction)] 33 | 34 | if incoming_direction in ("u", "d"): 35 | next_moves = [(row, col + 1, "r"), (row, col - 1, "l")] 36 | 37 | if current_symbol == "|": 38 | if incoming_direction == "d": 39 | next_moves = [(row + 1, col, incoming_direction)] 40 | 41 | if incoming_direction == "u": 42 | next_moves = [(row - 1, col, incoming_direction)] 43 | 44 | if incoming_direction in ("r", "l"): 45 | next_moves = [(row + 1, col, "d"), (row - 1, col, "u")] 46 | 47 | if current_symbol == "/": 48 | if incoming_direction == "r": 49 | next_moves = [(row - 1, col, "u")] 50 | 51 | if incoming_direction == "l": 52 | next_moves = [(row + 1, col, "d")] 53 | 54 | if incoming_direction == "u": 55 | next_moves = [(row, col + 1, "r")] 56 | 57 | if incoming_direction == "d": 58 | next_moves = [(row, col - 1, "l")] 59 | 60 | if current_symbol == "\\": 61 | if incoming_direction == "r": 62 | next_moves = [(row + 1, col, "d")] 63 | 64 | if incoming_direction == "l": 65 | next_moves = [(row - 1, col, "u")] 66 | 67 | if incoming_direction == "u": 68 | next_moves = [(row, col - 1, "l")] 69 | 70 | if incoming_direction == "d": 71 | next_moves = [(row, col + 1, "r")] 72 | 73 | next_moves = list( 74 | filter( 75 | lambda x: x[0] >= 0 76 | and x[0] < total_rows 77 | and x[1] >= 0 78 | and x[1] < total_cols, 79 | next_moves, 80 | ) 81 | ) 82 | 83 | return next_moves 84 | 85 | 86 | def get_all_start_positions(total_rows, total_cols): 87 | start_positions = [] 88 | for row in range(total_rows): 89 | start_positions.extend([(row, 0, "r"), (row, total_cols - 1, "l")]) 90 | for col in range(total_cols): 91 | start_positions.extend([(0, col, "d"), (total_rows - 1, col, "u")]) 92 | return start_positions 93 | 94 | 95 | contraption = parse_contraption("input.txt") 96 | max_coverage = 0 97 | 98 | start_positions = get_all_start_positions(len(contraption), len(contraption[0])) 99 | 100 | for row, col, direction in start_positions: 101 | visited_tiles = defaultdict(set) 102 | visited_tiles[(row, col)].add(direction) # direction of incoming light 103 | 104 | queue = deque() 105 | queue.append((row, col, direction)) 106 | 107 | while len(queue) > 0: 108 | current_row, current_col, current_direction = queue.popleft() 109 | next_moves = get_next_moves( 110 | current_row, current_col, current_direction, contraption 111 | ) 112 | 113 | for next_row, next_col, next_direction in next_moves: 114 | if ((next_row, next_col) not in visited_tiles) or ( 115 | next_direction not in visited_tiles[(next_row, next_col)] 116 | ): 117 | queue.append((next_row, next_col, next_direction)) 118 | visited_tiles[(next_row, next_col)].add(next_direction) 119 | 120 | max_coverage = max(max_coverage, len(visited_tiles)) 121 | 122 | print(max_coverage) 123 | -------------------------------------------------------------------------------- /2023/day17/day17_pt1.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mskozlova/advent_of_code/1025d79f7b77cd7ab84fab1cd88bce368a509497/2023/day17/day17_pt1.py -------------------------------------------------------------------------------- /2023/day18/day18_pt1.py: -------------------------------------------------------------------------------- 1 | direction_mapping = { 2 | "R": "-", 3 | "L": "-", 4 | "U": "|", 5 | "D": "|", 6 | "UR": "F", 7 | "LD": "F", 8 | "UL": "7", 9 | "RD": "7", 10 | "DL": "J", 11 | "RU": "J", 12 | "DR": "L", 13 | "LU": "L", 14 | } 15 | 16 | 17 | def parse_trench_commands(file_name): 18 | commands = [] 19 | with open(file_name, "r") as file: 20 | for line in file.readlines(): 21 | direction, duration, color = line.strip().split() 22 | duration = int(duration) 23 | color = color[1:-1] 24 | commands.append((direction, duration, color)) 25 | return commands 26 | 27 | 28 | def build_trench(commands): 29 | field_size = len(commands) 30 | field = [[] for _ in range(2 * field_size + 1)] 31 | 32 | row, col = field_size + 1, field_size + 1 33 | 34 | for i, (direction, duration, _) in enumerate(commands): 35 | for step in range(duration): 36 | if direction == "U": 37 | row -= 1 38 | elif direction == "D": 39 | row += 1 40 | elif direction == "R": 41 | col += 1 42 | else: 43 | col -= 1 44 | 45 | if step == duration - 1: 46 | next_direction = commands[(i + 1) % (len(commands))][0] 47 | if direction != next_direction: 48 | direction += next_direction 49 | 50 | field[row].append((col, direction)) 51 | 52 | field = [sorted(row) for row in filter(lambda row: len(row) > 0, field)] 53 | min_col = min(min(map(lambda x: x[0], row)) for row in field) 54 | return [[(x - min_col, direction_mapping[sym]) for x, sym in row] for row in field] 55 | 56 | 57 | def generate_row(row): 58 | new_row = [] 59 | for i, (coord, sym) in enumerate(row): 60 | if i == 0: 61 | new_row.append(sym) 62 | else: 63 | if row[i - 1][0] < coord - 1: 64 | new_row.extend(["."] * (coord - 1 - row[i - 1][0])) 65 | new_row.append(sym) 66 | return "".join(new_row) 67 | 68 | 69 | def calculate_inside_area(row): 70 | total_area = 0 71 | inside = False 72 | group_start = None 73 | 74 | for sym in row: 75 | if sym != ".": 76 | total_area += 1 77 | 78 | if sym == "|": 79 | inside = not inside 80 | elif sym in ("F", "L"): 81 | group_start = sym 82 | elif sym in ("J", "7"): 83 | assert group_start is not None 84 | if (group_start == "F" and sym == "J") or ( 85 | group_start == "L" and sym == "7" 86 | ): 87 | inside = not inside 88 | group_start = None 89 | elif sym == ".": 90 | if inside: 91 | total_area += 1 92 | 93 | return total_area 94 | 95 | 96 | commands = parse_trench_commands("input.txt") 97 | field = build_trench(commands) 98 | inside_area = 0 99 | for row in field: 100 | inside_area += calculate_inside_area(generate_row(row)) 101 | 102 | print(inside_area) 103 | -------------------------------------------------------------------------------- /2023/day18/day18_pt2.py: -------------------------------------------------------------------------------- 1 | direction_mapping = { 2 | "R": "-", 3 | "L": "-", 4 | "U": "|", 5 | "D": "|", 6 | "UR": "F", 7 | "LD": "F", 8 | "UL": "7", 9 | "RD": "7", 10 | "DL": "J", 11 | "RU": "J", 12 | "DR": "L", 13 | "LU": "L", 14 | } 15 | 16 | 17 | def parse_trench_commands(file_name): 18 | commands = [] 19 | with open(file_name, "r") as file: 20 | for line in file.readlines(): 21 | _, _, hex_code = line.strip().split() 22 | duration_code, direction = hex_code[2:-2], hex_code[-2] 23 | duration = int(duration_code, 16) 24 | commands.append((direction, duration)) 25 | return commands 26 | 27 | 28 | def build_trench(commands): 29 | field_size = sum(command[1] for command in commands) 30 | print(f"Field size: {field_size}") 31 | field = [[] for _ in range(2 * field_size + 1)] 32 | 33 | row, col = field_size + 1, field_size + 1 34 | 35 | for i, (direction, duration) in enumerate(commands): 36 | for step in range(duration): 37 | if direction == "U": 38 | row -= 1 39 | elif direction == "D": 40 | row += 1 41 | elif direction == "R": 42 | col += 1 43 | else: 44 | col -= 1 45 | 46 | if step == duration - 1: 47 | next_direction = commands[(i + 1) % (len(commands))][0] 48 | if direction != next_direction: 49 | direction += next_direction 50 | 51 | field[row].append((col, direction)) 52 | 53 | field = [sorted(row) for row in filter(lambda row: len(row) > 0, field)] 54 | min_col = min(min(map(lambda x: x[0], row)) for row in field) 55 | return [[(x - min_col, direction_mapping[sym]) for x, sym in row] for row in field] 56 | 57 | 58 | def generate_row(row): 59 | new_row = [] 60 | for i, (coord, sym) in enumerate(row): 61 | if i == 0: 62 | new_row.append(sym) 63 | else: 64 | if row[i - 1][0] < coord - 1: 65 | new_row.extend(["."] * (coord - 1 - row[i - 1][0])) 66 | new_row.append(sym) 67 | return "".join(new_row) 68 | 69 | 70 | def calculate_inside_area(row): 71 | total_area = 0 72 | inside = False 73 | group_start = None 74 | 75 | for sym in row: 76 | if sym != ".": 77 | total_area += 1 78 | 79 | if sym == "|": 80 | inside = not inside 81 | elif sym in ("F", "L"): 82 | group_start = sym 83 | elif sym in ("J", "7"): 84 | assert group_start is not None 85 | if (group_start == "F" and sym == "J") or ( 86 | group_start == "L" and sym == "7" 87 | ): 88 | inside = not inside 89 | group_start = None 90 | elif sym == ".": 91 | if inside: 92 | total_area += 1 93 | 94 | return total_area 95 | 96 | 97 | commands = parse_trench_commands("input.txt") 98 | field = build_trench(commands) 99 | 100 | inside_area = 0 101 | for row in field: 102 | print(row) 103 | # inside_area += calculate_inside_area(generate_row(row)) 104 | 105 | # print(inside_area) 106 | -------------------------------------------------------------------------------- /2023/day20/day20_pt1.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict, deque 2 | 3 | 4 | class Node: 5 | def __init__(self, id, node_type, nodes_to): 6 | self.id = id 7 | self.node_type = node_type 8 | self.nodes_to = nodes_to 9 | self.nodes_from = None 10 | 11 | if node_type == "flip": 12 | self.active = False 13 | 14 | def add_nodes_from(self, nodes): 15 | self.nodes_from = nodes 16 | 17 | if self.node_type == "conj": 18 | self.states = dict(zip(self.nodes_from, ["low"] * len(self.nodes_from))) 19 | 20 | def process_signal(self, signal="low", signal_from=None): 21 | assert self.nodes_from is not None, f"nodes_from is None for node id {self.id}" 22 | 23 | if self.node_type == "flip": 24 | if signal == "high": 25 | return [] 26 | self.active = not self.active 27 | output = "high" if self.active else "low" 28 | 29 | if self.node_type == "conj": 30 | assert signal_from is not None 31 | self.states[signal_from] = signal 32 | 33 | output = ( 34 | "low" 35 | if all(map(lambda x: x == "high", self.states.values())) 36 | else "high" 37 | ) 38 | 39 | if self.node_type == "broadcaster": 40 | output = signal 41 | 42 | return list( 43 | zip( 44 | self.nodes_to, 45 | [output] * len(self.nodes_to), 46 | [self.id] * len(self.nodes_to), 47 | ) 48 | ) 49 | 50 | 51 | def parse_commands(input_file): 52 | nodes = dict() 53 | 54 | with open(input_file, "r") as file: 55 | for line in file.readlines(): 56 | node_from, nodes_to = line.strip().split(" -> ") 57 | nodes_to = nodes_to.split(", ") 58 | 59 | if node_from == "broadcaster": 60 | nodes[node_from] = Node(node_from, node_from, nodes_to) 61 | elif node_from.startswith("%"): 62 | node_from = node_from[1:] 63 | nodes[node_from] = Node(node_from, "flip", nodes_to) 64 | elif node_from.startswith("&"): 65 | node_from = node_from[1:] 66 | nodes[node_from] = Node(node_from, "conj", nodes_to) 67 | else: 68 | print(f"Unknown node type: {node_from}") 69 | 70 | return nodes 71 | 72 | 73 | def init_conjunctions(nodes): 74 | inputs = defaultdict(set) 75 | for node in nodes.values(): 76 | for next_node in node.nodes_to: 77 | inputs[next_node].add(node.id) 78 | 79 | for node in nodes.values(): 80 | node.add_nodes_from(inputs[node.id]) 81 | 82 | 83 | def run_commands(start_node, input_signal, nodes): 84 | low_signals, high_signals = 0, 0 85 | queue = deque() 86 | queue.append((start_node, input_signal, None)) 87 | 88 | while len(queue) > 0: 89 | node_id, input_signal, signal_from = queue.popleft() 90 | 91 | if node_id in nodes: 92 | next_commands = nodes[node_id].process_signal(input_signal, signal_from) 93 | queue.extend(next_commands) 94 | 95 | if input_signal == "low": 96 | low_signals += 1 97 | else: 98 | high_signals += 1 99 | 100 | return low_signals, high_signals 101 | 102 | 103 | nodes = parse_commands("input.txt") 104 | init_conjunctions(nodes) 105 | 106 | total_low_signals, total_high_signals = 0, 0 107 | 108 | for _ in range(1000): 109 | low_signals, high_signals = run_commands("broadcaster", "low", nodes) 110 | total_low_signals += low_signals 111 | total_high_signals += high_signals 112 | 113 | print(total_low_signals, total_high_signals, total_low_signals * total_high_signals) 114 | -------------------------------------------------------------------------------- /2023/day21/day21_pt1.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | TARGET_N_STEPS = 64 5 | 6 | 7 | def parse_field(file_name): 8 | with open(file_name, "r") as file: 9 | return [line.strip() for line in file.readlines()] 10 | 11 | 12 | def get_start_coords(field): 13 | for i, row in enumerate(field): 14 | for j, sym in enumerate(row): 15 | if sym == "S": 16 | return i, j 17 | 18 | 19 | def get_available_steps(row, col, field): 20 | steps = [ 21 | (row + 1, col), 22 | (row - 1, col), 23 | (row, col + 1), 24 | (row, col - 1), 25 | ] 26 | 27 | steps = list(filter( 28 | lambda c: c[0] >= 0 and c[0] < len(field) and c[1] >= 0 and c[1] < len(field[0]) and field[c[0]][c[1]] == ".", 29 | steps 30 | )) 31 | 32 | return steps 33 | 34 | 35 | def count_gardens(start_row, start_col, field): 36 | visited = set() 37 | visited.add((start_row, start_col)) 38 | 39 | queue = deque() 40 | queue.append((start_row, start_col, 0)) 41 | 42 | n_reached_gardens = 0 43 | 44 | while len(queue) > 0: 45 | row, col, level = queue.popleft() 46 | 47 | if level % 2 == TARGET_N_STEPS % 2: 48 | n_reached_gardens += 1 49 | 50 | if level == TARGET_N_STEPS: 51 | continue 52 | 53 | next_steps = get_available_steps(row, col, field) 54 | 55 | for next_row, next_col in next_steps: 56 | if (next_row, next_col) not in visited: 57 | visited.add((next_row, next_col)) 58 | queue.append((next_row, next_col, level + 1)) 59 | 60 | return n_reached_gardens 61 | 62 | 63 | field = parse_field("input.txt") 64 | start_row, start_col = get_start_coords(field) 65 | n_accessible_gardens = count_gardens(start_row, start_col, field) 66 | print(n_accessible_gardens) -------------------------------------------------------------------------------- /2023/day21/day21_pt2.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | TARGET_N_STEPS = 6 5 | 6 | 7 | def parse_field(file_name): 8 | with open(file_name, "r") as file: 9 | return [line.strip() for line in file.readlines()] 10 | 11 | 12 | def get_start_coords(field): 13 | for i, row in enumerate(field): 14 | for j, sym in enumerate(row): 15 | if sym == "S": 16 | return i, j 17 | 18 | 19 | def get_available_steps(row, col, field): 20 | steps = [ 21 | (row + 1, col), 22 | (row - 1, col), 23 | (row, col + 1), 24 | (row, col - 1), 25 | ] 26 | 27 | steps = list(filter( 28 | lambda c: c[0] >= 0 and c[0] < len(field) and c[1] >= 0 and c[1] < len(field[0]) and field[c[0]][c[1]] == ".", 29 | steps 30 | )) 31 | 32 | return steps 33 | 34 | 35 | def count_gardens(start_row, start_col, field): 36 | visited = set() 37 | visited.add((start_row, start_col)) 38 | 39 | queue = deque() 40 | queue.append((start_row, start_col, 0)) 41 | 42 | n_reached_gardens = 0 43 | 44 | while len(queue) > 0: 45 | row, col, level = queue.popleft() 46 | 47 | if level % 2 == TARGET_N_STEPS % 2: 48 | n_reached_gardens += 1 49 | 50 | if level == TARGET_N_STEPS: 51 | continue 52 | 53 | next_steps = get_available_steps(row, col, field) 54 | 55 | for next_row, next_col in next_steps: 56 | if (next_row, next_col) not in visited: 57 | visited.add((next_row, next_col)) 58 | queue.append((next_row, next_col, level + 1)) 59 | 60 | return n_reached_gardens 61 | 62 | 63 | field = parse_field("input.txt") 64 | start_row, start_col = get_start_coords(field) 65 | n_accessible_gardens = count_gardens(start_row, start_col, field) 66 | print(n_accessible_gardens) -------------------------------------------------------------------------------- /2024/day01_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "os" 7 | "slices" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | func main() { 13 | total_diff := 0 14 | input, _ := os.ReadFile("2024/day01_pt1/input.txt") 15 | left_list := make([]int, 0, 0) 16 | right_list := make([]int, 0, 0) 17 | 18 | for _, line := range strings.Split(string(input), "\n") { 19 | elements := strings.Fields(line) 20 | left_element, _ := strconv.Atoi(elements[0]) 21 | right_element, _ := strconv.Atoi(elements[1]) 22 | left_list = append(left_list, left_element) 23 | right_list = append(right_list, right_element) 24 | } 25 | 26 | slices.Sort(left_list) 27 | slices.Sort(right_list) 28 | 29 | for i := range len(left_list) { 30 | current_diff := int(math.Abs(float64(left_list[i] - right_list[i]))) 31 | total_diff += current_diff 32 | } 33 | 34 | fmt.Printf("%d", total_diff) 35 | } 36 | -------------------------------------------------------------------------------- /2024/day01_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | func updateCounter(counter *map[int]int, id int) { 11 | cnt, ok := (*counter)[id] 12 | if ok { 13 | (*counter)[id] = cnt + 1 14 | } else { 15 | (*counter)[id] = 1 16 | } 17 | } 18 | 19 | func getValue(counter *map[int]int, id int) int { 20 | cnt, ok := (*counter)[id] 21 | if ok { 22 | return cnt 23 | } 24 | return 0 25 | } 26 | 27 | func main() { 28 | similarity_score := 0 29 | input, _ := os.ReadFile("2024/day01_pt2/input.txt") 30 | left_ids := make(map[int]int) 31 | right_ids := make(map[int]int) 32 | 33 | for _, line := range strings.Split(string(input), "\n") { 34 | elements := strings.Fields(line) 35 | left_element, _ := strconv.Atoi(elements[0]) 36 | right_element, _ := strconv.Atoi(elements[1]) 37 | 38 | updateCounter(&left_ids, left_element) 39 | updateCounter(&right_ids, right_element) 40 | } 41 | 42 | for id, cnt := range left_ids { 43 | similarity_score += id * cnt * getValue(&right_ids, id) 44 | } 45 | 46 | fmt.Printf("%d", similarity_score) 47 | } 48 | -------------------------------------------------------------------------------- /2024/day01_python/day01_pt1.py: -------------------------------------------------------------------------------- 1 | list1 = [] 2 | list2 = [] 3 | diff = 0 4 | 5 | with open("2024/day01/input.txt", "r") as file: 6 | for line in file.readlines(): 7 | l, r = line.strip().split() 8 | list1.append(int(l)) 9 | list2.append(int(r)) 10 | 11 | for l, r in zip(sorted(list1), sorted(list2)): 12 | diff += abs(l - r) 13 | 14 | print(diff) -------------------------------------------------------------------------------- /2024/day01_python/day01_pt2.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | list1 = [] 4 | list2 = [] 5 | similarity_score = 0 6 | 7 | with open("2024/day01/input.txt", "r") as file: 8 | for line in file.readlines(): 9 | l, r = line.strip().split() 10 | list1.append(int(l)) 11 | list2.append(int(r)) 12 | 13 | l_counter = Counter(list1) 14 | r_counter = Counter(list2) 15 | 16 | for id, count in l_counter.items(): 17 | similarity_score += id * count * r_counter[id] 18 | 19 | print(similarity_score) -------------------------------------------------------------------------------- /2024/day02_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "os" 7 | "slices" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | func arrayAtoi(array []string) []int { 13 | var numbers = make([]int, len(array)) 14 | for idx, e_str := range array { 15 | num, err := strconv.Atoi(e_str) 16 | if err != nil { 17 | panic(err) 18 | } 19 | numbers[idx] = num 20 | } 21 | return numbers 22 | } 23 | 24 | func isSafe(levels []int) bool { 25 | if len(levels) == 1 { 26 | return true 27 | } 28 | is_ascending := levels[1] >= levels[0] 29 | 30 | for i := 1; i < len(levels); i++ { 31 | if is_ascending && (levels[i-1] > levels[i]) { 32 | return false 33 | } 34 | if !is_ascending && (levels[i-1] <= levels[i]) { 35 | return false 36 | } 37 | diff := int(math.Abs(float64(levels[i] - levels[i-1]))) 38 | if (diff == 0) || (diff > 3) { 39 | return false 40 | } 41 | } 42 | return true 43 | } 44 | 45 | func equalSlices(lhs []int, rhs []int) bool { 46 | for i := range len(lhs) { 47 | if lhs[i] != rhs[i] { 48 | return false 49 | } 50 | } 51 | return true 52 | } 53 | 54 | func checkIncreasing(levels []int) bool { 55 | sorted_levels := make([]int, len(levels)) 56 | copy(sorted_levels, levels) 57 | slices.Sort(sorted_levels) 58 | 59 | return equalSlices(sorted_levels, levels) 60 | } 61 | 62 | func checkDecreasing(levels []int) bool { 63 | sorted_levels := make([]int, len(levels)) 64 | copy(sorted_levels, levels) 65 | 66 | slices.Sort(sorted_levels) 67 | slices.Reverse(sorted_levels) 68 | 69 | return equalSlices(sorted_levels, levels) 70 | } 71 | 72 | func main() { 73 | input, _ := os.ReadFile("2024/day02_pt1/input.txt") 74 | n_safe := 0 75 | 76 | for idx, line := range strings.Split(string(input), "\n") { 77 | line = strings.TrimSpace(line) 78 | levels := arrayAtoi(strings.Split(line, " ")) 79 | if isSafe(levels) { 80 | // debug: 81 | if !checkIncreasing(levels) && !checkDecreasing(levels) { 82 | fmt.Println(idx, levels) 83 | } 84 | n_safe += 1 85 | // fmt.Printf("Line #%d is safe: %s\n", idx, line) 86 | } 87 | } 88 | fmt.Printf("%d\n", n_safe) 89 | } 90 | -------------------------------------------------------------------------------- /2024/day02_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | "os" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | func arrayAtoi(array []string) []int { 12 | var numbers = make([]int, len(array)) 13 | for idx, e_str := range array { 14 | num, err := strconv.Atoi(e_str) 15 | if err != nil { 16 | panic(err) 17 | } 18 | numbers[idx] = num 19 | } 20 | return numbers 21 | } 22 | 23 | func isSafe(levels []int) bool { 24 | if len(levels) == 1 { 25 | return true 26 | } 27 | is_ascending := levels[1] >= levels[0] 28 | 29 | for i := 1; i < len(levels); i++ { 30 | if is_ascending && (levels[i-1] > levels[i]) { 31 | return false 32 | } 33 | if !is_ascending && (levels[i-1] <= levels[i]) { 34 | return false 35 | } 36 | diff := int(math.Abs(float64(levels[i] - levels[i-1]))) 37 | if (diff == 0) || (diff > 3) { 38 | return false 39 | } 40 | } 41 | return true 42 | } 43 | 44 | func removeIthElement(array []int, i int) []int { 45 | array_short := make([]int, i) 46 | copy(array_short, array[:i]) 47 | return append(array_short, array[i+1:]...) 48 | } 49 | 50 | func equalSlices(lhs []int, rhs []int) bool { 51 | for i := range len(lhs) { 52 | if lhs[i] != rhs[i] { 53 | return false 54 | } 55 | } 56 | return true 57 | } 58 | 59 | func main() { 60 | input, _ := os.ReadFile("2024/day02_pt2/input.txt") 61 | n_safe := 0 62 | 63 | for _, line := range strings.Split(string(input), "\n") { 64 | line = strings.TrimSpace(line) 65 | levels := arrayAtoi(strings.Split(line, " ")) 66 | fmt.Println("--", levels, "--") 67 | 68 | for i := range len(levels) { 69 | levels_trunc := removeIthElement(levels, i) 70 | fmt.Println(levels_trunc) 71 | 72 | if isSafe(levels_trunc) { 73 | n_safe += 1 74 | break 75 | } 76 | } 77 | } 78 | fmt.Printf("Total safe: %d\n", n_safe) 79 | } 80 | -------------------------------------------------------------------------------- /2024/day03_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "regexp" 7 | "strconv" 8 | ) 9 | 10 | func performOperation(op []byte) int { 11 | re_num := regexp.MustCompile(`\d{1,3}`) 12 | result := 1 13 | for _, num_b := range re_num.FindAll(op, -1) { 14 | num, _ := strconv.Atoi(string(num_b)) 15 | result *= num 16 | } 17 | return result 18 | } 19 | 20 | func main() { 21 | input, _ := os.ReadFile("2024/day03_pt1/input.txt") 22 | 23 | re_ops := regexp.MustCompile(`mul\(\d{1,3},\d{1,3}\)`) 24 | total_sum := 0 25 | 26 | for _, op := range re_ops.FindAll(input, -1) { 27 | total_sum += performOperation(op) 28 | } 29 | 30 | fmt.Printf("%d\n", total_sum) 31 | } 32 | -------------------------------------------------------------------------------- /2024/day03_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "regexp" 7 | "slices" 8 | "strconv" 9 | ) 10 | 11 | type event struct { 12 | start_idx int 13 | end_idx int 14 | t string 15 | } 16 | 17 | func performOperation(e event, input []byte) int { 18 | op := input[e.start_idx:e.end_idx] 19 | re_num := regexp.MustCompile(`\d{1,3}`) 20 | result := 1 21 | for _, num_b := range re_num.FindAll(op, -1) { 22 | num, _ := strconv.Atoi(string(num_b)) 23 | result *= num 24 | } 25 | return result 26 | } 27 | 28 | func createGrid(op_idxs [][]int, do_idxs [][]int, dont_idxs [][]int) []event { 29 | grid := make([]event, 0) 30 | for _, coords := range op_idxs { 31 | grid = append( 32 | grid, 33 | event{start_idx: coords[0], end_idx: coords[1], t: "op"}, 34 | ) 35 | } 36 | 37 | for _, coords := range do_idxs { 38 | grid = append( 39 | grid, 40 | event{start_idx: coords[0], end_idx: coords[1], t: "do"}, 41 | ) 42 | } 43 | 44 | for _, coords := range dont_idxs { 45 | grid = append( 46 | grid, 47 | event{start_idx: coords[0], end_idx: coords[1], t: "dont"}, 48 | ) 49 | } 50 | 51 | slices.SortFunc(grid, func(a, b event) int { 52 | return a.start_idx - b.start_idx 53 | }) 54 | return grid 55 | } 56 | 57 | func filterOperations(op_idxs [][]int, do_idxs [][]int, dont_idxs [][]int) []event { 58 | do := true 59 | filtered := make([]event, 0) 60 | 61 | for _, event := range createGrid(op_idxs, do_idxs, dont_idxs) { 62 | if do && (event.t == "op") { 63 | filtered = append(filtered, event) 64 | } else if event.t == "do" { 65 | do = true 66 | } else if event.t == "dont" { 67 | do = false 68 | } 69 | } 70 | return filtered 71 | } 72 | 73 | func main() { 74 | input, _ := os.ReadFile("2024/day03_pt2/input.txt") 75 | 76 | re_ops := regexp.MustCompile(`mul\(\d{1,3},\d{1,3}\)`) 77 | re_dos := regexp.MustCompile(`do\(\)`) 78 | re_donts := regexp.MustCompile(`don't\(\)`) 79 | 80 | ops := re_ops.FindAllIndex(input, -1) 81 | dos := re_dos.FindAllIndex(input, -1) 82 | donts := re_donts.FindAllIndex(input, -1) 83 | 84 | filtered_ops := filterOperations(ops, dos, donts) 85 | 86 | total_sum := 0 87 | for _, op := range filtered_ops { 88 | total_sum += performOperation(op, input) 89 | } 90 | 91 | fmt.Printf("%d\n", total_sum) 92 | } 93 | -------------------------------------------------------------------------------- /2024/day04_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type coords struct { 10 | row int 11 | col int 12 | } 13 | 14 | type direction struct { 15 | h int 16 | v int 17 | } 18 | 19 | func findAllIndices(input []string, sym byte) []coords { 20 | idxs := make([]coords, 0) 21 | for row := range len(input) { 22 | for col := range len(input[0]) { 23 | if input[row][col] == sym { 24 | idxs = append(idxs, coords{row: row, col: col}) 25 | } 26 | } 27 | } 28 | return idxs 29 | } 30 | 31 | func countPatterns(input []string, pattern string, start coords) int { 32 | n_patterns := 0 33 | n_rows := len(input) 34 | n_cols := len(input[0]) 35 | 36 | directions := []direction{ 37 | {h: 1, v: -1}, 38 | {h: 1, v: 0}, 39 | {h: 1, v: 1}, 40 | {h: 0, v: -1}, 41 | {h: 0, v: 1}, 42 | {h: -1, v: -1}, 43 | {h: -1, v: 0}, 44 | {h: -1, v: 1}, 45 | } 46 | 47 | for _, d := range directions { 48 | matches := true 49 | for i := range len(pattern) { 50 | c := coords{ 51 | row: start.row + d.v*i, 52 | col: start.col + d.h*i, 53 | } 54 | if (c.row < 0) || (c.row >= n_rows) || 55 | (c.col < 0) || (c.col >= n_cols) { 56 | matches = false 57 | break 58 | } 59 | if input[c.row][c.col] != pattern[i] { 60 | matches = false 61 | break 62 | } 63 | } 64 | if matches { 65 | n_patterns += 1 66 | } 67 | } 68 | return n_patterns 69 | } 70 | 71 | func main() { 72 | input, _ := os.ReadFile("2024/day04_pt1/input.txt") 73 | input_rows := strings.Split(string(input), "\n") 74 | n_patterns := 0 75 | pattern := "XMAS" 76 | 77 | x_idxs := findAllIndices(input_rows, pattern[0]) 78 | 79 | for _, c := range x_idxs { 80 | n_patterns += countPatterns(input_rows, pattern, c) 81 | } 82 | 83 | fmt.Println(n_patterns) 84 | } 85 | -------------------------------------------------------------------------------- /2024/day04_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type coords struct { 10 | row int 11 | col int 12 | } 13 | 14 | type direction struct { 15 | h int 16 | v int 17 | } 18 | 19 | func findAllIndices(input []string, sym byte) []coords { 20 | idxs := make([]coords, 0) 21 | for row := range len(input) { 22 | for col := range len(input[0]) { 23 | if input[row][col] == sym { 24 | idxs = append(idxs, coords{row: row, col: col}) 25 | } 26 | } 27 | } 28 | return idxs 29 | } 30 | 31 | func checkPattern(input []string, pattern string, start coords) bool { 32 | n_rows := len(input) 33 | n_cols := len(input[0]) 34 | 35 | directions := []direction{ 36 | {h: 1, v: 1}, 37 | {h: -1, v: 1}, 38 | } 39 | 40 | matches := true 41 | for _, d := range directions { 42 | has0 := false 43 | has2 := false 44 | 45 | for _, coef := range []int{1, -1} { 46 | c := coords{ 47 | row: start.row + coef*d.v, 48 | col: start.col + coef*d.h, 49 | } 50 | if (c.row < 0) || (c.row >= n_rows) || 51 | (c.col < 0) || (c.col >= n_cols) { 52 | matches = false 53 | break 54 | } 55 | if input[c.row][c.col] == pattern[0] { 56 | has0 = true 57 | } 58 | if input[c.row][c.col] == pattern[2] { 59 | has2 = true 60 | } 61 | } 62 | if !has0 || !has2 { 63 | matches = false 64 | break 65 | } 66 | } 67 | return matches 68 | } 69 | 70 | func main() { 71 | input, _ := os.ReadFile("2024/day04_pt2/input.txt") 72 | input_rows := strings.Split(string(input), "\n") 73 | n_patterns := 0 74 | pattern := "MAS" 75 | 76 | a_idxs := findAllIndices(input_rows, pattern[1]) 77 | 78 | for _, c := range a_idxs { 79 | if checkPattern(input_rows, pattern, c) { 80 | n_patterns += 1 81 | } 82 | } 83 | 84 | fmt.Println(n_patterns) 85 | } 86 | -------------------------------------------------------------------------------- /2024/day05_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type order struct { 11 | before int 12 | after int 13 | } 14 | 15 | func parseOrder(order_input string) map[order]bool { 16 | rows := strings.Split(strings.TrimSpace(order_input), "\n") 17 | result := make(map[order]bool) 18 | 19 | for _, row := range rows { 20 | elements := strings.Split(row, "|") 21 | before, _ := strconv.Atoi(elements[0]) 22 | after, _ := strconv.Atoi(elements[1]) 23 | result[order{before: before, after: after}] = true 24 | } 25 | return result 26 | } 27 | 28 | func parsePages(page_input string) [][]int { 29 | rows := strings.Split(strings.TrimSpace(page_input), "\n") 30 | result := make([][]int, len(rows)) 31 | 32 | for i, row := range rows { 33 | page := strings.Split(row, ",") 34 | rule := make([]int, len(page)) 35 | for j, id_str := range page { 36 | id_num, _ := strconv.Atoi(id_str) 37 | rule[j] = id_num 38 | } 39 | result[i] = rule 40 | } 41 | return result 42 | } 43 | 44 | func checkPage(page []int, rules map[order]bool) bool { 45 | for i := 0; i < len(page); i++ { 46 | current_id := page[i] 47 | for j := 0; j < i; j++ { 48 | prev_id := page[j] 49 | _, is_in := rules[order{before: current_id, after: prev_id}] 50 | if is_in { 51 | return false 52 | } 53 | } 54 | } 55 | return true 56 | } 57 | 58 | func getMiddleId(page []int) int { 59 | idx := len(page) / 2 60 | return page[idx] 61 | } 62 | 63 | func main() { 64 | input, _ := os.ReadFile("2024/day05_pt1/input.txt") 65 | input_parts := strings.Split(string(input), "\n\n") 66 | 67 | rules := parseOrder(input_parts[0]) 68 | pages := parsePages(input_parts[1]) 69 | middle_sum := 0 70 | 71 | for i, page := range pages { 72 | is_ok := checkPage(page, rules) 73 | if is_ok { 74 | middle_sum += getMiddleId(page) 75 | } 76 | fmt.Printf("Page %d: %v --> %t\n", i, page, is_ok) 77 | } 78 | 79 | fmt.Println(middle_sum) 80 | } 81 | -------------------------------------------------------------------------------- /2024/day05_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "slices" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | type order struct { 12 | before int 13 | after int 14 | } 15 | 16 | func parseOrder(order_input string) map[order]bool { 17 | rows := strings.Split(strings.TrimSpace(order_input), "\n") 18 | result := make(map[order]bool) 19 | 20 | for _, row := range rows { 21 | elements := strings.Split(row, "|") 22 | before, _ := strconv.Atoi(elements[0]) 23 | after, _ := strconv.Atoi(elements[1]) 24 | result[order{before: before, after: after}] = true 25 | } 26 | return result 27 | } 28 | 29 | func parsePages(page_input string) [][]int { 30 | rows := strings.Split(strings.TrimSpace(page_input), "\n") 31 | result := make([][]int, len(rows)) 32 | 33 | for i, row := range rows { 34 | page := strings.Split(row, ",") 35 | rule := make([]int, len(page)) 36 | for j, id_str := range page { 37 | id_num, _ := strconv.Atoi(id_str) 38 | rule[j] = id_num 39 | } 40 | result[i] = rule 41 | } 42 | return result 43 | } 44 | 45 | func checkPage(page []int, rules map[order]bool) bool { 46 | for i := 0; i < len(page); i++ { 47 | current_id := page[i] 48 | for j := 0; j < i; j++ { 49 | prev_id := page[j] 50 | _, is_in := rules[order{before: current_id, after: prev_id}] 51 | if is_in { 52 | return false 53 | } 54 | } 55 | } 56 | return true 57 | } 58 | 59 | func correctPage(page []int, rules map[order]bool) { 60 | slices.SortFunc(page, func(a, b int) int { 61 | _, is_sorted_asc := rules[order{before: a, after: b}] 62 | _, is_sorted_desc := rules[order{before: b, after: a}] 63 | 64 | if is_sorted_asc { 65 | return 1 66 | } 67 | if is_sorted_desc { 68 | return -1 69 | } 70 | return 0 71 | }) 72 | } 73 | 74 | func getMiddleId(page []int) int { 75 | idx := len(page) / 2 76 | return page[idx] 77 | } 78 | 79 | func main() { 80 | input, _ := os.ReadFile("2024/day05_pt2/input.txt") 81 | input_parts := strings.Split(string(input), "\n\n") 82 | 83 | rules := parseOrder(input_parts[0]) 84 | pages := parsePages(input_parts[1]) 85 | middle_sum := 0 86 | 87 | for i, page := range pages { 88 | is_ok := checkPage(page, rules) 89 | if !is_ok { 90 | correctPage(page, rules) 91 | middle_sum += getMiddleId(page) 92 | } 93 | fmt.Printf("Page %d: %v --> %t\n", i, page, is_ok) 94 | } 95 | 96 | fmt.Println(middle_sum) 97 | } 98 | -------------------------------------------------------------------------------- /2024/day06_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type coord struct { 10 | row, col int 11 | } 12 | 13 | type position struct { 14 | row, col int 15 | dir_v, dir_h int 16 | } 17 | 18 | func findStart(field []string, obstacle string, empty string) position { 19 | for row := range len(field) { 20 | for col := range len(field[0]) { 21 | sym := string(field[row][col]) 22 | if (sym != obstacle) && (sym != empty) { 23 | dir_h, dir_v := 0, 0 24 | if sym == "^" { 25 | dir_v = -1 26 | } else if sym == ">" { 27 | dir_h = 1 28 | } else if sym == "v" { 29 | dir_v = 1 30 | } else if sym == "<" { 31 | dir_h = -1 32 | } 33 | return position{ 34 | row: row, col: col, 35 | dir_v: dir_v, dir_h: dir_h, 36 | } 37 | } 38 | } 39 | } 40 | return position{} 41 | } 42 | 43 | func turn(pos position) position { 44 | if pos.dir_h != 0 { 45 | return position{ 46 | row: pos.row, 47 | col: pos.col, 48 | dir_v: pos.dir_h, 49 | dir_h: 0, 50 | } 51 | } 52 | 53 | if pos.dir_v != 0 { 54 | return position{ 55 | row: pos.row, 56 | col: pos.col, 57 | dir_v: 0, 58 | dir_h: -pos.dir_v, 59 | } 60 | } 61 | 62 | return position{} 63 | } 64 | 65 | func doStep(field []string, pos position, obstacle string) (bool, position) { 66 | rows := len(field) 67 | cols := len(field[0]) 68 | 69 | next_row := pos.row + pos.dir_v 70 | next_col := pos.col + pos.dir_h 71 | 72 | if (next_row < 0) || (next_row >= rows) || (next_col < 0) || (next_col >= cols) { 73 | return true, position{} 74 | } 75 | 76 | if string(field[next_row][next_col]) == obstacle { 77 | return false, turn(pos) 78 | } 79 | 80 | return false, position{ 81 | row: next_row, 82 | col: next_col, 83 | dir_v: pos.dir_v, 84 | dir_h: pos.dir_h, 85 | } 86 | } 87 | 88 | func countVisited(field []string, start position, obstacle string) int { 89 | visited := make(map[coord]bool) 90 | 91 | do_exit := false 92 | pos := start 93 | 94 | for !do_exit { 95 | visited[coord{pos.row, pos.col}] = true 96 | do_exit, pos = doStep(field, pos, obstacle) 97 | fmt.Println(do_exit, pos) 98 | } 99 | 100 | return len(visited) 101 | } 102 | 103 | func main() { 104 | input, _ := os.ReadFile("2024/day06_pt1/input.txt") 105 | field := strings.Split(string(input), "\n") 106 | obstacle := "#" 107 | empty := "." 108 | 109 | start := findStart(field, obstacle, empty) 110 | n_visited := countVisited(field, start, obstacle) 111 | fmt.Println(n_visited) 112 | } 113 | -------------------------------------------------------------------------------- /2024/day06_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type coord struct { 10 | row, col int 11 | } 12 | 13 | type position struct { 14 | row, col int 15 | dir_v, dir_h int 16 | } 17 | 18 | func findStart(field []string, obstacle string, empty string) position { 19 | for row := range len(field) { 20 | for col := range len(field[0]) { 21 | sym := string(field[row][col]) 22 | if (sym != obstacle) && (sym != empty) { 23 | dir_h, dir_v := 0, 0 24 | if sym == "^" { 25 | dir_v = -1 26 | } else if sym == ">" { 27 | dir_h = 1 28 | } else if sym == "v" { 29 | dir_v = 1 30 | } else if sym == "<" { 31 | dir_h = -1 32 | } 33 | return position{ 34 | row: row, col: col, 35 | dir_v: dir_v, dir_h: dir_h, 36 | } 37 | } 38 | } 39 | } 40 | return position{} 41 | } 42 | 43 | func turn(pos position) position { 44 | if pos.dir_h != 0 { 45 | return position{ 46 | row: pos.row, 47 | col: pos.col, 48 | dir_v: pos.dir_h, 49 | dir_h: 0, 50 | } 51 | } 52 | 53 | if pos.dir_v != 0 { 54 | return position{ 55 | row: pos.row, 56 | col: pos.col, 57 | dir_v: 0, 58 | dir_h: -pos.dir_v, 59 | } 60 | } 61 | 62 | return position{} 63 | } 64 | 65 | func doStep(field []string, pos position, obstacle string) (bool, position) { 66 | rows := len(field) 67 | cols := len(field[0]) 68 | 69 | next_row := pos.row + pos.dir_v 70 | next_col := pos.col + pos.dir_h 71 | 72 | if (next_row < 0) || (next_row >= rows) || (next_col < 0) || (next_col >= cols) { 73 | return true, position{} 74 | } 75 | 76 | if string(field[next_row][next_col]) == obstacle { 77 | return false, turn(pos) 78 | } 79 | 80 | return false, position{ 81 | row: next_row, 82 | col: next_col, 83 | dir_v: pos.dir_v, 84 | dir_h: pos.dir_h, 85 | } 86 | } 87 | 88 | func runPath(field []string, start position, obstacle string) (bool, map[coord]bool) { 89 | visited := make(map[coord]bool) 90 | visited_pos := make(map[position]bool) 91 | 92 | do_exit := false 93 | pos := start 94 | 95 | for !do_exit { 96 | visited[coord{pos.row, pos.col}] = true 97 | visited_pos[pos] = true 98 | do_exit, pos = doStep(field, pos, obstacle) 99 | _, been_there := visited_pos[pos] 100 | if been_there { 101 | return true, visited 102 | } 103 | } 104 | 105 | return false, visited 106 | } 107 | 108 | func main() { 109 | input, _ := os.ReadFile("2024/day06_pt2/input.txt") 110 | field := strings.Split(string(input), "\n") 111 | obstacle := "#" 112 | empty := "." 113 | 114 | start := findStart(field, obstacle, empty) 115 | _, visited := runPath(field, start, obstacle) 116 | 117 | n_looping_obst := 0 118 | 119 | for c := range visited { 120 | if string(field[c.row][c.col]) != empty { 121 | // it's the start 122 | continue 123 | } 124 | orig_row := field[c.row] 125 | field[c.row] = orig_row[:c.col] + obstacle + orig_row[c.col+1:] 126 | is_loop, _ := runPath(field, start, obstacle) 127 | if is_loop { 128 | n_looping_obst += 1 129 | } 130 | field[c.row] = orig_row 131 | } 132 | fmt.Println(n_looping_obst) 133 | } 134 | -------------------------------------------------------------------------------- /2024/day07_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type equation struct { 11 | expected int 12 | nums []int 13 | } 14 | 15 | func parseEquations(raw string) equation { 16 | parts := strings.Split(raw, ":") 17 | res, _ := strconv.Atoi(parts[0]) 18 | nums_str := strings.Split(strings.TrimSpace(parts[1]), " ") 19 | nums := make([]int, len(nums_str)) 20 | 21 | for i, num_str := range nums_str { 22 | num, _ := strconv.Atoi(num_str) 23 | nums[i] = num 24 | } 25 | 26 | return equation{ 27 | expected: res, 28 | nums: nums, 29 | } 30 | } 31 | 32 | func evaluateEquation(eq equation, idx int, current_val int) bool { 33 | if (current_val == eq.expected) && (idx == len(eq.nums)) { 34 | return true 35 | } 36 | if current_val > eq.expected { 37 | return false 38 | } 39 | if idx == len(eq.nums) { 40 | return false 41 | } 42 | 43 | next_val := eq.nums[idx] 44 | 45 | return evaluateEquation(eq, idx+1, current_val*next_val) || 46 | evaluateEquation(eq, idx+1, current_val+next_val) 47 | } 48 | 49 | func main() { 50 | input, _ := os.ReadFile("2024/day07_pt1/input.txt") 51 | equations_raw := strings.Split(string(input), "\n") 52 | total_sum := 0 53 | 54 | for i, raw := range equations_raw { 55 | eq := parseEquations(raw) 56 | is_ok := evaluateEquation(eq, 1, eq.nums[0]) 57 | if is_ok { 58 | total_sum += eq.expected 59 | } 60 | fmt.Printf("Eq #%d: %s -> %t\n", i, raw, is_ok) 61 | } 62 | 63 | fmt.Println(total_sum) 64 | } 65 | -------------------------------------------------------------------------------- /2024/day07_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type equation struct { 11 | expected int 12 | nums []int 13 | } 14 | 15 | func parseEquations(raw string) equation { 16 | parts := strings.Split(raw, ":") 17 | res, _ := strconv.Atoi(parts[0]) 18 | nums_str := strings.Split(strings.TrimSpace(parts[1]), " ") 19 | nums := make([]int, len(nums_str)) 20 | 21 | for i, num_str := range nums_str { 22 | num, _ := strconv.Atoi(num_str) 23 | nums[i] = num 24 | } 25 | 26 | return equation{ 27 | expected: res, 28 | nums: nums, 29 | } 30 | } 31 | 32 | func concat(lhs int, rhs int) int { 33 | res, _ := strconv.Atoi(strconv.Itoa(lhs) + strconv.Itoa(rhs)) 34 | return res 35 | } 36 | 37 | func evaluateEquation(eq equation, idx int, current_val int) bool { 38 | if (current_val == eq.expected) && (idx == len(eq.nums)) { 39 | return true 40 | } 41 | if current_val > eq.expected { 42 | return false 43 | } 44 | if idx == len(eq.nums) { 45 | return false 46 | } 47 | 48 | next_val := eq.nums[idx] 49 | 50 | return evaluateEquation(eq, idx+1, current_val*next_val) || 51 | evaluateEquation(eq, idx+1, current_val+next_val) || 52 | evaluateEquation(eq, idx+1, concat(current_val, next_val)) 53 | } 54 | 55 | func main() { 56 | input, _ := os.ReadFile("2024/day07_pt2/input.txt") 57 | equations_raw := strings.Split(string(input), "\n") 58 | total_sum := 0 59 | 60 | for i, raw := range equations_raw { 61 | eq := parseEquations(raw) 62 | is_ok := evaluateEquation(eq, 1, eq.nums[0]) 63 | if is_ok { 64 | total_sum += eq.expected 65 | } 66 | fmt.Printf("Eq #%d: %s -> %t\n", i, raw, is_ok) 67 | } 68 | 69 | fmt.Println(total_sum) 70 | } 71 | -------------------------------------------------------------------------------- /2024/day08_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type coord struct { 10 | row, col int 11 | } 12 | 13 | func scanAntennas(field []string) map[string][]coord { 14 | antennas := make(map[string][]coord) 15 | 16 | for row := range len(field) { 17 | for col := range len(field[0]) { 18 | if string(field[row][col]) == "." { 19 | continue 20 | } 21 | 22 | coords, exists := antennas[string(field[row][col])] 23 | if exists { 24 | coords = append(coords, coord{row: row, col: col}) 25 | } else { 26 | coords = []coord{{row: row, col: col}} 27 | } 28 | antennas[string(field[row][col])] = coords 29 | } 30 | } 31 | return antennas 32 | } 33 | 34 | func findAntinodes(c1, c2 coord) (coord, coord) { 35 | diff_rows := c2.row - c1.row 36 | diff_cols := c2.col - c1.col 37 | 38 | return coord{row: c1.row - diff_rows, col: c1.col - diff_cols}, coord{row: c2.row + diff_rows, col: c2.col + diff_cols} 39 | } 40 | 41 | func checkAntinode(c coord, total_rows int, total_cols int) bool { 42 | if (c.row < 0) || (c.row >= total_rows) { 43 | return false 44 | } 45 | if (c.col < 0) || (c.col >= total_cols) { 46 | return false 47 | } 48 | return true 49 | } 50 | 51 | func main() { 52 | input, _ := os.ReadFile("2024/day08_pt1/input.txt") 53 | field := strings.Split(string(input), "\n") 54 | total_rows := len(field) 55 | total_cols := len(field[0]) 56 | 57 | antennas := scanAntennas(field) 58 | antinodes := make(map[coord]bool) 59 | for id, coords := range antennas { 60 | fmt.Printf("Processing antenna %s\n", id) 61 | for i := range len(coords) { 62 | for j := range i { 63 | a1, a2 := findAntinodes(coords[i], coords[j]) 64 | if checkAntinode(a1, total_rows, total_cols) { 65 | antinodes[a1] = true 66 | } 67 | if checkAntinode(a2, total_rows, total_cols) { 68 | antinodes[a2] = true 69 | } 70 | } 71 | } 72 | } 73 | fmt.Println(len(antinodes)) 74 | } 75 | -------------------------------------------------------------------------------- /2024/day08_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type coord struct { 10 | row, col int 11 | } 12 | 13 | func scanAntennas(field []string) map[string][]coord { 14 | antennas := make(map[string][]coord) 15 | 16 | for row := range len(field) { 17 | for col := range len(field[0]) { 18 | if string(field[row][col]) == "." { 19 | continue 20 | } 21 | 22 | coords, exists := antennas[string(field[row][col])] 23 | if exists { 24 | coords = append(coords, coord{row: row, col: col}) 25 | } else { 26 | coords = []coord{{row: row, col: col}} 27 | } 28 | antennas[string(field[row][col])] = coords 29 | } 30 | } 31 | return antennas 32 | } 33 | 34 | func findAntinodes(c1, c2 coord, total_rows int, total_cols int) []coord { 35 | coords := make([]coord, 0) 36 | 37 | diff_rows := c2.row - c1.row 38 | diff_cols := c2.col - c1.col 39 | 40 | is_in := true 41 | i := 0 42 | for is_in { 43 | coords = append(coords, coord{row: c1.row - i*diff_rows, col: c1.col - i*diff_cols}) 44 | i += 1 45 | 46 | if (c1.row-i*diff_rows < 0) || (c1.row-i*diff_rows >= total_rows) { 47 | is_in = false 48 | } 49 | if (c1.col-i*diff_cols < 0) || (c1.col-i*diff_cols >= total_cols) { 50 | is_in = false 51 | } 52 | } 53 | 54 | is_in = true 55 | i = 1 56 | for is_in { 57 | coords = append(coords, coord{row: c1.row + i*diff_rows, col: c1.col + i*diff_cols}) 58 | i += 1 59 | 60 | if (c1.row+i*diff_rows < 0) || (c1.row+i*diff_rows >= total_rows) { 61 | is_in = false 62 | } 63 | if (c1.col+i*diff_cols < 0) || (c1.col+i*diff_cols >= total_cols) { 64 | is_in = false 65 | } 66 | } 67 | 68 | return coords 69 | } 70 | 71 | func main() { 72 | input, _ := os.ReadFile("2024/day08_pt2/input.txt") 73 | field := strings.Split(string(input), "\n") 74 | total_rows := len(field) 75 | total_cols := len(field[0]) 76 | 77 | antennas := scanAntennas(field) 78 | antinodes := make(map[coord]bool) 79 | for id, coords := range antennas { 80 | fmt.Printf("Processing antenna %s\n", id) 81 | for i := range len(coords) { 82 | for j := range i { 83 | as := findAntinodes(coords[i], coords[j], total_rows, total_cols) 84 | for _, a := range as { 85 | antinodes[a] = true 86 | } 87 | } 88 | } 89 | } 90 | fmt.Println(len(antinodes)) 91 | } 92 | -------------------------------------------------------------------------------- /2024/day09_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | ) 8 | 9 | func main() { 10 | input, _ := os.ReadFile("2024/day09_pt1/input.txt") 11 | 12 | // checksum := 0 13 | files, free := parseFileBlocks(string(input)) 14 | 15 | fmt.Println("files:", files, "free:", free, "") 16 | 17 | fmt.Println(moveFileBlocks(files, free)) 18 | } 19 | 20 | func parseFileBlocks(input string) ([]int, []int) { 21 | files := make([]int, 0) 22 | free := make([]int, 0) 23 | 24 | for i := range len(input) { 25 | block_str := string(input[i]) 26 | block, _ := strconv.Atoi(block_str) 27 | 28 | if i%2 == 0 { 29 | // block is file 30 | files = append(files, block) 31 | } else { 32 | // block is free 33 | free = append(free, block) 34 | } 35 | } 36 | 37 | return files, free 38 | } 39 | 40 | func moveFileBlocks(files []int, free []int) int { 41 | current_pos := 0 42 | checksum := 0 43 | free_idx := 0 44 | file_idx := len(files) 45 | in_file_idx := 0 46 | 47 | for free_idx < file_idx { 48 | fmt.Printf("Free #%d\tFile #%d\tChecksum - %d\n", free_idx, file_idx, checksum) 49 | // adding previous file info to checksum 50 | for range files[free_idx] { 51 | fmt.Printf("\tadding file space: pos #%d file id %d, total %d\n", current_pos, free_idx, free_idx*current_pos) 52 | checksum += free_idx * current_pos 53 | current_pos += 1 54 | } 55 | 56 | // moving last file blocks into current free block 57 | moved := 0 58 | for moved != free[free_idx] { 59 | if in_file_idx == 0 { 60 | file_idx -= 1 61 | in_file_idx = files[file_idx] 62 | } 63 | 64 | moving := min(in_file_idx, free[free_idx]-moved) 65 | 66 | for range moving { 67 | fmt.Printf("\tadding free space: pos #%d file id %d, total %d\n", current_pos, file_idx, file_idx*current_pos) 68 | checksum += file_idx * current_pos 69 | current_pos += 1 70 | } 71 | 72 | moved += moving 73 | in_file_idx -= moving 74 | } 75 | free_idx += 1 76 | } 77 | 78 | for range in_file_idx { 79 | fmt.Printf("\tadding left over files: pos #%d file id %d, total %d\n", current_pos, file_idx, file_idx*current_pos) 80 | checksum += file_idx * current_pos 81 | current_pos += 1 82 | } 83 | 84 | return checksum 85 | } 86 | -------------------------------------------------------------------------------- /2024/day09_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | ) 8 | 9 | type block struct { 10 | size int 11 | id int 12 | start_idx int 13 | is_moved bool 14 | } 15 | 16 | func main() { 17 | input, _ := os.ReadFile("2024/day09_pt2/input.txt") 18 | 19 | // checksum := 0 20 | files, free := parseFileBlocks(string(input)) 21 | 22 | fmt.Println("files prefix:", files[:10], "\nfree prefix:", free[:10], "") 23 | // fmt.Println("files suffix:", files[len(files)-10:], "\nfree suffix:", free[len(free)-10:], "") 24 | fmt.Println("files len:", len(files), "\nfree len:", len(free), "") 25 | 26 | fmt.Println(moveFileBlocks(files, free)) 27 | } 28 | 29 | func parseFileBlocks(input string) ([]block, []block) { 30 | files := make([]block, 0) 31 | free := make([]block, 0) 32 | idx := 0 33 | 34 | for i := range len(input) { 35 | b_str := string(input[i]) 36 | b, _ := strconv.Atoi(b_str) 37 | 38 | if i%2 == 0 { 39 | // block is file 40 | files = append(files, block{size: b, id: i / 2, start_idx: idx, is_moved: false}) 41 | } else { 42 | // block is free 43 | free = append(free, block{size: b, id: i / 2, start_idx: idx, is_moved: false}) 44 | } 45 | idx += b 46 | } 47 | 48 | return files, free 49 | } 50 | 51 | func moveFileBlocks(files []block, free []block) int { 52 | checksum := 0 53 | free_idx := 0 54 | 55 | for free_idx < len(free) { 56 | fmt.Printf("Free #%d\tChecksum - %d\n", free_idx, checksum) 57 | // adding previous file info to checksum 58 | if !files[free_idx].is_moved { 59 | for i := range files[free_idx].size { 60 | current_pos := files[free_idx].start_idx + i 61 | fmt.Printf("\tadding file space: pos #%d file id %d, total %d\n", current_pos, free_idx, free_idx*current_pos) 62 | checksum += free_idx * current_pos 63 | } 64 | } 65 | 66 | // moving last file blocks into current free block 67 | file_idx := len(files) - 1 68 | moved := 0 69 | for (moved != free[free_idx].size) && (file_idx > free_idx) { 70 | if (files[file_idx].size <= free[free_idx].size-moved) && !files[file_idx].is_moved { 71 | fmt.Printf("\t\tspace left to move %d\n", free[free_idx].size-moved) 72 | fmt.Printf("\t\tmoving file #%d -> free #%d\n", file_idx, free_idx) 73 | // moving 74 | for i := range files[file_idx].size { 75 | current_pos := free[free_idx].start_idx + moved + i 76 | fmt.Printf("\tadding free space: pos #%d file id %d, total %d\n", current_pos, file_idx, file_idx*current_pos) 77 | checksum += file_idx * current_pos 78 | } 79 | files[file_idx].is_moved = true 80 | moved += files[file_idx].size 81 | } 82 | file_idx-- 83 | if file_idx < 0 { 84 | break 85 | } 86 | } 87 | free_idx += 1 88 | } 89 | 90 | return checksum 91 | } 92 | -------------------------------------------------------------------------------- /2024/day10_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type coord struct { 11 | row, col int 12 | } 13 | 14 | func main() { 15 | input, _ := os.ReadFile("2024/day10_pt1/input.txt") 16 | field := parseField(string(input)) 17 | starts := findStarts(field) 18 | total_score := 0 19 | 20 | for _, start := range starts { 21 | score := countTrails(field, start) 22 | total_score += score 23 | 24 | fmt.Printf("Start (%d, %d) - score %d\n", start.row, start.col, score) 25 | } 26 | 27 | fmt.Println(total_score) 28 | } 29 | 30 | func parseField(input string) [][]int { 31 | rows := strings.Split(input, "\n") 32 | field := make([][]int, len(rows)) 33 | 34 | for i, row := range rows { 35 | row_num := make([]int, len(row)) 36 | for j, sym := range row { 37 | num, _ := strconv.Atoi(string(sym)) 38 | row_num[j] = num 39 | } 40 | field[i] = row_num 41 | } 42 | 43 | return field 44 | } 45 | 46 | func findStarts(field [][]int) []coord { 47 | starts := make([]coord, 0) 48 | for row := range len(field) { 49 | for col := range len(field[0]) { 50 | if field[row][col] == 0 { 51 | starts = append(starts, coord{row: row, col: col}) 52 | } 53 | } 54 | } 55 | return starts 56 | } 57 | 58 | func countTrails(field [][]int, start coord) int { 59 | n_rows := len(field) 60 | n_cols := len(field[0]) 61 | 62 | queue := []coord{start} 63 | visited := make(map[coord]bool) 64 | visited[start] = true 65 | n_trails := 0 66 | 67 | for len(queue) > 0 { 68 | c := queue[0] 69 | queue = queue[1:] 70 | value := field[c.row][c.col] 71 | 72 | if value == 9 { 73 | n_trails += 1 74 | continue 75 | } 76 | 77 | next_pos := []coord{ 78 | {c.row - 1, c.col}, 79 | {c.row + 1, c.col}, 80 | {c.row, c.col - 1}, 81 | {c.row, c.col + 1}, 82 | } 83 | 84 | for _, n := range next_pos { 85 | _, is_visited := visited[n] 86 | 87 | if is_visited { 88 | continue 89 | } 90 | 91 | if (n.row < 0) || (n.row >= n_rows) || (n.col < 0) || (n.col >= n_cols) { 92 | continue 93 | } 94 | 95 | if field[n.row][n.col] == value+1 { 96 | queue = append(queue, n) 97 | visited[n] = true 98 | } 99 | } 100 | } 101 | 102 | return n_trails 103 | } 104 | -------------------------------------------------------------------------------- /2024/day10_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type coord struct { 11 | row, col int 12 | } 13 | 14 | func main() { 15 | input, _ := os.ReadFile("2024/day10_pt2/input.txt") 16 | field := parseField(string(input)) 17 | starts := findStarts(field) 18 | total_score := 0 19 | 20 | for _, start := range starts { 21 | score := countTrails(field, start) 22 | total_score += score 23 | 24 | fmt.Printf("Start (%d, %d) - score %d\n", start.row, start.col, score) 25 | } 26 | 27 | fmt.Println(total_score) 28 | } 29 | 30 | func parseField(input string) [][]int { 31 | rows := strings.Split(input, "\n") 32 | field := make([][]int, len(rows)) 33 | 34 | for i, row := range rows { 35 | row_num := make([]int, len(row)) 36 | for j, sym := range row { 37 | num, _ := strconv.Atoi(string(sym)) 38 | row_num[j] = num 39 | } 40 | field[i] = row_num 41 | } 42 | 43 | return field 44 | } 45 | 46 | func findStarts(field [][]int) []coord { 47 | starts := make([]coord, 0) 48 | for row := range len(field) { 49 | for col := range len(field[0]) { 50 | if field[row][col] == 0 { 51 | starts = append(starts, coord{row: row, col: col}) 52 | } 53 | } 54 | } 55 | return starts 56 | } 57 | 58 | func countTrails(field [][]int, start coord) int { 59 | n_rows := len(field) 60 | n_cols := len(field[0]) 61 | 62 | queue := []coord{start} 63 | n_trails := 0 64 | 65 | for len(queue) > 0 { 66 | c := queue[0] 67 | queue = queue[1:] 68 | value := field[c.row][c.col] 69 | 70 | if value == 9 { 71 | n_trails += 1 72 | continue 73 | } 74 | 75 | next_pos := []coord{ 76 | {c.row - 1, c.col}, 77 | {c.row + 1, c.col}, 78 | {c.row, c.col - 1}, 79 | {c.row, c.col + 1}, 80 | } 81 | 82 | for _, n := range next_pos { 83 | if (n.row < 0) || (n.row >= n_rows) || (n.col < 0) || (n.col >= n_cols) { 84 | continue 85 | } 86 | 87 | if field[n.row][n.col] == value+1 { 88 | queue = append(queue, n) 89 | } 90 | } 91 | } 92 | 93 | return n_trails 94 | } 95 | -------------------------------------------------------------------------------- /2024/day11_pt1_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | n_blinks int = 75 12 | ) 13 | 14 | type status struct { 15 | stone int 16 | in_n_blinks int 17 | } 18 | 19 | func main() { 20 | input, _ := os.ReadFile("2024/day11_pt1/input.txt") 21 | stones := getStones(string(input)) 22 | precounts := make(map[status]int) 23 | fmt.Println(getStoneCountAfterNBlinks(stones, n_blinks, &precounts)) 24 | } 25 | 26 | func getStones(input string) []int { 27 | stones_str := strings.Split(input, " ") 28 | stones := make([]int, len(stones_str)) 29 | 30 | for i, stone_str := range stones_str { 31 | stone, _ := strconv.Atoi(stone_str) 32 | stones[i] = stone 33 | } 34 | return stones 35 | } 36 | 37 | func applyRules(stone int) []int { 38 | if stone == 0 { 39 | return []int{1} 40 | } 41 | 42 | stone_str := strconv.Itoa(stone) 43 | if len(stone_str)%2 == 0 { 44 | middle := len(stone_str) / 2 45 | stone1, _ := strconv.Atoi(stone_str[:middle]) 46 | stone2, _ := strconv.Atoi(stone_str[middle:]) 47 | return []int{stone1, stone2} 48 | } 49 | 50 | return []int{stone * 2024} 51 | } 52 | 53 | func getStoneCountAfterNBlinks(stones []int, n int, precounts *map[status]int) int { 54 | if n < 0 { 55 | return 1 56 | } 57 | 58 | total_stone_count := 0 59 | for _, stone := range stones { 60 | stone_count, is_saved := (*precounts)[status{stone: stone, in_n_blinks: n}] 61 | fmt.Printf("Searching stone %d in %d blinks - %t, %d\n", stone, n, is_saved, stone_count) 62 | if !is_saved { 63 | after_blink := applyRules(stone) 64 | stone_count = getStoneCountAfterNBlinks(after_blink, n-1, precounts) 65 | (*precounts)[status{stone: stone, in_n_blinks: n}] = stone_count 66 | fmt.Printf("Saving stone %d in %d blinks - %d\n", stone, n, stone_count) 67 | } 68 | total_stone_count += stone_count 69 | } 70 | return total_stone_count 71 | } 72 | -------------------------------------------------------------------------------- /2024/day12_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type direction struct { 10 | v, h int 11 | } 12 | 13 | type coord struct { 14 | row, col int 15 | } 16 | 17 | type plant struct { 18 | area, perimeter int 19 | } 20 | 21 | func main() { 22 | input, _ := os.ReadFile("2024/day12_pt1/input.txt") 23 | field := strings.Split(string(input), "\n") 24 | 25 | processed := make(map[coord]bool) 26 | price := 0 27 | 28 | for row := range len(field) { 29 | for col := range len(field[0]) { 30 | _, is_processed := processed[coord{row, col}] 31 | if !is_processed { 32 | p := getPlantStats(field, coord{row, col}, &processed) 33 | fmt.Printf("ID: %s, area: %d, perimeter: %d\n", string(field[row][col]), p.area, p.perimeter) 34 | price += p.area * p.perimeter 35 | } 36 | } 37 | } 38 | fmt.Println(price) 39 | } 40 | 41 | func getPlantStats(field []string, start coord, processed *map[coord]bool) plant { 42 | id := field[start.row][start.col] 43 | area := make(map[coord]bool) 44 | perimeter := 0 45 | 46 | directions := []direction{{0, 1}, {1, 0}, {0, -1}, {-1, 0}} 47 | 48 | queue := []coord{start} 49 | area[start] = true 50 | 51 | for len(queue) > 0 { 52 | pos := queue[0] 53 | queue = queue[1:] 54 | for _, d := range directions { 55 | next_pos := coord{pos.row + d.v, pos.col + d.h} 56 | if (next_pos.row < 0) || (next_pos.row >= len(field)) || (next_pos.col < 0) || (next_pos.col >= len(field[0])) { 57 | perimeter += 1 58 | continue 59 | } 60 | if field[next_pos.row][next_pos.col] != id { 61 | perimeter += 1 62 | continue 63 | } 64 | _, is_visited := area[next_pos] 65 | if !is_visited { 66 | queue = append(queue, next_pos) 67 | area[next_pos] = true 68 | } 69 | } 70 | } 71 | 72 | for c := range area { 73 | (*processed)[c] = true 74 | } 75 | return plant{len(area), perimeter} 76 | } 77 | -------------------------------------------------------------------------------- /2024/day12_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type direction struct { 10 | v, h int 11 | } 12 | 13 | type coord struct { 14 | row, col int 15 | } 16 | 17 | type border struct { 18 | c coord 19 | d direction 20 | } 21 | 22 | func main() { 23 | input, _ := os.ReadFile("2024/day12_pt2/input.txt") 24 | field := strings.Split(string(input), "\n") 25 | 26 | processed := make(map[coord]bool) 27 | price := 0 28 | 29 | for row := range len(field) { 30 | for col := range len(field[0]) { 31 | _, is_processed := processed[coord{row, col}] 32 | if !is_processed { 33 | area, perimeter_h, perimeter_v := getArea(field, coord{row, col}, &processed) 34 | sides := getSides(perimeter_h, direction{v: 0, h: -1}) 35 | sides += getSides(perimeter_v, direction{v: -1, h: 0}) 36 | 37 | fmt.Printf("ID: %s, area: %d, perimeter: %d\n", string(field[row][col]), len(area), sides) 38 | fmt.Println(perimeter_h, "\n", perimeter_v) 39 | price += len(area) * sides 40 | } 41 | } 42 | } 43 | fmt.Println(price) 44 | } 45 | 46 | func getArea(field []string, start coord, processed *map[coord]bool) ([]coord, map[border]bool, map[border]bool) { 47 | id := field[start.row][start.col] 48 | area := make(map[coord]bool) 49 | perimeter_v := make(map[border]bool) 50 | perimeter_h := make(map[border]bool) 51 | 52 | directions := []direction{{0, 1}, {1, 0}, {0, -1}, {-1, 0}} 53 | 54 | queue := []coord{start} 55 | area[start] = true 56 | 57 | for len(queue) > 0 { 58 | pos := queue[0] 59 | queue = queue[1:] 60 | for _, d := range directions { 61 | next_pos := coord{pos.row + d.v, pos.col + d.h} 62 | is_border := false 63 | if (next_pos.row < 0) || (next_pos.row >= len(field)) || (next_pos.col < 0) || (next_pos.col >= len(field[0])) { 64 | is_border = true 65 | } else if field[next_pos.row][next_pos.col] != id { 66 | is_border = true 67 | } 68 | if is_border && (d.h != 0) { 69 | perimeter_v[border{next_pos, d}] = true 70 | } else if is_border { 71 | perimeter_h[border{next_pos, d}] = true 72 | } 73 | 74 | if is_border { 75 | continue 76 | } 77 | 78 | _, is_visited := area[next_pos] 79 | if !is_visited { 80 | queue = append(queue, next_pos) 81 | area[next_pos] = true 82 | } 83 | } 84 | } 85 | 86 | area_list := make([]coord, 0) 87 | for c := range area { 88 | (*processed)[c] = true 89 | area_list = append(area_list, c) 90 | } 91 | return area_list, perimeter_h, perimeter_v 92 | } 93 | 94 | func getSides(perimeter map[border]bool, axis direction) int { 95 | sides := 0 96 | for tile := range perimeter { 97 | prev_tile := coord{tile.c.row + axis.v, tile.c.col + axis.h} 98 | _, exists := perimeter[border{prev_tile, tile.d}] 99 | if !exists { 100 | sides += 1 101 | } 102 | } 103 | return sides 104 | } 105 | -------------------------------------------------------------------------------- /2024/day13_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | a_cost int = 3 13 | b_cost int = 1 14 | ) 15 | 16 | type button struct { 17 | X, Y int 18 | } 19 | type prize struct { 20 | X, Y int 21 | } 22 | type machine struct { 23 | A, B button 24 | p prize 25 | } 26 | 27 | func main() { 28 | input, _ := os.ReadFile("2024/day13_pt1/input.txt") 29 | total_price := 0 30 | 31 | for i, m := range parseMachines(string(input)) { 32 | m_str, _ := json.Marshal(m) 33 | price := solveMachine(m) 34 | fmt.Printf("Machine #%d: %s, price: %d\n", i, string(m_str), price) 35 | if price >= 0 { 36 | total_price += price 37 | } 38 | } 39 | 40 | fmt.Println(total_price) 41 | } 42 | 43 | func solveMachine(m machine) int { 44 | nb_denom := (m.B.X*m.A.Y - m.B.Y*m.A.X) 45 | nb_num := (m.p.X*m.A.Y - m.p.Y*m.A.X) 46 | 47 | if nb_num%nb_denom != 0 { 48 | return -1 49 | } 50 | 51 | nb := nb_num / nb_denom 52 | na_denom := m.A.X 53 | na_num := m.p.X - nb*m.B.X 54 | 55 | if na_num%na_denom != 0 { 56 | return -1 57 | } 58 | na := na_num / na_denom 59 | return na*a_cost + nb*b_cost 60 | } 61 | 62 | func parseMachines(input string) []machine { 63 | machines_raw := strings.Split(input, "\n\n") 64 | machines := make([]machine, len(machines_raw)) 65 | 66 | for i, machine_raw := range machines_raw { 67 | rows := strings.Split(machine_raw, "\n") 68 | m := machine{} 69 | for j, row := range rows { 70 | if j == 0 { 71 | m.A = parseButton(row) 72 | } else if j == 1 { 73 | m.B = parseButton(row) 74 | } else { 75 | m.p = parsePrize(row) 76 | } 77 | } 78 | machines[i] = m 79 | } 80 | return machines 81 | } 82 | 83 | func parseButton(row string) button { 84 | elements := strings.Split(row[10:], ", ") 85 | X, _ := strconv.Atoi(elements[0][2:]) 86 | Y, _ := strconv.Atoi(elements[1][2:]) 87 | return button{X, Y} 88 | } 89 | func parsePrize(row string) prize { 90 | elements := strings.Split(row[7:], ", ") 91 | X, _ := strconv.Atoi(elements[0][2:]) 92 | Y, _ := strconv.Atoi(elements[1][2:]) 93 | return prize{X, Y} 94 | } 95 | -------------------------------------------------------------------------------- /2024/day13_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | a_cost int = 3 13 | b_cost int = 1 14 | prize_add int = 10000000000000 15 | ) 16 | 17 | type button struct { 18 | X, Y int 19 | } 20 | type prize struct { 21 | X, Y int 22 | } 23 | type machine struct { 24 | A, B button 25 | p prize 26 | } 27 | 28 | func main() { 29 | input, _ := os.ReadFile("2024/day13_pt2/input.txt") 30 | total_price := 0 31 | 32 | for i, m := range parseMachines(string(input)) { 33 | m_str, _ := json.Marshal(m) 34 | price := solveMachine(m) 35 | fmt.Printf("Machine #%d: %s, price: %d\n", i, string(m_str), price) 36 | if price >= 0 { 37 | total_price += price 38 | } 39 | } 40 | 41 | fmt.Println(total_price) 42 | } 43 | 44 | func solveMachine(m machine) int { 45 | nb_denom := (m.B.X*m.A.Y - m.B.Y*m.A.X) 46 | nb_num := (m.p.X*m.A.Y - m.p.Y*m.A.X) 47 | 48 | if nb_num%nb_denom != 0 { 49 | return -1 50 | } 51 | 52 | nb := nb_num / nb_denom 53 | na_denom := m.A.X 54 | na_num := m.p.X - nb*m.B.X 55 | 56 | if na_num%na_denom != 0 { 57 | return -1 58 | } 59 | na := na_num / na_denom 60 | return na*a_cost + nb*b_cost 61 | } 62 | 63 | func parseMachines(input string) []machine { 64 | machines_raw := strings.Split(input, "\n\n") 65 | machines := make([]machine, len(machines_raw)) 66 | 67 | for i, machine_raw := range machines_raw { 68 | rows := strings.Split(machine_raw, "\n") 69 | m := machine{} 70 | for j, row := range rows { 71 | if j == 0 { 72 | m.A = parseButton(row) 73 | } else if j == 1 { 74 | m.B = parseButton(row) 75 | } else { 76 | m.p = parsePrize(row) 77 | } 78 | } 79 | machines[i] = m 80 | } 81 | return machines 82 | } 83 | 84 | func parseButton(row string) button { 85 | elements := strings.Split(row[10:], ", ") 86 | X, _ := strconv.Atoi(elements[0][2:]) 87 | Y, _ := strconv.Atoi(elements[1][2:]) 88 | return button{X, Y} 89 | } 90 | func parsePrize(row string) prize { 91 | elements := strings.Split(row[7:], ", ") 92 | X, _ := strconv.Atoi(elements[0][2:]) 93 | Y, _ := strconv.Atoi(elements[1][2:]) 94 | return prize{X + prize_add, Y + prize_add} 95 | } 96 | -------------------------------------------------------------------------------- /2024/day14_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | n_total_rows int = 103 12 | n_total_cols int = 101 13 | n_seconds int = 100 14 | ) 15 | 16 | type pos struct { 17 | x, y int 18 | } 19 | type velocity struct { 20 | x, y int 21 | } 22 | type robot struct { 23 | p pos 24 | v velocity 25 | } 26 | 27 | func main() { 28 | input, _ := os.ReadFile("2024/day14_pt1/input.txt") 29 | rows := strings.Split(string(input), "\n") 30 | // robots := make([]robot, len(rows)) 31 | robot_positions := make(map[pos]int) 32 | 33 | for i := range len(rows) { 34 | r := parseRobot(rows[i]) 35 | p := calculatePos(r, n_seconds) 36 | _, is_occupied := robot_positions[p] 37 | if !is_occupied { 38 | robot_positions[p] = 0 39 | } 40 | robot_positions[p] += 1 41 | } 42 | fmt.Println(calculateSafety(robot_positions)) 43 | } 44 | 45 | func calculateSafety(r_pos map[pos]int) int { 46 | total_safety := 1 47 | q_safety := []int{0, 0, 0, 0} 48 | for rp, cnt := range r_pos { 49 | q_idx := -1 50 | if (rp.x < n_total_cols/2) && (rp.y < n_total_rows/2) { 51 | q_idx = 0 52 | } else if (rp.x > n_total_cols/2) && (rp.y < n_total_rows/2) { 53 | q_idx = 1 54 | } else if (rp.x < n_total_cols/2) && (rp.y > n_total_rows/2) { 55 | q_idx = 2 56 | } else if (rp.x > n_total_cols/2) && (rp.y > n_total_rows/2) { 57 | // Q1 58 | q_idx = 3 59 | } 60 | 61 | if q_idx >= 0 { 62 | q_safety[q_idx] += cnt 63 | } 64 | } 65 | 66 | for _, s := range q_safety { 67 | total_safety *= s 68 | } 69 | return total_safety 70 | } 71 | 72 | func calculatePos(r robot, secs int) pos { 73 | next_x := (r.p.x + secs*r.v.x) % n_total_cols 74 | next_y := (r.p.y + secs*r.v.y) % n_total_rows 75 | 76 | if next_x < 0 { 77 | next_x += n_total_cols 78 | } 79 | if next_y < 0 { 80 | next_y += n_total_rows 81 | } 82 | return pos{next_x, next_y} 83 | } 84 | 85 | func parseRobot(row string) robot { 86 | elems := strings.Split(row, " ") 87 | 88 | p_raw := strings.Split(elems[0][2:], ",") 89 | px, _ := strconv.Atoi(p_raw[0]) 90 | py, _ := strconv.Atoi(p_raw[1]) 91 | 92 | v_raw := strings.Split(elems[1][2:], ",") 93 | vx, _ := strconv.Atoi(v_raw[0]) 94 | vy, _ := strconv.Atoi(v_raw[1]) 95 | 96 | return robot{pos{px, py}, velocity{vx, vy}} 97 | } 98 | -------------------------------------------------------------------------------- /2024/day14_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | n_total_rows int = 103 12 | n_total_cols int = 101 13 | ) 14 | 15 | type pos struct { 16 | x, y int 17 | } 18 | type velocity struct { 19 | x, y int 20 | } 21 | type robot struct { 22 | p pos 23 | v velocity 24 | } 25 | 26 | func main() { 27 | input, _ := os.ReadFile("2024/day14_pt2/input.txt") 28 | rows := strings.Split(string(input), "\n") 29 | robots := make([]robot, len(rows)) 30 | for i := range len(rows) { 31 | r := parseRobot(rows[i]) 32 | robots[i] = r 33 | } 34 | 35 | max_symmetrical := 0 36 | max_symmetrical_seconds := 0 37 | 38 | for n_seconds := range 200000 { 39 | robot_positions := calculateMap(robots, n_seconds) 40 | n_symmetrical := checkMap(robot_positions) 41 | if n_symmetrical > max_symmetrical { 42 | max_symmetrical = n_symmetrical 43 | max_symmetrical_seconds = n_seconds 44 | } 45 | fmt.Printf("# Symmetrical after %d seconds: %d out of %d\n", n_seconds, n_symmetrical, len(robot_positions)) 46 | } 47 | 48 | fmt.Println(max_symmetrical, max_symmetrical_seconds) 49 | robot_positions := calculateMap(robots, max_symmetrical_seconds) 50 | fmt.Println(drawMap(robot_positions)) 51 | } 52 | 53 | func drawMap(robot_positions map[pos]int) string { 54 | full_map := make([]string, n_total_rows) 55 | for row := range n_total_rows { 56 | str := "" 57 | for col := range n_total_cols { 58 | _, exists := robot_positions[pos{col, row}] 59 | if exists { 60 | str += "*" 61 | } else { 62 | str += "." 63 | } 64 | } 65 | full_map[row] = str 66 | } 67 | return strings.Join(full_map, "\n") 68 | } 69 | 70 | func checkMap(robot_positions map[pos]int) int { 71 | n_symmetrical := 0 72 | for r := range robot_positions { 73 | _, exists := robot_positions[pos{n_total_cols - r.x, r.y}] 74 | if exists { 75 | n_symmetrical += 1 76 | } 77 | } 78 | return n_symmetrical / 2 79 | } 80 | 81 | func calculateMap(robots []robot, n_seconds int) map[pos]int { 82 | robot_positions := make(map[pos]int) 83 | for i := range len(robots) { 84 | r := robots[i] 85 | p := calculatePos(r, n_seconds) 86 | _, is_occupied := robot_positions[p] 87 | if !is_occupied { 88 | robot_positions[p] = 0 89 | } 90 | robot_positions[p] += 1 91 | } 92 | return robot_positions 93 | } 94 | 95 | func calculatePos(r robot, secs int) pos { 96 | next_x := (r.p.x + secs*r.v.x) % n_total_cols 97 | next_y := (r.p.y + secs*r.v.y) % n_total_rows 98 | 99 | if next_x < 0 { 100 | next_x += n_total_cols 101 | } 102 | if next_y < 0 { 103 | next_y += n_total_rows 104 | } 105 | return pos{next_x, next_y} 106 | } 107 | 108 | func parseRobot(row string) robot { 109 | elems := strings.Split(row, " ") 110 | 111 | p_raw := strings.Split(elems[0][2:], ",") 112 | px, _ := strconv.Atoi(p_raw[0]) 113 | py, _ := strconv.Atoi(p_raw[1]) 114 | 115 | v_raw := strings.Split(elems[1][2:], ",") 116 | vx, _ := strconv.Atoi(v_raw[0]) 117 | vy, _ := strconv.Atoi(v_raw[1]) 118 | 119 | return robot{pos{px, py}, velocity{vx, vy}} 120 | } 121 | -------------------------------------------------------------------------------- /2024/day15_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type direction struct { 10 | v, h int 11 | } 12 | 13 | type position struct { 14 | row, col int 15 | } 16 | 17 | type field struct { 18 | field []string 19 | boxes map[position]bool 20 | } 21 | 22 | func main() { 23 | file_name := "2024/day15_pt1/input.txt" 24 | input, err := os.ReadFile(file_name) 25 | if err != nil { 26 | fmt.Printf("No such file %s\n", file_name) 27 | return 28 | } 29 | start, fld, directions := parseInput(string(input)) 30 | fmt.Println(start) 31 | fmt.Println("boxes", fld.boxes) 32 | fmt.Println("field\n", fld.field) 33 | end_pos := runRobot(start, &fld, directions) 34 | printField(end_pos, &fld) 35 | fmt.Println(getBoxScore(&fld)) 36 | } 37 | 38 | func getBoxScore(fld *field) int { 39 | total_score := 0 40 | for box := range (*fld).boxes { 41 | score := 100*box.row + box.col 42 | total_score += score 43 | } 44 | return total_score 45 | } 46 | 47 | func printField(pos position, fld *field) { 48 | print := make([]string, len((*fld).field)) 49 | for row := range len((*fld).field) { 50 | row_str := "" 51 | for col := range len((*fld).field[0]) { 52 | _, is_box := (*fld).boxes[position{row, col}] 53 | if string((*fld).field[row][col]) == "#" { 54 | row_str += "#" 55 | } else if (pos.row == row) && (pos.col == col) { 56 | row_str += "@" 57 | } else if is_box { 58 | row_str += "O" 59 | } else { 60 | row_str += "." 61 | } 62 | } 63 | print[row] = row_str 64 | } 65 | fmt.Println(strings.Join(print, "\n")) 66 | } 67 | 68 | func runRobot(start position, fld *field, directions []direction) position { 69 | pos := start 70 | for i, dir := range directions { 71 | can_do := checkStep(pos, fld, dir) 72 | if can_do { 73 | pos = makeStep(pos, fld, dir, false) 74 | } 75 | fmt.Printf("Step #%d (%d, %d) - %t - pos after (%d, %d)\n", i, dir.v, dir.h, can_do, pos.row, pos.col) 76 | } 77 | return pos 78 | } 79 | 80 | func checkStep(pos position, fld *field, dir direction) bool { 81 | next_pos := position{row: pos.row + dir.v, col: pos.col + dir.h} 82 | 83 | if (next_pos.row < 0) || (next_pos.row >= len((*fld).field)) { 84 | return false 85 | } 86 | if (next_pos.col < 0) || (next_pos.col >= len((*fld).field[0])) { 87 | return false 88 | } 89 | if string((*fld).field[next_pos.row][next_pos.col]) == "#" { 90 | return false 91 | } 92 | 93 | _, is_box := (*fld).boxes[next_pos] 94 | if is_box { 95 | return checkStep(next_pos, fld, dir) 96 | } 97 | 98 | return true 99 | } 100 | 101 | func makeStep(pos position, fld *field, dir direction, as_box bool) position { 102 | next_pos := position{row: pos.row + dir.v, col: pos.col + dir.h} 103 | 104 | _, is_box := (*fld).boxes[next_pos] 105 | if is_box { 106 | if !as_box { 107 | delete((*fld).boxes, next_pos) 108 | } 109 | makeStep(next_pos, fld, dir, true) 110 | } else if as_box && !is_box { 111 | (*fld).boxes[next_pos] = true 112 | } 113 | return next_pos 114 | } 115 | 116 | func parseInput(input string) (position, field, []direction) { 117 | parts := strings.Split(input, "\n\n") 118 | pos, fld := parseField(parts[0]) 119 | return pos, fld, parseDirections(parts[1]) 120 | } 121 | 122 | func parseDirections(dir_raw string) []direction { 123 | directions := make([]direction, len(dir_raw)) 124 | for i, sym := range dir_raw { 125 | if string(sym) == "<" { 126 | directions[i] = direction{v: 0, h: -1} 127 | } else if string(sym) == ">" { 128 | directions[i] = direction{v: 0, h: 1} 129 | } else if string(sym) == "^" { 130 | directions[i] = direction{v: -1, h: 0} 131 | } else if string(sym) == "v" { 132 | directions[i] = direction{v: 1, h: 0} 133 | } 134 | } 135 | return directions 136 | } 137 | 138 | func parseField(field_raw string) (position, field) { 139 | rows_raw := strings.Split(field_raw, "\n") 140 | fld := make([]string, len(rows_raw)) 141 | start := position{} 142 | boxes := make(map[position]bool) 143 | 144 | for r, row_raw := range rows_raw { 145 | row := "" 146 | for c, sym := range row_raw { 147 | if string(sym) == "@" { 148 | start.row = r 149 | start.col = c 150 | row += "." 151 | } else if string(sym) == "O" { 152 | boxes[position{r, c}] = true 153 | row += "." 154 | } else if string(sym) == "#" { 155 | row += "#" 156 | } else { 157 | row += "." 158 | } 159 | } 160 | fld[r] = row 161 | } 162 | return start, field{fld, boxes} 163 | } 164 | -------------------------------------------------------------------------------- /2024/day16_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "container/heap" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | step_point int = 1 12 | turn_point int = 1000 13 | inf_dist = 100000000000 14 | ) 15 | 16 | type direction struct { 17 | v, h int 18 | } 19 | 20 | type coord struct { 21 | row, col int 22 | } 23 | 24 | type node struct { 25 | c coord 26 | dir direction 27 | } 28 | 29 | type QueueNode struct { 30 | node node 31 | dist int 32 | index int 33 | } 34 | 35 | // https://pkg.go.dev/container/heap#example-package-PriorityQueue 36 | type PriorityQueue []*QueueNode 37 | 38 | func (pq PriorityQueue) Len() int { return len(pq) } 39 | 40 | func (pq PriorityQueue) Less(i, j int) bool { 41 | return pq[i].dist < pq[j].dist 42 | } 43 | 44 | func (pq PriorityQueue) Swap(i, j int) { 45 | pq[i], pq[j] = pq[j], pq[i] 46 | pq[i].index = i 47 | pq[j].index = j 48 | } 49 | 50 | func (pq *PriorityQueue) Push(x any) { 51 | n := len(*pq) 52 | item := x.(*QueueNode) 53 | item.index = n 54 | *pq = append(*pq, item) 55 | } 56 | 57 | func (pq *PriorityQueue) Pop() any { 58 | old := *pq 59 | n := len(old) 60 | item := old[n-1] 61 | old[n-1] = nil // don't stop the GC from reclaiming the item eventually 62 | item.index = -1 // for safety 63 | *pq = old[0 : n-1] 64 | return item 65 | } 66 | 67 | func (pq *PriorityQueue) update(item *QueueNode, n node, dist int) { 68 | item.node = n 69 | item.dist = dist 70 | heap.Fix(pq, item.index) 71 | } 72 | 73 | func main() { 74 | file_name := "2024/day16_pt1/input.txt" 75 | input, err := os.ReadFile(file_name) 76 | if err != nil { 77 | fmt.Println(err) 78 | return 79 | } 80 | 81 | start, end, field := parseInput(string(input)) 82 | fmt.Println(findMinDistance(start, end, field)) 83 | } 84 | 85 | func initQueue(start coord, field []string) (PriorityQueue, map[node]*QueueNode) { 86 | directions := []direction{{1, 0}, {-1, 0}, {0, 1}, {0, -1}} 87 | pq := make(PriorityQueue, len(field)*len(field[0])*len(directions)) 88 | links := make(map[node]*QueueNode) 89 | 90 | i := 0 91 | for row := range len(field) { 92 | for col := range len(field[0]) { 93 | for _, d := range directions { 94 | dist := inf_dist 95 | if (row == start.row) && (col == start.col) && (d.v == 0) && (d.h == 1) { 96 | dist = 0 97 | } 98 | links[node{coord{row, col}, d}] = &QueueNode{ 99 | node{coord{row, col}, d}, 100 | dist, 101 | i, 102 | } 103 | pq[i] = links[node{coord{row, col}, d}] 104 | i += 1 105 | } 106 | } 107 | } 108 | heap.Init(&pq) 109 | 110 | return pq, links 111 | } 112 | 113 | func findMinDistance(start coord, end coord, field []string) int { 114 | pq, links := initQueue(start, field) 115 | 116 | for { 117 | unvis_node := heap.Pop(&pq).(*QueueNode) 118 | fmt.Printf("Visiting node row %d, col %d, v %d, h %d - dist %d\n", 119 | unvis_node.node.c.row, unvis_node.node.c.col, 120 | unvis_node.node.dir.v, unvis_node.node.dir.h, unvis_node.dist) 121 | if (unvis_node.node.c.row == end.row) && (unvis_node.node.c.col == end.col) { 122 | return unvis_node.dist 123 | } 124 | 125 | neighbours := getNeighbours(unvis_node.node, field) 126 | fmt.Println("Neighbours: ", neighbours) 127 | for _, neighbour := range neighbours { 128 | n_link, _ := links[neighbour.node] 129 | new_dist := min(n_link.dist, unvis_node.dist+neighbour.dist) 130 | pq.update(n_link, n_link.node, new_dist) 131 | } 132 | } 133 | } 134 | 135 | func (d direction) getNeighbours() []direction { 136 | if d.h != 0 { 137 | return []direction{{v: 1, h: 0}, {v: -1, h: 0}} 138 | } 139 | return []direction{{v: 0, h: 1}, {v: 0, h: -1}} 140 | } 141 | 142 | func getNeighbours(n node, field []string) []QueueNode { 143 | neighbours := make([]QueueNode, 0) 144 | 145 | // 1. step option 146 | next_pos := coord{row: n.c.row + n.dir.v, col: n.c.col + n.dir.h} 147 | if (next_pos.row >= 0) && (next_pos.row < len(field)) && (next_pos.col >= 0) && (next_pos.col < len(field[0])) { 148 | // we're in the field 149 | if string(field[next_pos.row][next_pos.col]) != "#" { 150 | // can make a step 151 | neighbours = append(neighbours, QueueNode{node{next_pos, n.dir}, step_point, -1}) 152 | } 153 | } 154 | 155 | // 2. turn option 156 | for _, new_dir := range n.dir.getNeighbours() { 157 | neighbours = append(neighbours, QueueNode{node{n.c, new_dir}, turn_point, -1}) 158 | } 159 | 160 | return neighbours 161 | } 162 | 163 | func parseInput(input string) (coord, coord, []string) { 164 | field := strings.Split(input, "\n") 165 | start := coord{} 166 | end := coord{} 167 | 168 | for row := range len(field) { 169 | for col := range len(field[0]) { 170 | if string(field[row][col]) == "S" { 171 | start.row = row 172 | start.col = col 173 | } else if string(field[row][col]) == "E" { 174 | end.row = row 175 | end.col = col 176 | } 177 | } 178 | } 179 | return start, end, field 180 | } 181 | -------------------------------------------------------------------------------- /2024/day16_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "container/heap" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | step_point int = 1 12 | turn_point int = 1000 13 | inf_dist = 100000000000 14 | ) 15 | 16 | type direction struct { 17 | v, h int 18 | } 19 | 20 | type coord struct { 21 | row, col int 22 | } 23 | 24 | type node struct { 25 | c coord 26 | dir direction 27 | } 28 | 29 | type QueueNode struct { 30 | node node 31 | dist int 32 | index int 33 | } 34 | 35 | // https://pkg.go.dev/container/heap#example-package-PriorityQueue 36 | type PriorityQueue []*QueueNode 37 | 38 | func (pq PriorityQueue) Len() int { return len(pq) } 39 | 40 | func (pq PriorityQueue) Less(i, j int) bool { 41 | return pq[i].dist < pq[j].dist 42 | } 43 | 44 | func (pq PriorityQueue) Swap(i, j int) { 45 | pq[i], pq[j] = pq[j], pq[i] 46 | pq[i].index = i 47 | pq[j].index = j 48 | } 49 | 50 | func (pq *PriorityQueue) Push(x any) { 51 | n := len(*pq) 52 | item := x.(*QueueNode) 53 | item.index = n 54 | *pq = append(*pq, item) 55 | } 56 | 57 | func (pq *PriorityQueue) Pop() any { 58 | old := *pq 59 | n := len(old) 60 | item := old[n-1] 61 | old[n-1] = nil // don't stop the GC from reclaiming the item eventually 62 | item.index = -1 // for safety 63 | *pq = old[0 : n-1] 64 | return item 65 | } 66 | 67 | func (pq *PriorityQueue) update(item *QueueNode, n node, dist int) { 68 | item.node = n 69 | item.dist = dist 70 | heap.Fix(pq, item.index) 71 | } 72 | 73 | func main() { 74 | file_name := "2024/day16_pt2/input.txt" 75 | input, err := os.ReadFile(file_name) 76 | if err != nil { 77 | fmt.Println(err) 78 | return 79 | } 80 | 81 | start, end, field := parseInput(string(input)) 82 | links, prev := findMinDistance(start, field) 83 | fmt.Println(searchPaths(links, prev, end)) 84 | } 85 | 86 | func initQueue(start coord, field []string) (PriorityQueue, map[node]*QueueNode, map[node][]node) { 87 | directions := []direction{{1, 0}, {-1, 0}, {0, 1}, {0, -1}} 88 | pq := make(PriorityQueue, len(field)*len(field[0])*len(directions)) 89 | links := make(map[node]*QueueNode) 90 | prev := make(map[node][]node) 91 | 92 | i := 0 93 | for row := range len(field) { 94 | for col := range len(field[0]) { 95 | for _, d := range directions { 96 | dist := inf_dist 97 | if (row == start.row) && (col == start.col) && (d.v == 0) && (d.h == 1) { 98 | dist = 0 99 | } 100 | links[node{coord{row, col}, d}] = &QueueNode{ 101 | node{coord{row, col}, d}, 102 | dist, 103 | i, 104 | } 105 | pq[i] = links[node{coord{row, col}, d}] 106 | i += 1 107 | } 108 | } 109 | } 110 | heap.Init(&pq) 111 | 112 | return pq, links, prev 113 | } 114 | 115 | func findMinDistance(start coord, field []string) (map[node]*QueueNode, map[node][]node) { 116 | pq, links, prev := initQueue(start, field) 117 | 118 | for pq.Len() > 0 { 119 | unvis_node := heap.Pop(&pq).(*QueueNode) 120 | fmt.Printf("Visiting node row %d, col %d, v %d, h %d - dist %d\n", 121 | unvis_node.node.c.row, unvis_node.node.c.col, 122 | unvis_node.node.dir.v, unvis_node.node.dir.h, unvis_node.dist) 123 | 124 | neighbours := getNeighbours(unvis_node.node, field) 125 | fmt.Println("Neighbours: ", neighbours) 126 | for _, neighbour := range neighbours { 127 | n_link, _ := links[neighbour.node] 128 | new_dist := unvis_node.dist + neighbour.dist 129 | if new_dist < n_link.dist { 130 | pq.update(n_link, n_link.node, new_dist) 131 | prev[n_link.node] = []node{unvis_node.node} 132 | } else if new_dist == n_link.dist { 133 | val, _ := prev[n_link.node] 134 | prev[n_link.node] = append(val, unvis_node.node) 135 | } 136 | } 137 | } 138 | return links, prev 139 | } 140 | 141 | func searchPaths(links map[node]*QueueNode, prev map[node][]node, end coord) int { 142 | directions := []direction{{1, 0}, {-1, 0}, {0, 1}, {0, -1}} 143 | shortest_paths := make(map[coord]bool) 144 | min_dist := inf_dist 145 | nodes := make([]node, 0) 146 | 147 | for _, d := range directions { 148 | qn, exists := links[node{end, d}] 149 | if exists { 150 | min_dist = min(min_dist, qn.dist) 151 | } 152 | } 153 | for _, d := range directions { 154 | qn, exists := links[node{end, d}] 155 | if exists && (qn.dist == min_dist) { 156 | nodes = append(nodes, qn.node) 157 | } 158 | } 159 | 160 | for len(nodes) > 0 { 161 | n := nodes[0] 162 | nodes = nodes[1:] 163 | shortest_paths[n.c] = true 164 | 165 | prev_nodes, exists := prev[n] 166 | if exists { 167 | for _, pn := range prev_nodes { 168 | nodes = append(nodes, pn) 169 | } 170 | } 171 | } 172 | return len(shortest_paths) 173 | } 174 | 175 | func (d direction) getNeighbours() []direction { 176 | if d.h != 0 { 177 | return []direction{{v: 1, h: 0}, {v: -1, h: 0}} 178 | } 179 | return []direction{{v: 0, h: 1}, {v: 0, h: -1}} 180 | } 181 | 182 | func getNeighbours(n node, field []string) []QueueNode { 183 | neighbours := make([]QueueNode, 0) 184 | 185 | // 1. step option 186 | next_pos := coord{row: n.c.row + n.dir.v, col: n.c.col + n.dir.h} 187 | if (next_pos.row >= 0) && (next_pos.row < len(field)) && (next_pos.col >= 0) && (next_pos.col < len(field[0])) { 188 | // we're in the field 189 | if string(field[next_pos.row][next_pos.col]) != "#" { 190 | // can make a step 191 | neighbours = append(neighbours, QueueNode{node{next_pos, n.dir}, step_point, -1}) 192 | } 193 | } 194 | 195 | // 2. turn option 196 | for _, new_dir := range n.dir.getNeighbours() { 197 | neighbours = append(neighbours, QueueNode{node{n.c, new_dir}, turn_point, -1}) 198 | } 199 | 200 | return neighbours 201 | } 202 | 203 | func parseInput(input string) (coord, coord, []string) { 204 | field := strings.Split(input, "\n") 205 | start := coord{} 206 | end := coord{} 207 | 208 | for row := range len(field) { 209 | for col := range len(field[0]) { 210 | if string(field[row][col]) == "S" { 211 | start.row = row 212 | start.col = col 213 | } else if string(field[row][col]) == "E" { 214 | end.row = row 215 | end.col = col 216 | } 217 | } 218 | } 219 | return start, end, field 220 | } 221 | -------------------------------------------------------------------------------- /2024/day17_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | type status struct { 11 | A, B, C int 12 | i int 13 | output []int 14 | } 15 | 16 | func pow(n, m int) int { 17 | if m == 0 { 18 | return 1 19 | } 20 | if m == 1 { 21 | return n 22 | } 23 | result := n 24 | for i := 2; i <= m; i++ { 25 | result *= n 26 | } 27 | return result 28 | } 29 | 30 | func main() { 31 | file_name := "2024/day17_pt1/input.txt" 32 | input, err := os.ReadFile(file_name) 33 | if err != nil { 34 | fmt.Println(err) 35 | return 36 | } 37 | program_raw, program := parseProgram(string(input)) 38 | 39 | register := parseRegisters(string(input)) 40 | output := register.runProgram(program) 41 | output_str := formatOutput(output) 42 | fmt.Println("output: ", output_str) 43 | fmt.Println("should be:", program_raw) 44 | } 45 | 46 | func parseRegisters(input string) status { 47 | registers := strings.Split(input, "\n")[:3] 48 | A, _ := strconv.Atoi(registers[0][12:]) 49 | B, _ := strconv.Atoi(registers[1][12:]) 50 | C, _ := strconv.Atoi(registers[2][12:]) 51 | output := make([]int, 0) 52 | return status{A, B, C, 0, output} 53 | } 54 | 55 | func parseProgram(input string) (string, []int) { 56 | rows := strings.Split(input, "\n") 57 | program_raw := rows[len(rows)-1][9:] 58 | command_raw := strings.Split(program_raw, ",") 59 | commands := make([]int, len(command_raw)) 60 | 61 | for i, sym := range command_raw { 62 | num, _ := strconv.Atoi(sym) 63 | commands[i] = num 64 | } 65 | return program_raw, commands 66 | } 67 | 68 | func (s *status) runProgram(program []int) []int { 69 | for s.i < len(program) { 70 | s.applyCommand(program[s.i], program[s.i+1]) 71 | } 72 | return s.output 73 | } 74 | 75 | func (s *status) inc() { 76 | s.i += 2 77 | } 78 | 79 | func (s *status) getCombo(operand int) int { 80 | res := -1 81 | if operand <= 3 { 82 | res = operand 83 | } else if operand == 4 { 84 | res = s.A 85 | } else if operand == 5 { 86 | res = s.B 87 | } else if operand == 6 { 88 | res = s.C 89 | } else { 90 | panic("Unexpected operand value") 91 | } 92 | return res 93 | } 94 | 95 | func (s *status) applyCommand(command int, operand int) { 96 | fmt.Printf("\nRunning command %d, operand %d:\tA %d, B %d, C %d\n", command, operand, s.A, s.B, s.C) 97 | if command == 0 { 98 | // division -> A 99 | num := s.A 100 | denom := pow(2, s.getCombo(operand)) 101 | s.A = num / denom 102 | s.inc() 103 | } else if command == 1 { 104 | // bitwise XOR 105 | s.B = s.B ^ operand 106 | s.inc() 107 | } else if command == 2 { 108 | // modulo 8 109 | s.B = s.getCombo(operand) % 8 110 | s.inc() 111 | } else if command == 3 { 112 | // nothing or jump 113 | if s.A == 0 { 114 | s.inc() 115 | return 116 | } else { 117 | s.i = operand 118 | } 119 | } else if command == 4 { 120 | // bitwise XOR B ^ C 121 | s.B = s.B ^ s.C 122 | s.inc() 123 | } else if command == 5 { 124 | // modulo 8 + output 125 | s.output = append(s.output, s.getCombo(operand)%8) 126 | s.inc() 127 | } else if command == 6 { 128 | // division -> B 129 | num := s.A 130 | denom := pow(2, s.getCombo(operand)) 131 | s.B = num / denom 132 | s.inc() 133 | } else if command == 7 { 134 | // division -> C 135 | num := s.A 136 | denom := pow(2, s.getCombo(operand)) 137 | s.C = num / denom 138 | s.inc() 139 | } else { 140 | panic("Unknown instruction!") 141 | } 142 | 143 | fmt.Printf("...result A %d, B %d, C %d, output: %v, index: %d\n", s.A, s.B, s.C, s.output, s.i) 144 | } 145 | 146 | func formatOutput(output []int) string { 147 | output_str := make([]string, len(output)) 148 | for i, num := range output { 149 | output_str[i] = strconv.Itoa(num) 150 | } 151 | return strings.Join(output_str, ",") 152 | } 153 | -------------------------------------------------------------------------------- /2024/day17_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "slices" 6 | ) 7 | 8 | func pow(n, m int) int { 9 | if m == 0 { 10 | return 1 11 | } 12 | if m == 1 { 13 | return n 14 | } 15 | result := n 16 | for i := 2; i <= m; i++ { 17 | result *= n 18 | } 19 | return result 20 | } 21 | 22 | type status struct { 23 | A int 24 | i int 25 | } 26 | 27 | func main() { 28 | program := []int{2, 4, 1, 5, 7, 5, 1, 6, 0, 3, 4, 6, 5, 5, 3, 0} 29 | slices.Reverse(program) 30 | 31 | queue := []status{{A: 0, i: 0}} 32 | 33 | for len(queue) > 0 { 34 | s := queue[0] 35 | queue = queue[1:] 36 | 37 | for A_modulo_8 := range 8 { 38 | A := 8*s.A + A_modulo_8 39 | B_cand := A_modulo_8 ^ 5 ^ 6 ^ (A / pow(2, A_modulo_8^5)) 40 | if B_cand%8 == program[s.i] { 41 | if s.i+1 == len(program) { 42 | fmt.Printf("A: %d\tB: %d\tOUT: %d\n", A, B_cand, B_cand%8) 43 | } else { 44 | queue = append(queue, status{A: A, i: s.i + 1}) 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /2024/day18_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | field_size int = 71 12 | ) 13 | 14 | type coord struct { 15 | row, col int 16 | } 17 | type direction struct { 18 | v, h int 19 | } 20 | type bt struct { 21 | c coord 22 | ts int 23 | } 24 | 25 | func main() { 26 | file_name := "2024/day18_pt1/input.txt" 27 | input, err := os.ReadFile(file_name) 28 | if err != nil { 29 | fmt.Println(err) 30 | return 31 | } 32 | bts := parseBytes(string(input)) 33 | for i := 1024; i < len(bts); i++ { 34 | fmt.Println(i) 35 | bt_set := makeSet(bts, i) 36 | if findShortestPath(&bt_set, field_size, field_size) < 0 { 37 | fmt.Printf("%d,%d\n", bts[i-1].c.col, bts[i-1].c.row) 38 | break 39 | } 40 | } 41 | } 42 | 43 | func parseBytes(input string) []bt { 44 | rows := strings.Split(input, "\n") 45 | bts := make([]bt, len(rows)) 46 | for i, row := range rows { 47 | coords_str := strings.Split(row, ",") 48 | row, _ := strconv.Atoi(coords_str[1]) 49 | col, _ := strconv.Atoi(coords_str[0]) 50 | bts[i] = bt{coord{row, col}, i} 51 | } 52 | return bts 53 | } 54 | 55 | func makeSet(bts []bt, n_first int) map[coord]bool { 56 | bt_set := make(map[coord]bool) 57 | for i := range n_first { 58 | bt_set[bts[i].c] = true 59 | } 60 | return bt_set 61 | } 62 | 63 | func findShortestPath(bt_set *map[coord]bool, total_rows, total_cols int) int { 64 | queue := []bt{{coord{0, 0}, 0}} 65 | visited := make(map[coord]bool) 66 | visited[coord{0, 0}] = true 67 | finish := coord{total_rows - 1, total_cols - 1} 68 | 69 | directions := []direction{{0, 1}, {0, -1}, {1, 0}, {-1, 0}} 70 | 71 | for len(queue) > 0 { 72 | pos := queue[0] 73 | queue = queue[1:] 74 | 75 | if (pos.c.row == finish.row) && (pos.c.col == finish.col) { 76 | return pos.ts 77 | } 78 | 79 | for _, d := range directions { 80 | next_pos := coord{pos.c.row + d.v, pos.c.col + d.h} 81 | if (next_pos.row < 0) || (next_pos.row >= total_rows) || (next_pos.col < 0) || (next_pos.col >= total_cols) { 82 | continue 83 | } 84 | _, is_visited := visited[next_pos] 85 | _, is_obstacle := (*bt_set)[next_pos] 86 | if is_visited || is_obstacle { 87 | continue 88 | } 89 | visited[next_pos] = true 90 | queue = append(queue, bt{next_pos, pos.ts + 1}) 91 | } 92 | } 93 | return -1 94 | } 95 | -------------------------------------------------------------------------------- /2024/day19_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | func main() { 10 | file_name := "2024/day19_pt1/input.txt" 11 | input, err := os.ReadFile(file_name) 12 | if err != nil { 13 | fmt.Println(err) 14 | return 15 | } 16 | 17 | designs := parseDesigns(string(input)) 18 | towels := parseTowels(string(input)) 19 | n_possible := 0 20 | fmt.Println("Towels:", towels) 21 | 22 | for i, design := range designs { 23 | is_possible := checkDesign(design, towels, 0) 24 | fmt.Printf("Design %d: %s -> %t\n", i, design, is_possible) 25 | if is_possible { 26 | n_possible += 1 27 | } 28 | } 29 | fmt.Println(n_possible) 30 | } 31 | 32 | func parseTowels(input string) []string { 33 | return strings.Split(strings.Split(input, "\n\n")[0], ", ") 34 | } 35 | 36 | func parseDesigns(input string) []string { 37 | return strings.Split(strings.Split(input, "\n\n")[1], "\n") 38 | } 39 | 40 | func checkDesign(design string, towels []string, index int) bool { 41 | if index == len(design) { 42 | return true 43 | } 44 | 45 | can_continue := false 46 | for _, towel := range towels { 47 | if strings.HasPrefix(design[index:], towel) { 48 | can_continue = can_continue || checkDesign(design, towels, index+len(towel)) 49 | } 50 | if can_continue { 51 | break 52 | } 53 | } 54 | return can_continue 55 | } 56 | -------------------------------------------------------------------------------- /2024/day19_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type status struct { 10 | index int 11 | prefix string 12 | } 13 | 14 | func main() { 15 | file_name := "2024/day19_pt2/input.txt" 16 | input, err := os.ReadFile(file_name) 17 | if err != nil { 18 | fmt.Println(err) 19 | return 20 | } 21 | 22 | designs := parseDesigns(string(input)) 23 | towels := parseTowels(string(input)) 24 | n_possible := 0 25 | fmt.Println("Towels:", towels) 26 | 27 | for i, design := range designs { 28 | statuses := make(map[status]int) 29 | possible_options := countDesignOptions(design, towels, 0, &statuses) 30 | fmt.Printf("Design %d: %s -> %d\n", i, design, possible_options) 31 | n_possible += possible_options 32 | } 33 | fmt.Println(n_possible) 34 | } 35 | 36 | func parseTowels(input string) []string { 37 | return strings.Split(strings.Split(input, "\n\n")[0], ", ") 38 | } 39 | 40 | func parseDesigns(input string) []string { 41 | return strings.Split(strings.Split(input, "\n\n")[1], "\n") 42 | } 43 | 44 | func countDesignOptions(design string, towels []string, index int, statuses *map[status]int) int { 45 | if index == len(design) { 46 | return 1 47 | } 48 | 49 | design_options := 0 50 | for _, towel := range towels { 51 | if strings.HasPrefix(design[index:], towel) { 52 | n_options, has_value := (*statuses)[status{index: index, prefix: towel}] 53 | if !has_value { 54 | n_options = countDesignOptions(design, towels, index+len(towel), statuses) 55 | (*statuses)[status{index: index, prefix: towel}] = n_options 56 | } 57 | design_options += n_options 58 | } 59 | } 60 | return design_options 61 | } 62 | -------------------------------------------------------------------------------- /2024/day20_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "slices" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | cheat_steps int = 2 12 | n_saved = 100 13 | ) 14 | 15 | type coord struct { 16 | row, col int 17 | } 18 | 19 | type direction struct { 20 | v, h int 21 | } 22 | 23 | type step struct { 24 | c coord 25 | n int 26 | } 27 | 28 | func main() { 29 | file_name := "2024/day20_pt1/input.txt" 30 | input, err := os.ReadFile(file_name) 31 | if err != nil { 32 | fmt.Println(err) 33 | return 34 | } 35 | start, end, field := parseField(string(input)) 36 | n_steps_normal, _ := countStepsNormal(field, start)[end] 37 | fmt.Println("# steps without cheats: ", n_steps_normal) 38 | cheats := countCheats(field, start, end) 39 | 40 | printCheats(&cheats, n_steps_normal) 41 | 42 | } 43 | 44 | func parseField(input string) (coord, coord, []string) { 45 | field := strings.Split(input, "\n") 46 | start := coord{} 47 | end := coord{} 48 | 49 | for i, row := range field { 50 | for j, sym := range row { 51 | if string(sym) == "S" { 52 | start.row = i 53 | start.col = j 54 | } 55 | 56 | if string(sym) == "E" { 57 | end.row = i 58 | end.col = j 59 | } 60 | } 61 | } 62 | 63 | return start, end, field 64 | } 65 | 66 | func countStepsNormal(field []string, from coord) map[coord]int { 67 | queue := []step{{from, 0}} 68 | visited := make(map[coord]bool) 69 | visited[queue[0].c] = true 70 | distances := make(map[coord]int) 71 | 72 | directions := []direction{{0, 1}, {0, -1}, {1, 0}, {-1, 0}} 73 | 74 | for len(queue) > 0 { 75 | pos := queue[0].c 76 | n_steps := queue[0].n 77 | queue = queue[1:] 78 | 79 | distances[pos] = n_steps 80 | 81 | for _, d := range directions { 82 | next_coord := coord{pos.row + d.v, pos.col + d.h} 83 | 84 | if (next_coord.row < 0) || (next_coord.row >= len(field)) || (next_coord.col < 0) || (next_coord.col >= len(field[0])) { 85 | continue 86 | } 87 | if string(field[next_coord.row][next_coord.col]) == "#" { 88 | continue 89 | } 90 | _, is_visited := visited[next_coord] 91 | if is_visited { 92 | continue 93 | } 94 | queue = append(queue, step{next_coord, n_steps + 1}) 95 | visited[next_coord] = true 96 | } 97 | } 98 | 99 | return distances 100 | } 101 | 102 | func findAllCheats(field []string, pos coord) map[coord]bool { 103 | cheats := make(map[coord]bool) 104 | queue := []step{{pos, 0}} 105 | 106 | for len(queue) > 0 { 107 | c := queue[0].c 108 | n := queue[0].n 109 | queue = queue[1:] 110 | 111 | if n == cheat_steps { 112 | if c != pos { 113 | cheats[c] = true 114 | } 115 | continue 116 | } 117 | 118 | directions := []direction{{0, 1}, {0, -1}, {1, 0}, {-1, 0}} 119 | for _, d := range directions { 120 | next_c := coord{c.row + d.v, c.col + d.h} 121 | 122 | if (next_c.row < 0) || (next_c.row >= len(field)) || (next_c.col < 0) || (next_c.col >= len(field[0])) { 123 | continue 124 | } 125 | if (n == 0) && (string(field[next_c.row][next_c.col]) != "#") { 126 | continue 127 | } 128 | if (n == 1) && (string(field[next_c.row][next_c.col]) == "#") { 129 | continue 130 | } 131 | 132 | queue = append(queue, step{next_c, n + 1}) 133 | } 134 | } 135 | return cheats 136 | } 137 | 138 | func countCheats(field []string, start, end coord) map[int]int { 139 | map_to_start := countStepsNormal(field, start) 140 | map_to_end := countStepsNormal(field, end) 141 | cheat_secs := make(map[int]int) 142 | 143 | for row := range len(field) { 144 | for col := range len(field[0]) { 145 | pos := coord{row, col} 146 | steps_to_here, path_exists := map_to_start[pos] 147 | if !path_exists { 148 | continue 149 | } 150 | 151 | cheats := findAllCheats(field, pos) 152 | for cheat := range cheats { 153 | steps_to_finish, path_exists := map_to_end[cheat] 154 | if !path_exists { 155 | continue 156 | } 157 | total_steps := steps_to_here + 2 + steps_to_finish 158 | value, has_value := cheat_secs[total_steps] 159 | if !has_value { 160 | value = 0 161 | } 162 | cheat_secs[total_steps] = value + 1 163 | } 164 | } 165 | } 166 | return cheat_secs 167 | } 168 | 169 | func printCheats(cheats *map[int]int, n_steps_normal int) { 170 | n_good_cheats := 0 171 | steps_sorted := make([]int, len(*cheats)) 172 | i := 0 173 | for steps := range *cheats { 174 | steps_sorted[i] = steps 175 | i += 1 176 | } 177 | slices.Sort(steps_sorted) 178 | slices.Reverse(steps_sorted) 179 | for _, steps := range steps_sorted { 180 | cnt, _ := (*cheats)[steps] 181 | if steps < n_steps_normal { 182 | fmt.Printf("%d cheats save %d seconds\n", cnt, n_steps_normal-steps) 183 | } 184 | if n_steps_normal-steps >= n_saved { 185 | n_good_cheats += cnt 186 | } 187 | } 188 | fmt.Printf("Cheats saving >=%d seconds: %d\n", n_saved, n_good_cheats) 189 | } 190 | -------------------------------------------------------------------------------- /2024/day20_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "slices" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | cheat_steps int = 20 12 | n_saved = 100 13 | ) 14 | 15 | type coord struct { 16 | row, col int 17 | } 18 | 19 | type direction struct { 20 | v, h int 21 | } 22 | 23 | type step struct { 24 | c coord 25 | n int 26 | } 27 | 28 | func main() { 29 | file_name := "2024/day20_pt2/input.txt" 30 | input, err := os.ReadFile(file_name) 31 | if err != nil { 32 | fmt.Println(err) 33 | return 34 | } 35 | start, end, field := parseField(string(input)) 36 | n_steps_normal, _ := countStepsNormal(field, start)[end] 37 | fmt.Println("# steps without cheats: ", n_steps_normal) 38 | cheats := countCheats(field, start, end) 39 | 40 | printCheats(&cheats, n_steps_normal) 41 | 42 | } 43 | 44 | func parseField(input string) (coord, coord, []string) { 45 | field := strings.Split(input, "\n") 46 | start := coord{} 47 | end := coord{} 48 | 49 | for i, row := range field { 50 | for j, sym := range row { 51 | if string(sym) == "S" { 52 | start.row = i 53 | start.col = j 54 | } 55 | 56 | if string(sym) == "E" { 57 | end.row = i 58 | end.col = j 59 | } 60 | } 61 | } 62 | 63 | return start, end, field 64 | } 65 | 66 | func countStepsNormal(field []string, from coord) map[coord]int { 67 | queue := []step{{from, 0}} 68 | visited := make(map[coord]bool) 69 | visited[queue[0].c] = true 70 | distances := make(map[coord]int) 71 | 72 | directions := []direction{{0, 1}, {0, -1}, {1, 0}, {-1, 0}} 73 | 74 | for len(queue) > 0 { 75 | pos := queue[0].c 76 | n_steps := queue[0].n 77 | queue = queue[1:] 78 | 79 | distances[pos] = n_steps 80 | 81 | for _, d := range directions { 82 | next_coord := coord{pos.row + d.v, pos.col + d.h} 83 | 84 | if (next_coord.row < 0) || (next_coord.row >= len(field)) || (next_coord.col < 0) || (next_coord.col >= len(field[0])) { 85 | continue 86 | } 87 | if string(field[next_coord.row][next_coord.col]) == "#" { 88 | continue 89 | } 90 | _, is_visited := visited[next_coord] 91 | if is_visited { 92 | continue 93 | } 94 | queue = append(queue, step{next_coord, n_steps + 1}) 95 | visited[next_coord] = true 96 | } 97 | } 98 | 99 | return distances 100 | } 101 | 102 | func findAllCheats(field []string, pos coord) map[coord]int { 103 | cheats := make(map[coord]int) 104 | visited := make(map[coord]bool) 105 | queue := []step{{pos, 0}} 106 | visited[pos] = true 107 | 108 | for len(queue) > 0 { 109 | c := queue[0].c 110 | n := queue[0].n 111 | queue = queue[1:] 112 | 113 | if n > cheat_steps { 114 | continue 115 | } 116 | 117 | if string(field[c.row][c.col]) != "#" { 118 | if c != pos { 119 | cheats[c] = n 120 | } 121 | } 122 | 123 | directions := []direction{{0, 1}, {0, -1}, {1, 0}, {-1, 0}} 124 | for _, d := range directions { 125 | next_c := coord{c.row + d.v, c.col + d.h} 126 | 127 | if (next_c.row < 0) || (next_c.row >= len(field)) || (next_c.col < 0) || (next_c.col >= len(field[0])) { 128 | continue 129 | } 130 | _, is_visited := visited[next_c] 131 | if is_visited { 132 | continue 133 | } 134 | visited[next_c] = true 135 | queue = append(queue, step{next_c, n + 1}) 136 | } 137 | } 138 | return cheats 139 | } 140 | 141 | func countCheats(field []string, start, end coord) map[int]int { 142 | map_to_start := countStepsNormal(field, start) 143 | map_to_end := countStepsNormal(field, end) 144 | cheat_secs := make(map[int]int) 145 | 146 | for row := range len(field) { 147 | for col := range len(field[0]) { 148 | pos := coord{row, col} 149 | steps_to_here, path_exists := map_to_start[pos] 150 | if !path_exists { 151 | continue 152 | } 153 | 154 | cheats := findAllCheats(field, pos) 155 | for cheat, cheat_steps := range cheats { 156 | steps_to_finish, path_exists := map_to_end[cheat] 157 | if !path_exists { 158 | continue 159 | } 160 | total_steps := steps_to_here + cheat_steps + steps_to_finish 161 | value, has_value := cheat_secs[total_steps] 162 | if !has_value { 163 | value = 0 164 | } 165 | cheat_secs[total_steps] = value + 1 166 | } 167 | } 168 | } 169 | return cheat_secs 170 | } 171 | 172 | func printCheats(cheats *map[int]int, n_steps_normal int) { 173 | n_good_cheats := 0 174 | steps_sorted := make([]int, len(*cheats)) 175 | i := 0 176 | for steps := range *cheats { 177 | steps_sorted[i] = steps 178 | i += 1 179 | } 180 | slices.Sort(steps_sorted) 181 | slices.Reverse(steps_sorted) 182 | for _, steps := range steps_sorted { 183 | cnt, _ := (*cheats)[steps] 184 | if steps < n_steps_normal { 185 | fmt.Printf("%d cheats save %d seconds\n", cnt, n_steps_normal-steps) 186 | } 187 | if n_steps_normal-steps >= n_saved { 188 | n_good_cheats += cnt 189 | } 190 | } 191 | fmt.Printf("Cheats saving >=%d seconds: %d\n", n_saved, n_good_cheats) 192 | } 193 | -------------------------------------------------------------------------------- /2024/day21/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | // +---+---+---+ 11 | // | 7 | 8 | 9 | 12 | // +---+---+---+ 13 | // | 4 | 5 | 6 | 14 | // +---+---+---+ 15 | // | 1 | 2 | 3 | 16 | // +---+---+---+ 17 | // | 0 | A | 18 | // +---+---+ 19 | 20 | // +---+---+ 21 | // | ^ | A | 22 | // +---+---+---+ 23 | // | < | v | > | 24 | // +---+---+---+ 25 | 26 | const ( 27 | inf int = 1000000000000 28 | n_robots int = 25 29 | ) 30 | 31 | type coord struct { 32 | row, col int 33 | } 34 | 35 | type direction struct { 36 | v, h int 37 | } 38 | 39 | var code2coord = map[string]coord{ 40 | "7": {0, 0}, 41 | "8": {0, 1}, 42 | "9": {0, 2}, 43 | "4": {1, 0}, 44 | "5": {1, 1}, 45 | "6": {1, 2}, 46 | "1": {2, 0}, 47 | "2": {2, 1}, 48 | "3": {2, 2}, 49 | "0": {3, 1}, 50 | "A": {3, 2}, 51 | } 52 | 53 | var digit_coords = map[coord]bool{ 54 | {0, 0}: true, 55 | {0, 1}: true, 56 | {0, 2}: true, 57 | {1, 0}: true, 58 | {1, 1}: true, 59 | {1, 2}: true, 60 | {2, 0}: true, 61 | {2, 1}: true, 62 | {2, 2}: true, 63 | {3, 1}: true, 64 | {3, 2}: true, 65 | } 66 | 67 | var arrow2coord = map[string]coord{ 68 | "^": {0, 1}, 69 | "A": {0, 2}, 70 | "<": {1, 0}, 71 | "v": {1, 1}, 72 | ">": {1, 2}, 73 | } 74 | 75 | var arrow_coords = map[coord]bool{ 76 | {0, 1}: true, 77 | {0, 2}: true, 78 | {1, 0}: true, 79 | {1, 1}: true, 80 | {1, 2}: true, 81 | } 82 | 83 | var direction2arrow = map[direction]string{ 84 | {1, 0}: "v", 85 | {-1, 0}: "^", 86 | {0, 1}: ">", 87 | {0, -1}: "<", 88 | } 89 | 90 | type status struct { 91 | from coord 92 | to coord 93 | level int 94 | } 95 | 96 | func main() { 97 | file_name := "2024/day21/input.txt" 98 | input, err := os.ReadFile(file_name) 99 | if err != nil { 100 | fmt.Println(err) 101 | return 102 | } 103 | precalc := make(map[status]int) 104 | codes := strings.Split(string(input), "\n") 105 | complexity := 0 106 | 107 | for _, code := range codes { 108 | fmt.Printf("--- CODE %s ----", code) 109 | code_parsed := parseCode(code) 110 | full_shortest_path := 0 111 | 112 | for i := range len(code_parsed) { 113 | from := coord{} 114 | if i == 0 { 115 | from = code2coord["A"] 116 | } else { 117 | from = code_parsed[i-1] 118 | } 119 | to := code_parsed[i] 120 | moves := makeMove(from, to, &digit_coords) 121 | shortest_path := inf 122 | fmt.Println("from:", from, "to:", to) 123 | for _, move := range moves { 124 | fmt.Println("Digit move:", move) 125 | shortest_path = min(shortest_path, getShortestPathLength(move, 0, n_robots, &precalc)) 126 | } 127 | full_shortest_path += shortest_path 128 | } 129 | 130 | fmt.Println(code, "--->", full_shortest_path) 131 | complexity += full_shortest_path * getNumber(code) 132 | } 133 | fmt.Println("Complexity:", complexity) 134 | } 135 | 136 | func abs(x int) int { 137 | if x >= 0 { 138 | return x 139 | } 140 | return -x 141 | } 142 | 143 | func parseCode(code string) []coord { 144 | coords := make([]coord, len(code)) 145 | for i, sym := range code { 146 | coords[i] = code2coord[string(sym)] 147 | } 148 | return coords 149 | } 150 | 151 | func getNumber(code string) int { 152 | num, err := strconv.Atoi(code[:len(code)-1]) 153 | if err != nil { 154 | fmt.Println(err) 155 | return -1 156 | } 157 | return num 158 | } 159 | 160 | func makeMove(from coord, to coord, all_coords *map[coord]bool) [][]direction { 161 | all_paths := make([][]direction, 0) 162 | d := direction{v: 0, h: 0} 163 | if from.row > to.row { 164 | d.v = -1 165 | } else if from.row < to.row { 166 | d.v = 1 167 | } 168 | if from.col > to.col { 169 | d.h = -1 170 | } else if from.col < to.col { 171 | d.h = 1 172 | } 173 | 174 | shortest_path := abs(from.row-to.row) + abs(from.col-to.col) 175 | path := make([]direction, 0) 176 | getMoves(from, to, shortest_path, all_coords, d, &path, &all_paths) 177 | return all_paths 178 | } 179 | 180 | func getMoves(pos, to coord, max_steps int, all_coords *map[coord]bool, d direction, path *[]direction, paths *[][]direction) { 181 | if (pos.row == to.row) && (pos.col == to.col) { 182 | // we're here 183 | final_path := make([]direction, len(*path)) 184 | copy(final_path, *path) 185 | *paths = append(*paths, final_path) 186 | return 187 | } 188 | 189 | if len(*path) == max_steps { 190 | return 191 | } 192 | 193 | next_positions := make([]coord, 0) 194 | l := len(*path) 195 | 196 | if d.h != 0 { 197 | next_pos := coord{pos.row, pos.col + d.h} 198 | _, is_allowed := (*all_coords)[next_pos] 199 | if is_allowed { 200 | next_positions = append(next_positions, next_pos) 201 | if is_allowed { 202 | *path = append(*path, direction{0, d.h}) 203 | getMoves(next_pos, to, max_steps, all_coords, d, path, paths) 204 | *path = (*path)[:l] 205 | } 206 | } 207 | } 208 | 209 | if d.v != 0 { 210 | next_pos := coord{pos.row + d.v, pos.col} 211 | _, is_allowed := (*all_coords)[next_pos] 212 | if is_allowed { 213 | *path = append(*path, direction{d.v, 0}) 214 | getMoves(next_pos, to, max_steps, all_coords, d, path, paths) 215 | *path = (*path)[:l] 216 | } 217 | } 218 | } 219 | 220 | func getShortestPathLength(move []direction, level, max_level int, precalc *map[status]int) int { 221 | defer fmt.Println(*precalc) 222 | if level == max_level { 223 | return len(move) + 1 224 | } 225 | 226 | min_path_length := 0 227 | var from, to coord 228 | 229 | for i := range len(move) + 1 { 230 | if i == 0 { 231 | from = arrow2coord["A"] 232 | } else { 233 | from = arrow2coord[direction2arrow[move[i-1]]] 234 | } 235 | if i == len(move) { 236 | to = arrow2coord["A"] 237 | } else { 238 | to = arrow2coord[direction2arrow[move[i]]] 239 | } 240 | 241 | min_path, is_calculated := (*precalc)[status{from, to, level}] 242 | if is_calculated { 243 | min_path_length += min_path 244 | continue 245 | } 246 | 247 | arrow_moves := makeMove(from, to, &arrow_coords) 248 | min_path = inf 249 | for _, arrow_move := range arrow_moves { 250 | min_path = min( 251 | min_path, 252 | getShortestPathLength(arrow_move, level+1, max_level, precalc), 253 | ) 254 | } 255 | 256 | min_path_length += min_path 257 | (*precalc)[status{from, to, level}] = min_path 258 | } 259 | return min_path_length 260 | } 261 | -------------------------------------------------------------------------------- /2024/day22_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | n_steps int = 2000 12 | prune_param int = 16777216 13 | ) 14 | 15 | func main() { 16 | file_name := "2024/day22_pt1/input.txt" 17 | input, err := os.ReadFile(file_name) 18 | if err != nil { 19 | fmt.Println(err) 20 | return 21 | } 22 | secrets := parseInitSecrets(strings.Split(string(input), "\n")) 23 | total_sum := 0 24 | 25 | for i, secret := range secrets { 26 | new_secret := runSecretEvolution(secret) 27 | total_sum += new_secret 28 | fmt.Printf("Secret #%d: %d -> %d\n", i, secret, new_secret) 29 | } 30 | fmt.Println(total_sum) 31 | } 32 | 33 | func parseInitSecrets(input []string) []int { 34 | secrets := make([]int, len(input)) 35 | for i, str := range input { 36 | secret, _ := strconv.Atoi(str) 37 | secrets[i] = secret 38 | } 39 | return secrets 40 | } 41 | 42 | func runSecretEvolution(secret int) int { 43 | for range n_steps { 44 | secret = mix_and_prune(secret, secret*64) 45 | secret = mix_and_prune(secret, secret/32) 46 | secret = mix_and_prune(secret, secret*2048) 47 | } 48 | return secret 49 | } 50 | 51 | func mix_and_prune(result, secret int) int { 52 | return (result ^ secret) % prune_param 53 | } 54 | -------------------------------------------------------------------------------- /2024/day22_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "strings" 8 | ) 9 | 10 | const ( 11 | n_steps int = 2000 12 | prune_param int = 16777216 13 | ) 14 | 15 | type pattern struct { 16 | n0, n1, n2, n3 int 17 | } 18 | 19 | func main() { 20 | file_name := "2024/day22_pt2/input.txt" 21 | input, err := os.ReadFile(file_name) 22 | if err != nil { 23 | fmt.Println(err) 24 | return 25 | } 26 | secrets := parseInitSecrets(strings.Split(string(input), "\n")) 27 | wins := make(map[pattern]int) 28 | 29 | for _, secret := range secrets { 30 | runSecretEvolution(secret, &wins) 31 | } 32 | 33 | max_win := -1 34 | var best_pattern pattern 35 | for p, value := range wins { 36 | if value > max_win { 37 | max_win = value 38 | best_pattern = p 39 | } 40 | } 41 | fmt.Println(max_win, best_pattern) 42 | } 43 | 44 | func (p *pattern) fill(observed []int) { 45 | p.n0 = observed[1] - observed[0] 46 | p.n1 = observed[2] - observed[1] 47 | p.n2 = observed[3] - observed[2] 48 | p.n3 = observed[4] - observed[3] 49 | } 50 | 51 | func update(observed []int, new_value int) []int { 52 | observed = append(observed, new_value) 53 | for len(observed) > 5 { 54 | observed = observed[1:] 55 | } 56 | return observed 57 | } 58 | 59 | func parseInitSecrets(input []string) []int { 60 | secrets := make([]int, len(input)) 61 | for i, str := range input { 62 | secret, _ := strconv.Atoi(str) 63 | secrets[i] = secret 64 | } 65 | return secrets 66 | } 67 | 68 | func runSecretEvolution(secret int, wins *map[pattern]int) { 69 | occurences := make(map[pattern]bool) 70 | var p pattern 71 | prices := make([]int, 0) 72 | for range n_steps { 73 | secret = mix_and_prune(secret, secret*64) 74 | secret = mix_and_prune(secret, secret/32) 75 | secret = mix_and_prune(secret, secret*2048) 76 | 77 | price := getLastDigit(secret) 78 | prices = update(prices, price) 79 | 80 | if len(prices) < 5 { 81 | continue 82 | } 83 | p.fill(prices) 84 | _, seen := occurences[p] 85 | 86 | if !seen { 87 | occurences[p] = true 88 | value, has_value := (*wins)[p] 89 | if !has_value { 90 | value = 0 91 | } 92 | (*wins)[p] = value + price 93 | } 94 | } 95 | } 96 | 97 | func mix_and_prune(result, secret int) int { 98 | return (result ^ secret) % prune_param 99 | } 100 | 101 | func getLastDigit(num int) int { 102 | return num % 10 103 | } 104 | -------------------------------------------------------------------------------- /2024/day23_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "slices" 7 | "strings" 8 | ) 9 | 10 | type node struct { 11 | id string 12 | next map[string]bool 13 | } 14 | 15 | func main() { 16 | file_name := "2024/day23_pt1/input.txt" 17 | input, err := os.ReadFile(file_name) 18 | if err != nil { 19 | fmt.Println(err) 20 | return 21 | } 22 | graph := createGraph(strings.Split(string(input), "\n")) 23 | connected := findConnectedTriplets(&graph) 24 | fmt.Println(connected) 25 | fmt.Println(len(connected)) 26 | } 27 | 28 | func createGraph(input []string) map[string]node { 29 | graph := make(map[string]node) 30 | for _, row := range input { 31 | id1 := strings.Split(row, "-")[0] 32 | id2 := strings.Split(row, "-")[1] 33 | addConnection(id1, id2, &graph) 34 | addConnection(id2, id1, &graph) 35 | } 36 | return graph 37 | } 38 | 39 | func addConnection(from, to string, graph *map[string]node) { 40 | _, from_exists := (*graph)[from] 41 | if !from_exists { 42 | next := make(map[string]bool) 43 | n := node{from, next} 44 | (*graph)[from] = n 45 | } 46 | (*graph)[from].next[to] = true 47 | } 48 | 49 | func findConnectedTriplets(graph *map[string]node) map[string]bool { 50 | triplets := make(map[string]bool) 51 | for _, n1 := range *graph { 52 | if !strings.HasPrefix(n1.id, "t") { 53 | continue 54 | } 55 | if len(n1.next) >= 2 { 56 | for n2 := range n1.next { 57 | if len(n2) >= 2 { 58 | for n3 := range (*graph)[n2].next { 59 | _, is_connected := n1.next[n3] 60 | if is_connected { 61 | triplet := []string{n1.id, n2, n3} 62 | slices.Sort(triplet) 63 | triplets[strings.Join(triplet, ",")] = true 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | return triplets 71 | } 72 | -------------------------------------------------------------------------------- /2024/day23_pt2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "slices" 7 | "strings" 8 | ) 9 | 10 | type node struct { 11 | id string 12 | next map[string]bool 13 | } 14 | 15 | func main() { 16 | file_name := "2024/day23_pt2/input.txt" 17 | input, err := os.ReadFile(file_name) 18 | if err != nil { 19 | fmt.Println(err) 20 | return 21 | } 22 | graph := createGraph(strings.Split(string(input), "\n")) 23 | nodes := make([]string, 0) 24 | for _, n := range graph { 25 | nodes = append(nodes, n.id) 26 | } 27 | current_clique := make([]string, 0) 28 | max_clique := make([]string, 0) 29 | max_size := 0 30 | findMaxClique(nodes, 0, &graph, ¤t_clique, &max_size, &max_clique) 31 | slices.Sort(max_clique) 32 | fmt.Println(strings.Join(max_clique, ",")) 33 | } 34 | 35 | func createGraph(input []string) map[string]node { 36 | graph := make(map[string]node) 37 | for _, row := range input { 38 | id1 := strings.Split(row, "-")[0] 39 | id2 := strings.Split(row, "-")[1] 40 | addConnection(id1, id2, &graph) 41 | addConnection(id2, id1, &graph) 42 | } 43 | return graph 44 | } 45 | 46 | func addConnection(from, to string, graph *map[string]node) { 47 | _, from_exists := (*graph)[from] 48 | if !from_exists { 49 | next := make(map[string]bool) 50 | n := node{from, next} 51 | (*graph)[from] = n 52 | } 53 | (*graph)[from].next[to] = true 54 | } 55 | 56 | func findMaxClique(nodes []string, index int, graph *map[string]node, current_clique *[]string, max_size *int, max_clique *[]string) { 57 | if index >= len(nodes) { 58 | return 59 | } 60 | for i := index; i < len(nodes); i++ { 61 | id := nodes[i] 62 | size := len(*current_clique) 63 | *current_clique = append(*current_clique, id) 64 | is_clique := isClique(*current_clique, graph) 65 | if is_clique { 66 | if len(*current_clique) > *max_size { 67 | fmt.Println("New best:", *current_clique) 68 | *max_size = len(*current_clique) 69 | *max_clique = make([]string, *max_size) 70 | copy(*max_clique, *current_clique) 71 | } 72 | findMaxClique(nodes, i+1, graph, current_clique, max_size, max_clique) 73 | } 74 | *current_clique = (*current_clique)[:size] 75 | } 76 | } 77 | 78 | func isClique(candidates []string, graph *map[string]node) bool { 79 | for i := range len(candidates) { 80 | for j := range len(candidates) { 81 | if i == j { 82 | continue 83 | } 84 | 85 | n, _ := (*graph)[candidates[i]] 86 | _, is_connected := n.next[candidates[j]] 87 | 88 | if !is_connected { 89 | return false 90 | } 91 | } 92 | } 93 | return true 94 | } 95 | -------------------------------------------------------------------------------- /2024/day24_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "slices" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | const ( 12 | and int = iota 13 | or int = iota 14 | xor int = iota 15 | ) 16 | 17 | type node struct { 18 | id string 19 | arg1 string 20 | arg2 string 21 | command int 22 | } 23 | 24 | type graph map[string]*node 25 | 26 | func xor_bool(a, b bool) bool { 27 | if a == b { 28 | return false 29 | } 30 | return true 31 | } 32 | 33 | func main() { 34 | file_name := "2024/day24_pt1/input.txt" 35 | input, err := os.ReadFile(file_name) 36 | if err != nil { 37 | fmt.Println(err) 38 | return 39 | } 40 | input_parts := strings.Split(string(input), "\n\n") 41 | values := parseInput(strings.Split(input_parts[0], "\n")) 42 | g := parseGraph(strings.Split(input_parts[1], "\n")) 43 | start_ids := getStartIds(&g) 44 | 45 | fmt.Println(values) 46 | fmt.Println(g) 47 | // fmt.Println(start_ids) 48 | 49 | calc_order := getCalculationOrder(&g, start_ids) 50 | fmt.Println(calc_order) 51 | 52 | for i, id := range calc_order { 53 | fmt.Printf("%d Calculating node %s -> ", i+1, id) 54 | g[id].calculate(&values) 55 | fmt.Printf("%t\n", values[id]) 56 | } 57 | 58 | answer_str := "" 59 | for _, id := range start_ids { 60 | if values[id] { 61 | answer_str = answer_str + "1" 62 | } else { 63 | answer_str = answer_str + "0" 64 | } 65 | } 66 | fmt.Println(answer_str) 67 | answer_num, err := strconv.ParseInt(answer_str, 2, 64) 68 | fmt.Println(answer_num) 69 | } 70 | 71 | func (n *node) calculate(values *map[string]bool) { 72 | var result bool 73 | arg1_val, arg1_exists := (*values)[n.arg1] 74 | arg2_val, arg2_exists := (*values)[n.arg2] 75 | 76 | if !arg1_exists || !arg2_exists { 77 | fmt.Printf("Node: %s: arg1: %s, arg2: %s, command: %d.\nValues: %v\n", n.id, n.arg1, n.arg2, n.command, *values) 78 | panic("Trying to calculate node with some of the args not ready") 79 | } 80 | 81 | switch op := n.command; op { 82 | case or: 83 | result = arg1_val || arg2_val 84 | case xor: 85 | result = xor_bool(arg1_val, arg2_val) 86 | case and: 87 | result = arg1_val && arg2_val 88 | default: 89 | panic("Undefined command") 90 | } 91 | (*values)[n.id] = result 92 | } 93 | 94 | func parseInput(input []string) map[string]bool { 95 | values := make(map[string]bool) 96 | for _, row := range input { 97 | id := strings.Split(row, ": ")[0] 98 | value_str := strings.Split(row, ": ")[1] 99 | value, _ := strconv.Atoi(value_str) 100 | values[id] = (value == 1) 101 | } 102 | return values 103 | } 104 | 105 | func parseGraphRow(row string) node { 106 | var n node 107 | n.id = strings.Split(row, " -> ")[1] 108 | formula := strings.Split(strings.Split(row, " -> ")[0], " ") 109 | n.arg1 = formula[0] 110 | n.arg2 = formula[2] 111 | 112 | switch command := formula[1]; command { 113 | case "OR": 114 | n.command = or 115 | case "XOR": 116 | n.command = xor 117 | case "AND": 118 | n.command = and 119 | default: 120 | n.command = -1 121 | } 122 | return n 123 | } 124 | 125 | func parseGraph(input []string) graph { 126 | g := make(graph) 127 | for _, row := range input { 128 | n := parseGraphRow(row) 129 | g[n.id] = &n 130 | } 131 | return g 132 | } 133 | 134 | func getStartIds(g *graph) []string { 135 | ids := make([]string, 0) 136 | for id := range *g { 137 | if strings.HasPrefix(id, "z") { 138 | ids = append(ids, id) 139 | } 140 | } 141 | slices.Sort(ids) 142 | slices.Reverse(ids) 143 | return ids 144 | } 145 | 146 | func dfs(n *node, g *graph, visited *map[string]bool, order *[]string) { 147 | fmt.Println(n.id) 148 | (*visited)[n.id] = true 149 | next_nodes := []string{n.arg1, n.arg2} 150 | fmt.Println("\tnext:", next_nodes) 151 | for _, nid := range next_nodes { 152 | _, is_visited := (*visited)[nid] 153 | _, has_node := (*g)[nid] 154 | if !is_visited && has_node { 155 | dfs((*g)[nid], g, visited, order) 156 | } 157 | } 158 | *order = append(*order, n.id) 159 | } 160 | 161 | func getCalculationOrder(g *graph, start_ids []string) []string { 162 | order := make([]string, 0) 163 | visited := make(map[string]bool) 164 | 165 | for _, id := range start_ids { 166 | _, is_visited := visited[id] 167 | if !is_visited { 168 | // order = append(order, id) 169 | dfs((*g)[id], g, &visited, &order) 170 | } 171 | } 172 | return order 173 | } 174 | -------------------------------------------------------------------------------- /2024/day25_pt1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | type key []int 10 | type lock []int 11 | 12 | func main() { 13 | file_name := "2024/day25_pt1/input.txt" 14 | input, err := os.ReadFile(file_name) 15 | if err != nil { 16 | fmt.Println(err) 17 | return 18 | } 19 | 20 | elems := strings.Split(string(input), "\n\n") 21 | keys, locks := parseInput(elems) 22 | max_height := len(strings.Split(elems[0], "\n")) - 2 23 | fmt.Println("Keys:", len(keys)) 24 | fmt.Println("Locks:", len(locks)) 25 | 26 | ok := 0 27 | for ik, k := range keys { 28 | for il, l := range locks { 29 | if !k.overlaps(l, max_height) { 30 | ok += 1 31 | } else if (ik < 10) && (il < 10) { 32 | fmt.Println("key", ik, "-", k, "lock", il, "-", l) 33 | } 34 | } 35 | } 36 | fmt.Println(ok) 37 | } 38 | 39 | func parseInput(input []string) ([]key, []lock) { 40 | keys := make([]key, 0) 41 | locks := make([]lock, 0) 42 | 43 | for _, elem := range input { 44 | rows := strings.Split(elem, "\n") 45 | is_key := false 46 | k := make(key, len(rows[0])) 47 | l := make(lock, len(rows[0])) 48 | for row_num, row := range rows { 49 | if row_num == 0 { 50 | if string(row[0]) == "." { 51 | is_key = true 52 | } 53 | } 54 | 55 | for i, sym := range row { 56 | if !is_key { 57 | if string(sym) == "#" { 58 | l[i] = row_num 59 | } 60 | } else { 61 | if string(sym) == "." { 62 | k[i] = len(rows) - row_num - 2 63 | } 64 | } 65 | } 66 | } 67 | 68 | if is_key { 69 | keys = append(keys, k) 70 | } else { 71 | locks = append(locks, l) 72 | } 73 | } 74 | return keys, locks 75 | } 76 | 77 | func (k *key) overlaps(l lock, max_height int) bool { 78 | for i := range len(l) { 79 | if (*k)[i]+l[i] > max_height { 80 | return true 81 | } 82 | } 83 | return false 84 | } 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a repository for my [Advent of Code](https://adventofcode.com/) solutions. --------------------------------------------------------------------------------