Advent of Code Solutions

I have set myself a personal challenge to try and get up to date with advent of code, a set of programming challenges released each December.

Each day has 2 related problems, for a total of 50 problems per year. Each button below will show my code solving that problem. Green buttons where I have solved both problems of the day, orange where I have only solved the first one and red where I haven't solved either.

2015 2016 2017 2018 2019 2020 2021 2022 2023 2024
Not started
Part 1 completed
Both parts completed
from util.input import get_input
from collections import defaultdict

test_input = """RRRRIICCFF
RRRRIICCCF
VVRRRCCFFF
VVRCCCJFFF
VVVVCJJCFE
VVIVCCJJEE
VVIIICJJEE
MIIIIIJJEE
MIIISIJEEE
MMMISSJEEE
"""

test_input = """EEEEE
EXXXX
EEEEE
EXXXX
EEEEE
"""




def part_1():
    
    puzzle_input = get_input(2024, 12)

    # puzzle_input = test_input

    plant_type_to_pos = defaultdict(list)
    pos_to_plant_type = {}
    for i, row in enumerate(puzzle_input.splitlines()):
        for j, plant in enumerate(row):
            plant_type_to_pos[plant].append((i,j))
            pos_to_plant_type[(i,j)] = plant

    
    def find_contiguous_region(pos, plant_type, contiguous_region):

        if pos not in pos_to_plant_type:
            return
        
        if pos_to_plant_type[pos] != plant_type:
            return

        if pos in contiguous_region:
            return

        
        contiguous_region.add(pos)

        for neighbouring_pos in {
            (pos[0]-1, pos[1]), 
            (pos[0]+1, pos[1]), 
            (pos[0], pos[1]-1), 
            (pos[0], pos[1]+1)
        }:
            find_contiguous_region(neighbouring_pos, plant_type, contiguous_region)

    total_price = 0
    for plant_type in plant_type_to_pos:

        positions = plant_type_to_pos[plant_type]

        contiguous_regions = []
        for pos in positions:
            if any(pos in region for region in contiguous_regions):
                continue
            
            region = set()
            find_contiguous_region(pos, plant_type, region)

            contiguous_regions.append(region)

        for region in contiguous_regions:
            perimiter = 0
            for pos in region:
                for neighbouring_pos in {
                    (pos[0]-1, pos[1]), 
                    (pos[0]+1, pos[1]), 
                    (pos[0], pos[1]-1), 
                    (pos[0], pos[1]+1)
                }:
                    if neighbouring_pos not in pos_to_plant_type or pos_to_plant_type[neighbouring_pos] != plant_type:
                        perimiter += 1

            total_price += perimiter * len(region)

    return total_price


    

def part_2():
    puzzle_input = get_input(2024, 12)

    puzzle_input = test_input

    plant_type_to_pos = defaultdict(list)
    pos_to_plant_type = {}
    for i, row in enumerate(puzzle_input.splitlines()):
        for j, plant in enumerate(row):
            plant_type_to_pos[plant].append((i,j))
            pos_to_plant_type[(i,j)] = plant

    
    def find_contiguous_region(pos, plant_type, contiguous_region):

        if pos not in pos_to_plant_type:
            return
        
        if pos_to_plant_type[pos] != plant_type:
            return

        if pos in contiguous_region:
            return

        
        contiguous_region.add(pos)

        for neighbouring_pos in {
            (pos[0]-1, pos[1]), 
            (pos[0]+1, pos[1]), 
            (pos[0], pos[1]-1), 
            (pos[0], pos[1]+1)
        }:
            find_contiguous_region(neighbouring_pos, plant_type, contiguous_region)

    total_price = 0
    for plant_type in plant_type_to_pos:

        positions = plant_type_to_pos[plant_type]

        contiguous_regions = []
        for pos in positions:
            if any(pos in region for region in contiguous_regions):
                continue
            
            region = set()
            find_contiguous_region(pos, plant_type, region)

            contiguous_regions.append(region)

        for region in contiguous_regions:
            
            # find the top left corner
            n_sides = 0
            total_price += n_sides * len(region)

    return total_price