Browse Source

Merge branch 'day1' of noelle/adventofcode2025 into master

pull/2/head
noelle 1 week ago
parent
commit
8fb3ecf32e
3 changed files with 4920 additions and 0 deletions
  1. 187
    0
      day1-1.py
  2. 190
    0
      day1-2.py
  3. 4543
    0
      day1_input.txt

+ 187
- 0
day1-1.py View File

@@ -0,0 +1,187 @@
# encoding: utf-8

###########################
# Advent of Code Day 1-1 #
###########################
# Combination lock - rotary dial
# Instructions come in the form `DN+`,
# where D is a direction L or R, and N+ is a series of integers of
# arbitrary length. (e.g. L32, R947)
# The dial is 0-indexed and has 100 positions (0-99).
# Rotating left decrements the index; rotating right increments it.
# Rotating left from 0 goes to 99, and rotating right from 99 goes to 0
# (i.e. it wraps around).
# Count the number of times the dial lands on 0 at the end of a move.
# Instructions are in day1_input.txt.

# I'm gonna overengineer this.

import argparse
import logging
import os


logger = logging.getLogger(__name__)
sh = logging.StreamHandler()
logger.addHandler(sh)
logger.setLevel(logging.INFO)

DEFAULTS = {
"starting_position": 50,
"num_positions": 100,
"filename": "day1_input.txt"
}


class RotaryLock:
def __init__(self, starting_position:int|None=None, num_positions:int|None=None):
try:
num_positions = int(num_positions) if num_positions is not None else DEFAULTS["num_positions"]
except ValueError:
logger.warning(f"Number of positions {num_positions} is not an integer. Setting to default ({DEFAULTS['num_positions']}).")
num_positions = DEFAULTS["num_positions"]
if num_positions <= 0:
logger.warning(f"Number of positions {num_positions} is not positive. Setting to default ({DEFAULTS['num_positions']}).")
num_positions = DEFAULTS["num_positions"]
self.last_position = num_positions - 1
self.first_position = 0

try:
starting_position = int(starting_position) if starting_position is not None else DEFAULTS["starting_position"]
except ValueError:
logger.warning(f"Starting position {starting_position} is not an integer. Setting to default ({DEFAULTS['starting_position']}).")
starting_position = DEFAULTS["starting_position"]
if self.first_position <= int(starting_position) <= self.last_position:
self.current_position = (starting_position)
else:
logger.warning(
f"Starting position {starting_position} falls outside of possible positions {self.first_position}-{self.last_position}. Setting to 0."
)
self.current_position = 0
self.position_history = []
self.position_history.append(self.current_position)
self.instructions = []
self.num_zeroes = 0
logger.debug(f"RotaryLock created!")
logger.debug(self.__dict__)
def read_instructions(self, filename:str|None=None) -> None:
filename = filename if filename is not None else DEFAULTS["filename"]
if not filename.startswith("/"):
full_path = os.path.join(os.curdir, filename)
else:
full_path = filename
if os.path.exists(full_path) and os.path.isfile(full_path):
with open(full_path, "r") as f:
lines = f.readlines()
elif not os.path.exists(full_path):
logger.error(f"File {full_path} does not exist.")
return
elif not os.path.isfile(full_path):
logger.error(f"File {full_path} is not a file.")
return
self.instructions = [l.strip() for l in lines]

def rotate_right(self) -> None:
if self.current_position == self.last_position:
self.current_position = self.first_position
else:
self.current_position += 1

def rotate_left(self) -> None:
if self.current_position == self.first_position:
self.current_position = self.last_position
else:
self.current_position -= 1

def rotate_lock(self, direction:str, magnitude:int) -> None:
try:
d = direction.lower()
except AttributeError:
logger.error(f"Supplied direction {direction} is not usable (must be a string).")
return
if d not in ("l","r"):
logger.error(f"Supplied direction {direction} is not usable (must be L or R).")
return
try:
n = int(magnitude)
except ValueError:
logger.error(f"Supplied magnitude {magnitude} is not an integer.")
return
for i in range(n):
match d:
case "r":
self.rotate_right()
case "l":
self.rotate_left()
case default:
logger.error(f"Direction {d} was not recognized. (How did we get here?)")
# if d == "r":
# new_pos = self.current_position + n
# if new_pos > self.last_position:
# overflow = new_pos - (self.last_position + 1)
# new_pos = overflow
# else:
# new_pos = self.current_position - n
# if new_pos < 0:
# overflow = n - (self.current_position + 1)
# new_pos = self.last_position - overflow
# self.current_position = new_pos
logger.debug(f"After rotating {d.upper()} {n} places, current position is {self.current_position}.")

def follow_instruction(self, instruction:str) -> None:
try:
d, n = instruction[0].lower(), int(instruction[1:])
except (AttributeError, ValueError, IndexError):
logger.error(f"Instruction {instruction} is unreadable (format must be DN+).")
return
logger.debug(f"Following instruction to rotate {d} by {n} positions.")
self.rotate_lock(direction=d, magnitude=n)
if self.current_position == 0:
logger.debug(f"Landed on 0.")
self.num_zeroes += 1

def follow_instructions(self, instruction_list:list[str]|None=None) -> None:
if instruction_list is None:
self.read_instructions()
instruction_list = self.instructions
else:
if len(self.instructions) == 0:
logger.error(f"Instructions were not passed and have not been initialized.")
return
instruction_list = self.instructions
try:
instructions = [x for x in instruction_list]
except TypeError:
logger.error(f"Instruction list {instruction_list} is not an iterable.")
tenths = len(instructions) // 10
logger.debug(f"Processing {len(instructions)} instructions.")
logger.debug(f"Reporting every {tenths} instructions.")
for i, instr in enumerate(instructions):
if tenths > 0 and i % tenths == 0:
logger.debug(f"Processing instruction {i}.")
self.follow_instruction(instr)
logger.info(f"Completed all instructions.")
logger.info(f"Final position is {self.current_position}.")
logger.info(f"Number of zeroes is {self.num_zeroes}.")


def process_instructions(filename:str, start:int, num_pos:int):
rlock = RotaryLock(starting_position=start, num_positions=num_pos)
rlock.read_instructions(filename)
rlock.follow_instructions()

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--filename", type=str, default=DEFAULTS["filename"], help="Full or local path to instruction file")
parser.add_argument("--start", type=int, default=DEFAULTS["starting_position"], help="Starting position on the dial")
parser.add_argument("--num_pos", type=int, default=DEFAULTS["num_positions"], help="Number of positions on the dial")
parser.add_argument("--verbose", "-v", action="store_true", help="Enable debug logging")

args = parser.parse_args()

if args.verbose:
logger.setLevel(logging.DEBUG)

process_instructions(args.filename, args.start, args.num_pos)

+ 190
- 0
day1-2.py View File

@@ -0,0 +1,190 @@
# encoding: utf-8

###########################
# Advent of Code Day 1-2 #
###########################
# Combination lock - rotary dial
# Instructions come in the form `DN+`,
# where D is a direction L or R, and N+ is a series of integers of
# arbitrary length. (e.g. L32, R947)
# The dial is 0-indexed and has 100 positions (0-99).
# Rotating left decrements the index; rotating right increments it.
# Rotating left from 0 goes to 99, and rotating right from 99 goes to 0
# (i.e. it wraps around).
# Count the number of times the dial ever sees 0.
# Instructions are in day1_input.txt.

# I'm gonna overengineer this.

import argparse
import logging
import os


logger = logging.getLogger(__name__)
sh = logging.StreamHandler()
logger.addHandler(sh)
logger.setLevel(logging.INFO)

DEFAULTS = {
"starting_position": 50,
"num_positions": 100,
"filename": "day1_input.txt"
}


class RotaryLock:
def __init__(self, starting_position:int|None=None, num_positions:int|None=None):
try:
num_positions = int(num_positions) if num_positions is not None else DEFAULTS["num_positions"]
except ValueError:
logger.warning(f"Number of positions {num_positions} is not an integer. Setting to default ({DEFAULTS['num_positions']}).")
num_positions = DEFAULTS["num_positions"]
if num_positions <= 0:
logger.warning(f"Number of positions {num_positions} is not positive. Setting to default ({DEFAULTS['num_positions']}).")
num_positions = DEFAULTS["num_positions"]
self.last_position = num_positions - 1
self.first_position = 0

try:
starting_position = int(starting_position) if starting_position is not None else DEFAULTS["starting_position"]
except ValueError:
logger.warning(f"Starting position {starting_position} is not an integer. Setting to default ({DEFAULTS['starting_position']}).")
starting_position = DEFAULTS["starting_position"]
if self.first_position <= int(starting_position) <= self.last_position:
self.current_position = (starting_position)
else:
logger.warning(
f"Starting position {starting_position} falls outside of possible positions {self.first_position}-{self.last_position}. Setting to 0."
)
self.current_position = 0
self.position_history = []
self.position_history.append(self.current_position)
self.instructions = []
self.num_zeroes = 0
logger.debug(f"RotaryLock created!")
logger.debug(self.__dict__)
def read_instructions(self, filename:str|None=None) -> None:
filename = filename if filename is not None else DEFAULTS["filename"]
if not filename.startswith("/"):
full_path = os.path.join(os.curdir, filename)
else:
full_path = filename
if os.path.exists(full_path) and os.path.isfile(full_path):
with open(full_path, "r") as f:
lines = f.readlines()
elif not os.path.exists(full_path):
logger.error(f"File {full_path} does not exist.")
return
elif not os.path.isfile(full_path):
logger.error(f"File {full_path} is not a file.")
return
self.instructions = [l.strip() for l in lines]

def rotate_right(self) -> None:
if self.current_position == self.last_position:
self.current_position = self.first_position
else:
self.current_position += 1

def rotate_left(self) -> None:
if self.current_position == self.first_position:
self.current_position = self.last_position
else:
self.current_position -= 1

def rotate_lock(self, direction:str, magnitude:int) -> None:
try:
d = direction.lower()
except AttributeError:
logger.error(f"Supplied direction {direction} is not usable (must be a string).")
return
if d not in ("l","r"):
logger.error(f"Supplied direction {direction} is not usable (must be L or R).")
return
try:
n = int(magnitude)
except ValueError:
logger.error(f"Supplied magnitude {magnitude} is not an integer.")
return
for i in range(n):
match d:
case "r":
self.rotate_right()
case "l":
self.rotate_left()
case default:
logger.error(f"Direction {d} was not recognized. (How did we get here?)")
if self.current_position == self.first_position:
logger.debug(f"Saw a zero.")
self.num_zeroes += 1
# if d == "r":
# new_pos = self.current_position + n
# if new_pos > self.last_position:
# overflow = new_pos - (self.last_position + 1)
# new_pos = overflow
# else:
# new_pos = self.current_position - n
# if new_pos < 0:
# overflow = n - (self.current_position + 1)
# new_pos = self.last_position - overflow
# self.current_position = new_pos
logger.debug(f"After rotating {d.upper()} {n} places, current position is {self.current_position}.")

def follow_instruction(self, instruction:str) -> None:
try:
d, n = instruction[0].lower(), int(instruction[1:])
except (AttributeError, ValueError, IndexError):
logger.error(f"Instruction {instruction} is unreadable (format must be DN+).")
return
logger.debug(f"Following instruction to rotate {d} by {n} positions.")
self.rotate_lock(direction=d, magnitude=n)
# if self.current_position == 0:
# logger.debug(f"Landed on 0.")
# self.num_zeroes += 1

def follow_instructions(self, instruction_list:list[str]|None=None) -> None:
if instruction_list is None:
self.read_instructions()
instruction_list = self.instructions
else:
if len(self.instructions) == 0:
logger.error(f"Instructions were not passed and have not been initialized.")
return
instruction_list = self.instructions
try:
instructions = [x for x in instruction_list]
except TypeError:
logger.error(f"Instruction list {instruction_list} is not an iterable.")
tenths = len(instructions) // 10
logger.debug(f"Processing {len(instructions)} instructions.")
logger.debug(f"Reporting every {tenths} instructions.")
for i, instr in enumerate(instructions):
if tenths > 0 and i % tenths == 0:
logger.debug(f"Processing instruction {i}.")
self.follow_instruction(instr)
logger.info(f"Completed all instructions.")
logger.info(f"Final position is {self.current_position}.")
logger.info(f"Number of zeroes is {self.num_zeroes}.")


def process_instructions(filename:str, start:int, num_pos:int):
rlock = RotaryLock(starting_position=start, num_positions=num_pos)
rlock.read_instructions(filename)
rlock.follow_instructions()

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--filename", type=str, default=DEFAULTS["filename"], help="Full or local path to instruction file")
parser.add_argument("--start", type=int, default=DEFAULTS["starting_position"], help="Starting position on the dial")
parser.add_argument("--num_pos", type=int, default=DEFAULTS["num_positions"], help="Number of positions on the dial")
parser.add_argument("--verbose", "-v", action="store_true", help="Enable debug logging")

args = parser.parse_args()

if args.verbose:
logger.setLevel(logging.DEBUG)

process_instructions(args.filename, args.start, args.num_pos)

+ 4543
- 0
day1_input.txt
File diff suppressed because it is too large
View File


Loading…
Cancel
Save