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 235c28b62909ff016963880a7034ac465ee440df
parent 49b32ed7252e5edd87e0a4b426787193bcd5442f
Author: Eamon Caddigan <eamon.caddigan@gmail.com>
Date:   Thu,  2 Dec 2021 14:09:09 -0500

Solution to day 2, part 2

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

diff --git a/day02_part2.py b/day02_part2.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +"""Day 2, Part 2: It turns out that controlling the submarine is more +complicated than we thought in Part 1. We need to maintain a third state +variable (in addition to horizontal and vertical position) called 'aim', and +use that when calculating sub movement.""" + +# Hey, the functions from Part 1 will be useful once again. All the input +# parsing is the same, and now we just need to use slightly more complicated +# logic when figuring out final position. We can start from the point where we +# have a data frame with the columns `axis` and `offset`. + +#from io import StringIO +import numpy as np +import pandas as pd +from utils import get_puzzle_input +from day02_part1 import (convert_input_to_df, + split_command_parts, + convert_commands_to_offsets) + +def find_submarine_state(offset_df): + """Given a pandas data frame with the columns `axis` and `offset`, we track + `aim` as well as `horizontal` and `vertical` offsets, using complicated + rules: + * 'vertical X' increases or decreases aim (which starts at 0) + * 'horizontal X': + * increases horizontal position by X + * increases vertical position (i.e., depth) by aim * X + This returns a data frame with the columns `horizontal`, `vertical` and + `aim`, representing the running value of these states.""" + return ( + pd.DataFrame( + # 'aim' represents the up-to-date state of the aim parameter, while + # 'horizontal_step' is just the current step's offset. We save this + # intermediate calculation because we need it for 'depth'. + # This line uses the extremely new-to-me 'walrus operator' to + # cretae the variable `vert_rows` so we don't calculate the + # equality twice. Not sure how I feel about this style. + {'aim': np.where(vert_rows := offset_df['axis'] == 'vertical', + offset_df['offset'], 0).cumsum(), + 'horizontal_step': np.where(~vert_rows, + offset_df['offset'], 0)} + ) + .assign(depth=lambda x: (x['aim'] * x['horizontal_step']).cumsum(), + horizontal=lambda x: x['horizontal_step'].cumsum()) + .drop('horizontal_step', axis='columns') + ) + +def calculate_puzzle_solution(running_state_df): + """Given a pandas data frame with the running values of the state of the + submarine (representing the `horizontal` offset, the `depth`, and also the + `aim` which is not used), return the product of the final value of the + `horizontal` offset and `depth`""" + return running_state_df.iloc[-1][['depth', 'horizontal']].prod() + +def solve_puzzle(): + """Return the numeric solution to the puzzle""" + return calculate_puzzle_solution( + find_submarine_state( + convert_commands_to_offsets( + split_command_parts( + convert_input_to_df(get_puzzle_input(2)) + ) + ) + ) + ) + +if __name__ == "__main__": + print("Product of (new) horizontal and vertical offsets:", + solve_puzzle())