From 5b40798732bb90c4b219a3f9cc4c1955bbe0c237 Mon Sep 17 00:00:00 2001
From: Olle Kjellqvist <olle.kjellqvist@control.lth.se>
Date: Wed, 11 Dec 2024 15:09:45 +0100
Subject: [PATCH] Added some solutions

---
 day 10/day_10_olle.py |  79 +++++++++++++++++++++++++++++++++
 day 11/day_11_olle.py |  83 ++++++++++++++++++++++++++++++++++
 day 6/day_6_olle.py   |  99 +++++++++++++++++++++++++++++++++++++++++
 day 7/day_7_olle.py   |  66 +++++++++++++++++++++++++++
 day 8/day_8_olle.py   |  94 +++++++++++++++++++++++++++++++++++++++
 day 9/day_9_olle.py   | 101 ++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 522 insertions(+)
 create mode 100644 day 10/day_10_olle.py
 create mode 100644 day 11/day_11_olle.py
 create mode 100644 day 6/day_6_olle.py
 create mode 100644 day 7/day_7_olle.py
 create mode 100644 day 8/day_8_olle.py
 create mode 100644 day 9/day_9_olle.py

diff --git a/day 10/day_10_olle.py b/day 10/day_10_olle.py
new file mode 100644
index 0000000..d29efe0
--- /dev/null
+++ b/day 10/day_10_olle.py	
@@ -0,0 +1,79 @@
+from typing import List, Tuple
+import numpy as np
+def read_file(filename: str) -> List[List[int]]:
+    with open(filename, 'r') as file:
+        data = file.read().strip().split('\n')
+    topographic_map = [[int(c) for c in row] for row in data]
+    return topographic_map
+
+def get_all_indices(value: int, topographic_map: List[List[int]]) -> List[Tuple[int, int]]:
+    return [(i, j) for i in range(len(topographic_map)) for j in range(len(topographic_map[0])) if topographic_map[i][j] == value]
+
+def create_value_map(topographic_map: List[List[int]]) -> np.ndarray:
+    height, width = len(topographic_map), len(topographic_map[0])
+    # Initialize values to 1 for all indices with value 9
+    value_map = {}
+    for i, j in get_all_indices(9, topographic_map):
+        value_map[(i, j)] = set([(i, j)])
+    # Iterate backwards from 8 to 0
+    for value in range(8, -1, -1):
+        for i, j in get_all_indices(value, topographic_map):
+            if (i, j) not in value_map:
+                value_map[(i, j)] = set()
+            if ((i + 1) <= (height - 1) and
+                topographic_map[i + 1][j] == value + 1):
+                value_map[(i, j)] = value_map[(i, j)].union(value_map[(i + 1, j)])
+            if ((i - 1) >= 0 and
+                topographic_map[i - 1][j] == value + 1):
+                value_map[(i, j)] = value_map[(i, j)].union(value_map[(i - 1, j)])
+            if ((j + 1) <= (width - 1) and
+                topographic_map[i][j + 1] == value + 1):
+                value_map[(i, j)] = value_map[(i, j)].union(value_map[(i, j + 1)])
+            if ((j - 1) >= 0 and
+                topographic_map[i][j - 1] == value + 1):
+                value_map[(i, j)] = value_map[(i, j)].union(value_map[(i, j - 1)])
+    value_sums = [[len(value_map[(i, j)]) for j in range(width)] for i in range(height)]
+    return np.array(value_sums)
+
+
+def create_value_map_2(topographic_map: List[List[int]]) -> np.ndarray:
+    height, width = len(topographic_map), len(topographic_map[0])
+    value_map = np.zeros((height, width), dtype=int)
+    # Initialize values to 1 for all indices with value 9
+    for i, j in get_all_indices(9, topographic_map):
+        value_map[i][j] = 1
+    # Iterate backwards from 8 to 0
+    for value in range(8, -1, -1):
+        for i, j in get_all_indices(value, topographic_map):
+            if ((i + 1) <= (height - 1) and
+                topographic_map[i + 1][j] == value + 1):
+                value_map[i][j] += value_map[i + 1][j]
+            if ((i - 1) >= 0 and
+                topographic_map[i - 1][j] == value + 1):
+                value_map[i][j] += value_map[i - 1][j]
+            if ((j + 1) <= (width - 1) and
+                topographic_map[i][j + 1] == value + 1):
+                value_map[i][j] += value_map[i][j + 1]
+            if ((j - 1) >= 0 and
+                topographic_map[i][j - 1] == value + 1):
+                value_map[i][j] += value_map[i][j - 1]
+    return value_map
+
+def sum_of_trailhead_scores(topographic_map: List[List[int]]) -> int:
+    value_map = create_value_map(topographic_map)
+    sum_scores = 0
+    for i, j in get_all_indices(0, topographic_map):
+        sum_scores += value_map[i][j]
+    return sum_scores
+
+def sum_of_trailhead_scores_2(topographic_map: List[List[int]]) -> int:
+    value_map = create_value_map_2(topographic_map)
+    sum_scores = 0
+    for i, j in get_all_indices(0, topographic_map):
+        sum_scores += value_map[i][j]
+    return sum_scores
+
+if __name__ == '__main__':
+    topographic_map = read_file('input')
+    print(sum_of_trailhead_scores(topographic_map))
+    print(sum_of_trailhead_scores_2(topographic_map))
diff --git a/day 11/day_11_olle.py b/day 11/day_11_olle.py
new file mode 100644
index 0000000..5d9934e
--- /dev/null
+++ b/day 11/day_11_olle.py	
@@ -0,0 +1,83 @@
+from typing import List, Dict
+import time
+def read_file(filename:str) -> List[str]:
+    with open(filename) as f:
+        return f.read().strip().split(' ')
+
+def read_file_optimized(filename:str) -> Dict[str, int]:
+    arrangement = read_file(filename)
+    new_arrangement = {}
+    for stone in arrangement:
+        try:
+            new_arrangement[stone] += 1
+        except KeyError:
+            new_arrangement[stone] = 1
+    return new_arrangement
+
+
+def blink(arrangement:List[str]) -> List[str]:
+    new_arrangement = []
+    for stone in arrangement:
+        if int(stone) == 0:
+            new_arrangement.append('1')
+        elif len(stone) % 2 == 0:
+            first_half = stone[:len(stone)//2]
+            second_half = stone[len(stone)//2:]
+            # remove leading zeros of second half
+            second_half = str(int(second_half))
+            new_arrangement.append(first_half)
+            new_arrangement.append(second_half)
+        else:
+            new_arrangement.append(str(int(stone) * 2024))
+    return new_arrangement
+
+def blink_optimized(arrangement: Dict[str, int]) -> Dict[str, int]:
+    new_arrangement = {}
+    for stone, count in arrangement.items():
+        if int(stone) == 0:
+            try:
+                new_arrangement['1'] += count
+            except KeyError:
+                new_arrangement['1'] = count
+        elif len(stone) % 2 == 0:
+            first_half = stone[:len(stone)//2]
+            second_half = stone[len(stone)//2:]
+            # remove leading zeros of second half
+            second_half = str(int(second_half))
+            try:
+                new_arrangement[first_half] += count
+            except KeyError:
+                new_arrangement[first_half] = count
+            try:
+                new_arrangement[second_half] += count
+            except KeyError:
+                new_arrangement[second_half] = count
+        else:
+            try:
+                new_arrangement[str(int(stone) * 2024)] += count
+            except KeyError:
+                new_arrangement[str(int(stone) * 2024)] = count
+    return new_arrangement
+
+
+if __name__ == '__main__':
+    arrangement = read_file('input')
+    new_arrangement = read_file_optimized('input')
+    tic = time.time()
+    for i in range(25):
+        arrangement = blink(arrangement)
+    toc = time.time()
+    print(f"25 blinks took {toc-tic} seconds with unoptimized version")
+    print("Number of stones after 25 blinks:  ", len(arrangement))
+    tic = time.time()
+    for i in range(25):
+        new_arrangement = blink_optimized(new_arrangement)
+    toc = time.time()
+    print(f"25 blinks took {toc-tic} seconds with optimized version")
+    print("Number of stones after 25 blinks:  ", sum(new_arrangement.values()))
+    new_arrangement = read_file_optimized('input')
+    for i in range(75):
+        new_arrangement = blink_optimized(new_arrangement)
+    print("Number of stones after 75 blinks: ", sum(new_arrangement.values()))
+
+
diff --git a/day 6/day_6_olle.py b/day 6/day_6_olle.py
new file mode 100644
index 0000000..9b1ad31
--- /dev/null
+++ b/day 6/day_6_olle.py	
@@ -0,0 +1,99 @@
+from typing import List, Tuple
+
+class Turtle:
+    
+    def __init__(self, direction, environment, coords=(0, 0)):
+        self.direction = direction
+        self.coords = coords
+        self.environment = environment
+        self.has_visited = set([(coords[0], coords[1], direction)])
+        self.in_loop = False
+
+    def step(self) -> bool:
+        x, y = self.coords
+        if self.direction == 'N':
+            if y == 0:
+                return False
+            elif self.environment[y - 1][x] == '#':
+                self.direction = 'E'
+            else:
+                y -= 1
+        elif self.direction == 'E':
+            if x == len(self.environment[0]) - 1:
+                return False
+            elif self.environment[y][x + 1] == '#':
+                self.direction = 'S'
+            else:
+                x += 1
+        elif self.direction == 'S':
+            if y == len(self.environment) - 1:
+                return False
+            elif self.environment[y + 1][x] == '#':
+                self.direction = 'W'
+            else:
+                y += 1
+        elif self.direction == 'W':
+            if x == 0:
+                return False
+            elif self.environment[y][x - 1] == '#':
+                self.direction = 'N'
+            else:
+                x -= 1
+        else:
+            raise ValueError("Invalid direction")
+        
+        # Update position and track visited coordinates
+        self.coords = (x, y)
+        if (x, y, self.direction) in self.has_visited:
+            self.in_loop = True
+            return False
+        self.has_visited.add((x, y, self.direction))
+        return True
+
+    def run(self):
+        while self.step():
+            pass
+        if self.in_loop:
+            return False
+        else:
+            return True
+
+    def get_visited(self) -> int:
+        return len(set([(x, y) for x, y, _ in self.has_visited]))
+
+def get_starting_coords(environment: List[str]) -> Tuple[int, int]:
+    for row, col in enumerate(environment):
+        if '^' in col:
+            return (col.index('^'), row)
+    raise ValueError("Starting position not found in environment")
+
+def read_file(filename: str) -> List[str]:
+    with open(filename, 'r') as f:
+        data = f.read().strip().split('\n')
+    return data
+
+def get_p2_solution(filename: str) -> int:
+    orig_environment = read_file(filename)  
+    start_coords = get_starting_coords(orig_environment)
+    num_loops = 0
+    for i in range(len(orig_environment)):
+        print(f"Row {i} of {len(orig_environment)}, num_loops: {num_loops}")
+        for j in range(len(orig_environment[0])):
+            if orig_environment[i][j] in ['^', '#']:
+                continue
+            environment = [list(row) for row in orig_environment]
+            environment[i][j] = '#'
+            turtle = Turtle(direction='N', environment=environment, coords=start_coords)
+            turtle.run()
+            if turtle.in_loop:
+                num_loops += 1
+    return num_loops
+
+if __name__ == "__main__":
+    environment = read_file('input')  
+    start_coords = get_starting_coords(environment)
+    turtle = Turtle(direction='N', environment=environment, coords=start_coords)
+    turtle.run()
+    print(turtle.get_visited())
+    print(get_p2_solution('input'))
+
diff --git a/day 7/day_7_olle.py b/day 7/day_7_olle.py
new file mode 100644
index 0000000..06c4776
--- /dev/null
+++ b/day 7/day_7_olle.py	
@@ -0,0 +1,66 @@
+from typing import List, Tuple
+from math import log10
+def read_file(file_name: str) -> Tuple[List[int], List[List[int]]]:
+    lhs = []
+    rhs = []
+    with open(file_name, 'r') as file:
+        for line in file:
+            if line == '':
+                break
+            line = line.strip().split(': ')
+            lhs.append(int(line[0]))
+            rhs.append([int(x) for x in line[1].split(' ')])
+    return lhs, rhs
+
+def sum_valid_equations(lhs: List[int], rhs: List[List[int]]) -> int:
+    max_len = max(len(l) for l in rhs) - 1 # -1 because only operators between numbers
+    operator_table = [
+            [['+'], ['*']]
+            ]
+    for i in range(1, max_len):
+        operator_table.append(
+                [o + operator for o in operator_table[-1] for operator in [['+'], ['*']]]
+                )
+    valid_lhs = 0
+    for l, r in zip(lhs, rhs):
+        operator_list = operator_table[len(r) - 2]
+        for operators in operator_list:
+            if check_equation(l, r, operators):
+                valid_lhs += l
+                break
+    return valid_lhs
+
+def sum_valid_equations_p2(lhs: List[int], rhs: List[List[int]]) -> int:
+    max_len = max(len(l) for l in rhs) - 1 # -1 because only operators between numbers
+    operator_table = [
+            [['+'], ['*'], ['||']]
+            ]
+    for i in range(1, max_len):
+        operator_table.append(
+                [o + operator for o in operator_table[-1] for operator in [['+'], ['*'],['||']]]
+                )
+    valid_lhs = 0
+    for l, r in zip(lhs, rhs):
+        operator_list = operator_table[len(r) - 2]
+        for operators in operator_list:
+            if check_equation(l, r, operators):
+                valid_lhs += l
+                break
+    return valid_lhs
+
+def check_equation(lhs: int, rhs: List[int], operators: List[str]) -> bool:
+    result = rhs[0]
+    for i in range(1, len(rhs)):
+        if operators[i-1] == '+':
+            result += rhs[i]
+        elif operators[i-1] == '*':
+            result *= rhs[i]
+        elif operators[i-1] == '||':
+            result = result * 10**int(log10(rhs[i]) + 1) + rhs[i]
+    return result == lhs
+
+if __name__ == "__main__":
+    lhs, rhs = read_file('input')
+    print(sum_valid_equations(lhs, rhs))
+    print(sum_valid_equations_p2(lhs, rhs))
+
diff --git a/day 8/day_8_olle.py b/day 8/day_8_olle.py
new file mode 100644
index 0000000..ee598c8
--- /dev/null
+++ b/day 8/day_8_olle.py	
@@ -0,0 +1,94 @@
+from math import gcd
+from typing import List, Set, Dict
+def read_file(filename: str) -> List[str]:
+    with open(filename, 'r') as file:
+        data = file.read().strip().split('\n')
+    return data
+
+def get_letters(environment: List[str]) -> Set[str]:
+    letters = set()
+    for row in environment:
+        letters = letters.union(set(row))
+    letters.discard('.')
+    return letters
+
+def get_coordinates(environment: List[str]) -> Dict:
+    height = len(environment)
+    width = len(environment[0])
+    letter_coordinates = {}
+    for i in range(height):
+        line = environment[i]
+        for j in range(width):
+            if line[j] == '.':
+                continue
+            if line[j] in letter_coordinates.keys():
+                letter_coordinates[line[j]].append((i, j))
+            else:
+                letter_coordinates[line[j]] = [(i, j)]
+    return letter_coordinates
+
+
+def num_of_antinodes(environment: List[str]) -> int:
+    height = len(environment)
+    width = len(environment[0])
+    antinodes = set()
+    coords_dict = get_coordinates(environment)
+    for key in coords_dict.keys():
+        if len(coords_dict[key]) < 2:
+            continue
+        else:
+            coords = coords_dict[key]
+            for j in range(len(coords) - 1):
+                for k in range(j + 1, len(coords)):
+                    row1, col1 = coords[j]
+                    row2, col2 = coords[k]
+                    antinode1_row = 2 * row2 - row1
+                    antinode2_row = 2 * row1 - row2
+                    antinode1_col = 2 * col2 - col1
+                    antinode2_col = 2 * col1 - col2
+                    if (0 <= antinode1_row < height
+                        and 0 <= antinode1_col < width):
+                        antinodes.add((antinode1_row, antinode1_col))
+                    if (0 <= antinode2_row < height
+                        and 0 <= antinode2_col < width):
+                        antinodes.add((antinode2_row, antinode2_col))
+    return len(antinodes)
+
+def num_of_antinodes_2(environment: List[str]) -> int:
+    height = len(environment)
+    width = len(environment[0])
+    antinodes = set()
+    coords_dict = get_coordinates(environment)
+    for key in coords_dict.keys():
+        if len(coords_dict[key]) < 2:
+            continue
+        else:
+            coords = coords_dict[key]
+            for j in range(len(coords) - 1):
+                for k in range(j + 1, len(coords)):
+                    row1, col1 = coords[j]
+                    row2, col2 = coords[k]
+                    rowdist = row2-row1
+                    coldist = col2-col1
+                    rowdist = rowdist // gcd(rowdist, coldist)
+                    coldist = coldist // gcd(rowdist, coldist)
+                    antirow, anticol = row1, col1
+                    while (0 <= antirow < height and 
+                           0  <= anticol < height):
+                        antinodes.add((antirow, anticol))
+                        antirow += rowdist
+                        anticol += coldist
+                    antirow, anticol = row1, col1
+                    antirow -= rowdist
+                    anticol -= coldist
+                    while (0 <= antirow < height and 
+                           0  <= anticol < height):
+                        antinodes.add((antirow, anticol))
+                        antirow -= rowdist
+                        anticol -= coldist
+    return len(antinodes)
+
+if __name__ == "__main__":
+    env = read_file("input")
+    print(num_of_antinodes(env))
+    print(num_of_antinodes_2(env))
diff --git a/day 9/day_9_olle.py b/day 9/day_9_olle.py
new file mode 100644
index 0000000..4707546
--- /dev/null
+++ b/day 9/day_9_olle.py	
@@ -0,0 +1,101 @@
+from typing import List
+def read_file(filename: str) -> str:
+    with open(filename) as f:
+        return f.readline().strip()
+
+def get_id_representation(file: str) -> List[str]:
+    i = 0
+    id_rep = []
+    while i < len(file):
+        if i % 2 == 0:
+            id_rep += [str(i//2)]*int(file[i])
+        else:
+            id_rep += ['.']*int(file[i])
+        i += 1
+    return id_rep
+
+
+
+def rearrange(id_rep: str) -> List[str]:
+    i = 0
+    j = len(id_rep) - 1
+    rearranged_rep = []
+    while i < j:
+        while id_rep[i].isnumeric() and i < j:
+            rearranged_rep += [id_rep[i]]
+            i += 1
+        while (id_rep[j] == '.') and i < j:
+            j -= 1
+        if i == j:
+            break
+        else:
+            rearranged_rep += [id_rep[j]]
+            i += 1
+            j -= 1
+    rearranged_rep += [id_rep[j]]
+    print(len(id_rep))
+    print(len(rearranged_rep))
+    return rearranged_rep + ['.'] * (len(id_rep) - len(rearranged_rep))
+
+def rearrange_whole(file: str) -> List[str]:
+    file = list(file)
+    ids = []
+    i = 0
+    for i in range(len(file)):
+        if i % 2 == 0:
+            ids += [str(i // 2)]
+        else:
+            ids += [str('.')]
+    j = 1
+    while j <= (len(file) - 1):
+        if ids[-j] == '.':
+            j += 1
+            continue
+        i = 0
+        while (i < (len(file) - j) and
+               (ids[i].isnumeric() or int(file[i]) < int(file[-j]))):
+            i += 1
+        if i == (len(file) - j):
+            j += 1
+            continue
+        elif int(file[i]) == int(file[-j]):
+            ids[i], ids[-j] = ids[-j], ids[i]
+            file[i], file[-j] = file[-j], file[i]
+            j += 1
+        else:
+            numel_to_keep = int(file[i]) - int(file[-j])
+            file[i] = file[-j]
+            file = file[:i+1] + [str(numel_to_keep)] + file[i+1:]
+            ids[i], ids[-j] = ids[-j], ids[i]
+            ids = ids[:i+1] + ['.'] + ids[i+1:]
+            j += 2
+
+    rearranged_file = []
+    for i in range(len(file)):
+        rearranged_file += [ids[i]]*int(file[i])
+    return rearranged_file
+
+            
+
+def compute_checksum(arranged_file: List[str]) -> int:
+    checksum = 0
+    i = 0
+    while arranged_file[i].isnumeric():
+        checksum += int(arranged_file[i]) * i
+        i += 1
+    return checksum
+
+def compute_checksum_whole(arranged_file: List[str]) -> int:
+    checksum = 0
+    i = 0
+    for i in range(len(arranged_file)):
+        if arranged_file[i].isnumeric():
+            checksum += int(arranged_file[i]) * i
+    return checksum
+
+
+if __name__ == "__main__":
+    file = read_file('input')
+    id_rep = get_id_representation(file)
+    print(compute_checksum(rearrange(id_rep)))
+    print(compute_checksum_whole(rearrange_whole(file)))
-- 
GitLab