advent_of_code_2021

My attempts to work through the 2021 Advent of Code problems.
git clone https://git.eamoncaddigan.net/advent_of_code_2021.git
Log | Files | Refs | README | LICENSE

commit 49b32ed7252e5edd87e0a4b426787193bcd5442f
parent 621c531824bbfdf326c1c129cd2296fb05be21a7
Author: Eamon Caddigan <eamon.caddigan@gmail.com>
Date:   Thu,  2 Dec 2021 12:06:26 -0500

Solution to day 2, part 1

Diffstat:
Aday02_part1.py | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 72 insertions(+), 0 deletions(-)

diff --git a/day02_part1.py b/day02_part1.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +"""Day 2, Part 1: we need to parse submarine commands and figure out where the +submarine winds up. I am very confident that I wrote code to solve this exact +problem, using Lisp, decades ago in undergrad.""" + +# Once again I'm going to take a pandas-heavy approach even though it's not +# strictly necessary here. I'm going to make some tweaks to the coding +# conventions I used yesterday; I'd like to make it easier to develop different +# implementations and compare their performance. + +from io import StringIO +import numpy as np +import pandas as pd +from utils import get_puzzle_input + +def convert_input_to_df(input_string): + """Given a string representation of the input data, return a single-column + pandas data frame with the column `commands`""" + return pd.read_csv(StringIO(input_string), + names=('commands',)) + +def split_command_parts(command_df): + """Given a pandas data frame with a column `commands`, returns a new data + frame with the columns `direction` and `distance`""" + return ( + command_df['commands'] + .str.split(n=2, expand=True) + .rename(columns={0:'direction', 1:'distance'}) + .assign(distance=lambda x: pd.to_numeric(x['distance'])) + ) + +def convert_commands_to_offsets(command_df): + """Given a pandas data frame with the columns `direction` (in "forward", + "up", and "down") and `distance`, returns a new data frame with the columns + `axis` (either "horizontal" or "vertical") and `offset`""" + return pd.DataFrame( + {'axis': np.where(command_df['direction'].isin(('up', 'down')), + 'vertical', 'horizontal'), + 'offset': np.where(command_df['direction'] == 'up', + -command_df['distance'], command_df['distance'])} + ) + +def find_total_offsets(offset_df): + """Given a pandas data frame with the column `axis` and any others, find + the sum of other columns grouped by `axis`. (Note that this doesn't + restrict itself to the axes 'horizontal' or 'vertical' and it doesn't check + the other column names""" + # You know, in retrospect it was maybe confusing to call a column 'axis' in + # pandas world. Ah well! + return offset_df.groupby('axis').sum() + +def calculate_puzzle_solution(axis_summary_df): + """Given a pandas data frame with the sum of `offset` values along each + `axis`, multiply the 'horizontal' and 'vertical' offsets.""" + return axis_summary_df.loc['horizontal', 'offset'] * \ + axis_summary_df.loc['vertical', 'offset'] + +def solve_puzzle(): + """Return the numeric solution to the puzzle""" + return calculate_puzzle_solution( + find_total_offsets( + convert_commands_to_offsets( + split_command_parts( + convert_input_to_df(get_puzzle_input(2)) + ) + ) + ) + ) + +if __name__ == "__main__": + print("Product of horizontal and vertical offsets:", + solve_puzzle())