My 2024 Attempts

Day 1

Part 1

import os
import pandas as pd

input = pd.read_csv(
    os.path.join("2024", "day_01", "input_day01.txt"), sep="   ", header=None
)
input.columns = ["l1", "l2"]

def part1(input):
    l1 = list(input["l1"])
    l1.sort()
    l2 = list(input["l2"])
    l2.sort()

    df_lists = pd.DataFrame({"l1": l1, "l2": l2})
    df_lists["diff"] = abs(df_lists["l2"] - df_lists["l1"])

    return df_lists["diff"].sum()


print(part1(input))
1258579
1.447 seconds elapsed

Part 2

import os
import pandas as pd

input = pd.read_csv(
    os.path.join("2024", "day_01", "input_day01.txt"), sep="   ", header=None
)
input.columns = ["l1", "l2"]

def part2(input):
    l1 = list(input["l1"])
    l2 = list(input["l2"])

    count_array = []
    for value in l1:
        count_array.append(value * l2.count(value))

    return sum(count_array)


print(part2(input))
23981443
0.025 seconds elapsed

Day 2

Part 1

import os

input = open(os.path.join("2024", "day_02", "input_day02.txt")).read().split("\n")
input = [[int(level) for level in report.split(" ")] for report in input]

def check_if_save(report):
    increasing = [report[i + 1] - report[i] for i in range(len(report) - 1)]
    if set(increasing) <= {1, 2, 3} or set(increasing) <= {-1, -2, -3}:
        return True
    else:
        return False

def part1(input):
    n_safe = sum([check_if_save(report) for report in input])
    return n_safe


print(part1(input))
213
0.015 seconds elapsed

Part 2

import os

input = open(os.path.join("2024", "day_02", "input_day02.txt")).read().split("\n")
input = [[int(level) for level in report.split(" ")] for report in input]

def check_if_save(report):
    increasing = [report[i + 1] - report[i] for i in range(len(report) - 1)]
    if set(increasing) <= {1, 2, 3} or set(increasing) <= {-1, -2, -3}:
        return True
    else:
        return False

def part2(input):
    n_safe = sum(
        [
            any(
                [
                    check_if_save(report[:i] + report[i + 1 :])
                    for i in range(len(report))
                ]
            )
            for report in input
        ]
    )
    return n_safe


print(part2(input))
285
0.032 seconds elapsed

Day 3

Part 1

import os
import re
import math

input = open(os.path.join("2024", "day_03", "input_day03.txt")).read()

def get_multiplications(input):
    mul_strings = re.findall(r"mul\(\d+,\d+\)", input)
    mul_vals = [[int(s) for s in re.findall(r"\d+", mul)] for mul in mul_strings]
    mults = [math.prod(x) for x in mul_vals]
    mult_sum = sum(mults)
    return mult_sum

def part1(input):
    mult_sum = get_multiplications(input)
    return mult_sum


print(part1(input))
167090022
0.011 seconds elapsed

Part 2

import os
import re
import math

input = open(os.path.join("2024", "day_03", "input_day03.txt")).read()

def get_multiplications(input):
    mul_strings = re.findall(r"mul\(\d+,\d+\)", input)
    mul_vals = [[int(s) for s in re.findall(r"\d+", mul)] for mul in mul_strings]
    mults = [math.prod(x) for x in mul_vals]
    mult_sum = sum(mults)
    return mult_sum

def part2(input):
    input_cleaned = " ".join(sec.split("don't()")[0] for sec in input.split("do()"))
    mult_sum = get_multiplications(input_cleaned)
    return mult_sum


print(part2(input))
89823704
0.009 seconds elapsed

Day 4

Part 1

import os
import re

input = open(os.path.join("2024", "day_04", "input_day04.txt")).read()
input = input.split("\n")

def match(matrix, pattern, width):
    matches = 0
    for i in range(len(matrix) - width + 1):
        for j in range(len(matrix[i]) - width + 1):
            block = "".join(matrix[i + x][j : j + width] for x in range(width))
            matches += bool(re.match(pattern, block))

    return matches

def part1(input):
    word_n = 0
    for rotation in range(4):
        word_n += sum(row.count("XMAS") for row in input)
        word_n += match(input, r"X.{4}M.{4}A.{4}S", 4)

        input = ["".join(row[::-1]) for row in zip(*input)]

    return word_n


print(part1(input))
2591
0.139 seconds elapsed

Part 2

import os
import re

input = open(os.path.join("2024", "day_04", "input_day04.txt")).read()
input = input.split("\n")

def match(matrix, pattern, width):
    matches = 0
    for i in range(len(matrix) - width + 1):
        for j in range(len(matrix[i]) - width + 1):
            block = "".join(matrix[i + x][j : j + width] for x in range(width))
            matches += bool(re.match(pattern, block))

    return matches

def part2(input):
    word_n = 0
    for rotation in range(4):
        word_n += match(input, r"M.M.A.S.S", 3)

        input = ["".join(row[::-1]) for row in zip(*input)]

    return word_n


print(part2(input))
1880
0.132 seconds elapsed

Day 5

Day 6

Day 7

Part 1

import os
import re
from functools import reduce
from itertools import product
from operator import add, mul

input = open(os.path.join("2024", "day_07", "input_day07.txt")).read()
input = [list(map(int, re.findall(r"(\d+)", x))) for x in input.splitlines()]

def check_equation(equation, operators):
    test_val, nums = equation[0], equation[1:]
    for ops in product(operators, repeat=(len(nums) - 1)):
        if reduce(lambda k, x: x[0](k, x[1]), zip(ops, nums[1:]), nums[0]) == test_val:
            return True
    return False

def part1(input):
    cal_result = sum(eq[0] for eq in input if check_equation(eq, (add, mul)))
    return cal_result


print(part1(input))
7885693428401
0.402 seconds elapsed

Part 2

import os
import re
from functools import reduce
from itertools import product
from operator import add, mul

input = open(os.path.join("2024", "day_07", "input_day07.txt")).read()
input = [list(map(int, re.findall(r"(\d+)", x))) for x in input.splitlines()]

def check_equation(equation, operators):
    test_val, nums = equation[0], equation[1:]
    for ops in product(operators, repeat=(len(nums) - 1)):
        if reduce(lambda k, x: x[0](k, x[1]), zip(ops, nums[1:]), nums[0]) == test_val:
            return True
    return False

def _concat(a, b):
    a *= 10 ** len(str(b))
    return a + b


def part2(input):
    cal_result = sum(eq[0] for eq in input if check_equation(eq, (add, mul, _concat)))

    return cal_result


print(part2(input))
348360680516005
29.536 seconds elapsed

Day 8

Part 1

import os

input = open(os.path.join("2024", "day_08", "input_day08.txt")).read().splitlines()
input = [list(line) for line in input]

def get_pair_antinodes_pt1(antinodes, n, m, x_i, y_i, x_j, y_j):
    dx = x_j - x_i
    dy = y_j - y_i
    if x_i - dx >= 0 and y_i - dy >= 0 and y_i - dy < m:
        antinodes.add((x_i - dx, y_i - dy))
    if x_j + dx < n and y_j + dy < m and y_j + dy >= 0:
        antinodes.add((x_j + dx, y_j + dy))


def part1(input):
    n = len(input)
    m = len(input[0])
    antinodes = set()

    antenna_locations = {}
    for i in range(n):
        for j in range(m):
            if input[i][j] != ".":
                c = input[i][j]
                antenna_locations[c] = antenna_locations.get(c, []) + [(i, j)]
    for freq, antenna in antenna_locations.items():
        if len(antenna) < 2:
            continue
        for i in range(len(antenna) - 1):
            for j in range(i + 1, len(antenna)):
                x_i, y_i = antenna[i]
                x_j, y_j = antenna[j]
                get_pair_antinodes_pt1(antinodes, n, m, x_i, y_i, x_j, y_j)
    return len(antinodes)


print(part1(input))
308
0.013 seconds elapsed

Part 2

import os

input = open(os.path.join("2024", "day_08", "input_day08.txt")).read().splitlines()
input = [list(line) for line in input]

def get_pair_antinodes_pt2(antinodes, n, m, x_i, y_i, x_j, y_j):
    dx = x_j - x_i
    dy = y_j - y_i
    multiplier = 0
    while (
        x_i - multiplier * dx >= 0
        and y_i - multiplier * dy >= 0
        and y_i - multiplier * dy < m
    ):
        antinodes_new = antinodes.add((x_i - multiplier * dx, y_i - multiplier * dy))
        multiplier += 1
    multiplier = 0
    while (
        x_j + multiplier * dx < n
        and y_j + multiplier * dy < m
        and y_j + multiplier * dy >= 0
    ):
        antinodes_new = antinodes.add((x_j + multiplier * dx, y_j + multiplier * dy))
        multiplier += 1

    return antinodes_new


def part2(input):
    n = len(input)
    m = len(input[0])
    antinodes = set()

    antenna_locations = {}
    for i in range(n):
        for j in range(m):
            if input[i][j] != ".":
                c = input[i][j]
                antenna_locations[c] = antenna_locations.get(c, []) + [(i, j)]
    for freq, antenna in antenna_locations.items():
        if len(antenna) < 2:
            continue
        for i in range(len(antenna) - 1):
            for j in range(i + 1, len(antenna)):
                x_i, y_i = antenna[i]
                x_j, y_j = antenna[j]
                get_pair_antinodes_pt2(antinodes, n, m, x_i, y_i, x_j, y_j)
    return len(antinodes)


print(part2(input))
1147
0.013 seconds elapsed

Day 9

Day 10

Day 11

Part 1

import os
from functools import cache

input = open(os.path.join("2024", "day_11", "input_day11.txt")).read().split()

@cache
def calculate_state(n):
    if n == 0:
        return [1]

    string_num = str(n)
    if len(string_num) % 2 == 0:
        mid = len(string_num) // 2
        return [int(string_num[:mid]), int(string_num[mid:])]

    new_state = [n * 2024]
    return new_state

@cache
def stone_count(stone, blinks_left):
    if blinks_left == 0:
        return 1

    new_stone_state = calculate_state(stone)
    new_stone_count = sum(stone_count(num, blinks_left - 1) for num in new_stone_state)

    return new_stone_count

def part1(input):
    n_blinks = 25
    total_stones = 0
    stones = list(int(stone) for stone in input)
    for stone in stones:
        total_stones += stone_count(stone, n_blinks)
    return total_stones


print(part1(input))
189547
0.017 seconds elapsed

Part 2

import os
from functools import cache

input = open(os.path.join("2024", "day_11", "input_day11.txt")).read().split()

@cache
def calculate_state(n):
    if n == 0:
        return [1]

    string_num = str(n)
    if len(string_num) % 2 == 0:
        mid = len(string_num) // 2
        return [int(string_num[:mid]), int(string_num[mid:])]

    new_state = [n * 2024]
    return new_state

@cache
def stone_count(stone, blinks_left):
    if blinks_left == 0:
        return 1

    new_stone_state = calculate_state(stone)
    new_stone_count = sum(stone_count(num, blinks_left - 1) for num in new_stone_state)

    return new_stone_count

def part2(input):
    n_blinks = 75
    stones = list(int(stone) for stone in input)

    total_stones = 0
    for stone in stones:
        total_stones += stone_count(stone, n_blinks)

    return total_stones


print(part2(input))
224577979481346
0.305 seconds elapsed

Day 12

Day 13

Day 14

Part 1

library(tidyverse)

input <- read_delim(
  here::here("2024", "day_14", "input_day14.txt"),
  delim = " ",
  col_names = c("pos", "vel")
)

input <- input |>
  mutate(
    starts = str_extract_all(pos, "\\d+"),
    velocities = str_extract_all(vel, "-?\\d+")
  )

starts <- input |>
  pull(starts) |>
  map(as.numeric)

velocities <- input |>
  pull(velocities) |>
  map(as.numeric)

dims <- c(101, 103)

safety_factor <- function(starts, velocities, dims, times) {
  end_pos <- map2(
    starts,
    velocities,
    \(x, y, dims, times) (x + y * times) %% dims,
    dims = dims,
    times = times
  )
  do.call(rbind, end_pos) |>
    as_tibble(.name_repair = "unique") |>
    rename(x = "...1", y = "...2")
}

safety_factor(starts, velocities, dims, times = 100) |>
  mutate(
    quadrant = case_when(
      x < (dims[1] - 1) / 2 & y < (dims[2] - 1) / 2 ~ 1,
      x < (dims[1] - 1) / 2 & y > (dims[2] - 1) / 2 ~ 2,
      x > (dims[1] - 1) / 2 & y < (dims[2] - 1) / 2 ~ 3,
      x > (dims[1] - 1) / 2 & y > (dims[2] - 1) / 2 ~ 4,
    )
  ) |>
  filter(!is.na(quadrant)) |>
  count(quadrant) |>
  pull(n) |>
  prod() |>
  print()
[1] 215987200
0.441 sec elapsed

Part 2

library(tidyverse)

input <- read_delim(
  here::here("2024", "day_14", "input_day14.txt"),
  delim = " ",
  col_names = c("pos", "vel")
)

input <- input |>
  mutate(
    starts = str_extract_all(pos, "\\d+"),
    velocities = str_extract_all(vel, "-?\\d+")
  )

starts <- input |>
  pull(starts) |>
  map(as.numeric)

velocities <- input |>
  pull(velocities) |>
  map(as.numeric)

dims <- c(101, 103)

second_range <- seq(8000, 8100)

safety_factors <- map(
  second_range,
  ~ safety_factor(starts, velocities, dims, times = .x) |>
    mutate(
      second = .x,
      quadrant = case_when(
        x < (dims[1] - 1) / 2 & y < (dims[2] - 1) / 2 ~ 1,
        x < (dims[1] - 1) / 2 & y > (dims[2] - 1) / 2 ~ 2,
        x > (dims[1] - 1) / 2 & y < (dims[2] - 1) / 2 ~ 3,
        x > (dims[1] - 1) / 2 & y > (dims[2] - 1) / 2 ~ 4,
      )
    )
) |>
  bind_rows()

min_iterations <- safety_factors |>
  group_by(second) |>
  summarise(
    var_x = var(x),
    var_y = var(y)
  ) |>
  arrange(var_x, var_y) |>
  slice_head(n = 1) |>
  pull(second)

print(min_iterations)

safety_factors |>
  filter(second == min_iterations) |>
  ggplot(aes(x = x, y = y)) +
  geom_tile(fill = "white", color = "grey80", size = 0.1) +
  scale_y_reverse() +
  coord_equal() +
  theme_void() +
  theme(
    plot.margin = margin(rep(2, 4), unit = "mm"),
    plot.background = element_rect(
      fill = "#0f0f23",
      color = "transparent"
    )
  )

ggsave(
  here::here("2024", "day_14", "plot_day14_pt2.png"),
  last_plot(),
  height = 5, width = 10, dpi = 300, bg = "transparent"
)
[1] 8050
4.488 sec elapsed

Day 15

Part 1

import os

input = open(os.path.join("2024", "day_15", "input_day15.txt")).read().split("\n\n")

def move_coordinates(coord1, coord2):
    new_coords = tuple(i + j for i, j in zip(coord1, coord2))
    return new_coords


def part1(input):
    grid = input[0].splitlines()
    moves = input[1].replace("\n", "")

    boxes = {
        (i, j)
        for i, line in enumerate(grid)
        for j, char in enumerate(line)
        if char == "O"
    }
    walls = {
        (i, j)
        for i, line in enumerate(grid)
        for j, char in enumerate(line)
        if char == "#"
    }
    robot = [
        (i, j)
        for i, line in enumerate(grid)
        for j, char in enumerate(line)
        if char == "@"
    ]
    assert len(robot) == 1
    robot = robot[0]

    move_dict = {"<": (0, -1), "^": (-1, 0), "v": (1, 0), ">": (0, 1)}

    for move in moves:
        direction = move_dict[move]
        target1 = move_coordinates(robot, direction)
        if target1 not in walls and target1 not in boxes:
            robot = target1
        else:
            target2 = target1
            while target2 in boxes:
                target2 = move_coordinates(target2, direction)
            if target2 in walls:
                continue
            else:
                robot = target1
                boxes.remove(target1)
                boxes.add(target2)

    answer = sum(100 * i + j for i, j in boxes)

    return answer


print(part1(input))
1415498
0.046 seconds elapsed

Day 16

Day 17

Day 18

Day 19

Day 20

Day 21

Day 22

Day 23

Day 24

Day 25