commit c936d7d5739e5dd416d970b984378b446e9fa663 Author: FaultyBranches Date: Tue Dec 2 07:55:08 2025 -0600 feat: Redo of the AoC repo starting over with 2015 and keeping up with 2025 (completed day 2 so far) diff --git a/15/1/__pycache__/main.cpython-313.pyc b/15/1/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000..69fb6bd Binary files /dev/null and b/15/1/__pycache__/main.cpython-313.pyc differ diff --git a/15/1/__pycache__/test_day1.cpython-313-pytest-8.4.2.pyc b/15/1/__pycache__/test_day1.cpython-313-pytest-8.4.2.pyc new file mode 100644 index 0000000..a669850 Binary files /dev/null and b/15/1/__pycache__/test_day1.cpython-313-pytest-8.4.2.pyc differ diff --git a/15/1/__pycache__/test_day1.cpython-313-pytest-9.0.1.pyc b/15/1/__pycache__/test_day1.cpython-313-pytest-9.0.1.pyc new file mode 100644 index 0000000..f6b0703 Binary files /dev/null and b/15/1/__pycache__/test_day1.cpython-313-pytest-9.0.1.pyc differ diff --git a/15/1/main.py b/15/1/main.py new file mode 100644 index 0000000..38221b3 --- /dev/null +++ b/15/1/main.py @@ -0,0 +1,59 @@ +'''Day 1 of 2015 AOC''' + +def process_input_string(input_string: str) -> int: + '''Take in an input string and give back the floor number based on the instructions. + + Keyword Args: + input_string (str) - String of encoded instructions + + Return: + int - Floor number + ''' + curr_floor = 0 + + for char in input_string: + if char == '(': + curr_floor += 1 + elif char == ')': + curr_floor -= 1 + + return curr_floor + + +def find_the_basement(input_string: str) -> int: + '''Take in an input string of instructions and give back the position of the instruction entering the basement, or hitting floor "-1". + + Keyword Args: + input_string (str) - String of encoded instructions + + Return: + int - Position of instruction in the string + ''' + curr_floor = 0 + + for index, char in enumerate(input_string): + if char == '(': + curr_floor += 1 + elif char == ')': + curr_floor -= 1 + + if curr_floor == -1: + # Add one to give the 1s based position instead of the 0s based index + return index + 1 + + +def main(): + '''Entrypoint''' + with open('input.txt', encoding='utf-8') as fh: + input_string = fh.readline() + output = process_input_string(input_string) + + print(f'Part 1: {output}') + + output = find_the_basement(input_string) + + print(f'Part 2: {output}') + + +if __name__ == "__main__": + main() diff --git a/15/1/test_day1.py b/15/1/test_day1.py new file mode 100644 index 0000000..e93c088 --- /dev/null +++ b/15/1/test_day1.py @@ -0,0 +1,28 @@ +import pytest + +from main import find_the_basement, process_input_string + + +@pytest.mark.parametrize('test_input, expected', + [ + ('(())', 0), + ('()()', 0), + ('(((', 3), + ('(()(()(', 3), + ('))(((((', 3), + ('())', -1), + ('))(', -1), + (')))', -3), + (')())())', -3) + ]) +def test_instruction_examples(test_input, expected): + assert process_input_string(test_input) == expected + + +@pytest.mark.parametrize('test_input, expected', + [ + (')', 1), + ('()())', 5) + ]) +def test_basement_instructions(test_input, expected): + assert find_the_basement(test_input) == expected diff --git a/15/2/__pycache__/main.cpython-313.pyc b/15/2/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000..3a4f232 Binary files /dev/null and b/15/2/__pycache__/main.cpython-313.pyc differ diff --git a/15/2/__pycache__/test_day2.cpython-313-pytest-9.0.1.pyc b/15/2/__pycache__/test_day2.cpython-313-pytest-9.0.1.pyc new file mode 100644 index 0000000..2394119 Binary files /dev/null and b/15/2/__pycache__/test_day2.cpython-313-pytest-9.0.1.pyc differ diff --git a/15/2/main.py b/15/2/main.py new file mode 100644 index 0000000..c8bffa8 --- /dev/null +++ b/15/2/main.py @@ -0,0 +1,38 @@ +'''AoC day 2''' + +def get_surface_area(l: int, w: int, h: int) -> int: + '''Take the dimensions of the box and return its surface area + + Keyword args: + l (int) - Length + w (int) - Width + h (int) - Height + ''' + return 2*l*w + 2*w*h + 2*h*l + +def get_slack_amount(l: int, w: int, h: int) -> int: + '''Take dimensions and return the value of the smallest''' + return min([l*w, w*h, h*l]) + +def get_ribbon_amount(l: int, w: int, h: int) -> int: + return min([2*h + 2*w, 2*l + 2*w, 2*h + 2*l]) + (l*w*h) + +def main(): + '''Entrypoint''' + # Surface area for wrapping paper + with open('input.txt', encoding='utf-8') as fh: + sum = 0 + r_sum = 0 + + for line in fh: + l, w, h = [int(x) for x in line.split('x')] + # print(f'{l} {w} {h}') + sum += get_surface_area(l, w, h) + sum += get_slack_amount(l, w, h) + r_sum += get_ribbon_amount(l, w, h) + + print(f'Part 1, sum of wrapping paper: {sum}') + print(f'Part 2, sum of ribbon: {r_sum}') + +if __name__ == "__main__": + main() diff --git a/15/2/test_day2.py b/15/2/test_day2.py new file mode 100644 index 0000000..fa51a5a --- /dev/null +++ b/15/2/test_day2.py @@ -0,0 +1,27 @@ +import pytest + +from main import get_slack_amount, get_surface_area, get_ribbon_amount + +@pytest.mark.parametrize('l, w, h, expected', + [ + (2, 3, 4, 52), + (1, 1, 10, 42) + ]) +def test_surface_area(l, w, h, expected): + assert get_surface_area(l, w, h) == expected + +@pytest.mark.parametrize('l, w, h, expected', + [ + (2, 3, 4, 6), + (1, 1, 10, 1) + ]) +def test_slack_amount(l, w, h, expected): + assert get_slack_amount(l, w, h) == expected + +@pytest.mark.parametrize('l, w, h, expected', + [ + (2, 3, 4, 34), + (1, 1, 10, 14) + ]) +def test_ribbon_amount(l, w, h, expected): + assert get_ribbon_amount(l, w, h) == expected diff --git a/15/3/__pycache__/main.cpython-313.pyc b/15/3/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000..4175b4b Binary files /dev/null and b/15/3/__pycache__/main.cpython-313.pyc differ diff --git a/15/3/__pycache__/test_day3.cpython-313-pytest-9.0.1.pyc b/15/3/__pycache__/test_day3.cpython-313-pytest-9.0.1.pyc new file mode 100644 index 0000000..33f9710 Binary files /dev/null and b/15/3/__pycache__/test_day3.cpython-313-pytest-9.0.1.pyc differ diff --git a/15/3/main.py b/15/3/main.py new file mode 100644 index 0000000..2d12eec --- /dev/null +++ b/15/3/main.py @@ -0,0 +1,66 @@ +'''AoC day 2''' + +def get_new_pos(curr_pos, instruction): + if instruction == '<': + new_pos = (curr_pos[0] - 1, curr_pos[1]) + elif instruction == '>': + new_pos = (curr_pos[0] + 1, curr_pos[1]) + elif instruction == '^': + new_pos = (curr_pos[0], curr_pos[1] + 1) + elif instruction == 'v': + new_pos = (curr_pos[0], curr_pos[1] - 1) + else: + new_pos = (0, 0) + + return new_pos + + +def process_instructions(instruction_set: str) -> int: + '''Run through a set of instructions to return the number of unique houses visited + + Keyword args: + instruction_set (str) - Directional arrow instruction string giving moves to new locations + + Returns: + int - Number of unique houses visited + ''' + visited_house_coordinates = {(0,0)} + curr_pos = (0, 0) + + for instruction in instruction_set: + curr_pos = get_new_pos(curr_pos, instruction) + + visited_house_coordinates.add(curr_pos) + + return len(visited_house_coordinates) + +def process_robo_instructions(instruction_set: str) -> int: + visited_house_coordinates = {(0,0)} + santa_pos = (0,0) + robot_pos = (0,0) + + for index, instruction in enumerate(instruction_set): + if index % 2 == 0: + santa_pos = get_new_pos(santa_pos, instruction) + visited_house_coordinates.add(santa_pos) + else: + robot_pos = get_new_pos(robot_pos, instruction) + visited_house_coordinates.add(robot_pos) + + return len(visited_house_coordinates) + +def main(): + '''Entrypoint''' + with open('input.txt', encoding='utf-8') as fh: + instruction_set = fh.readline() + unique_houses = process_instructions(instruction_set) + + print(f'Part1: {unique_houses}') + + robo_houses = process_robo_instructions(instruction_set) + + print(f'Part2: {robo_houses}') + + +if __name__ == "__main__": + main() diff --git a/15/3/test_day3.py b/15/3/test_day3.py new file mode 100644 index 0000000..a030a94 --- /dev/null +++ b/15/3/test_day3.py @@ -0,0 +1,20 @@ +import pytest + +from main import process_instructions, process_robo_instructions + +@pytest.mark.parametrize('given, expected', + [ + ('>', 2), + ('^>v<', 4), + ('^v^v^v^v^v', 2) + ]) +def test_instructions(given, expected): + assert process_instructions(given) == expected + + +@pytest.mark.parametrize('given, expected', + [ + ('^v', 3) + ]) +def test_part2_instructions(given, expected): + assert process_robo_instructions(given) == expected diff --git a/15/4/__pycache__/main.cpython-313.pyc b/15/4/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000..b15f719 Binary files /dev/null and b/15/4/__pycache__/main.cpython-313.pyc differ diff --git a/15/4/__pycache__/test_main.cpython-313-pytest-9.0.1.pyc b/15/4/__pycache__/test_main.cpython-313-pytest-9.0.1.pyc new file mode 100644 index 0000000..e6be165 Binary files /dev/null and b/15/4/__pycache__/test_main.cpython-313-pytest-9.0.1.pyc differ diff --git a/15/4/main.py b/15/4/main.py new file mode 100644 index 0000000..2ba3c3a --- /dev/null +++ b/15/4/main.py @@ -0,0 +1,26 @@ +import hashlib + +def search_hashes(secret: str, match_str: str) -> int: + num = 1 + + while True: + search_string_bytes = (secret + str(num)).encode() + + hash_string = hashlib.md5(search_string_bytes).hexdigest() + + if hash_string.startswith(match_str): + return num + + num += 1 + + +def main(): + num = search_hashes('ckczppom', '00000') + print(f'Part1: {num}') + + num = search_hashes('ckczppom', '000000') + print(f'Part2: {num}') + + +if __name__ == "__main__": + main() diff --git a/15/4/test_main.py b/15/4/test_main.py new file mode 100644 index 0000000..2052a7d --- /dev/null +++ b/15/4/test_main.py @@ -0,0 +1,11 @@ +import pytest + +from main import search_hashes + +@pytest.mark.parametrize('given, match, expected', + [ + ('abcdef', '00000', 609043), + ('pqrstuv', '00000', 1048970) + ]) +def test_searching(given, match, expected): + assert search_hashes(given, match) == expected diff --git a/15/5/__pycache__/main.cpython-313.pyc b/15/5/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000..fa28d02 Binary files /dev/null and b/15/5/__pycache__/main.cpython-313.pyc differ diff --git a/15/5/__pycache__/test_main.cpython-313-pytest-9.0.1.pyc b/15/5/__pycache__/test_main.cpython-313-pytest-9.0.1.pyc new file mode 100644 index 0000000..87a9db6 Binary files /dev/null and b/15/5/__pycache__/test_main.cpython-313-pytest-9.0.1.pyc differ diff --git a/15/5/main.py b/15/5/main.py new file mode 100644 index 0000000..9ff922a --- /dev/null +++ b/15/5/main.py @@ -0,0 +1,81 @@ +def check_contains_no_bad_chars(test: str) -> bool: + bad_chars = ['ab', 'cd', 'pq', 'xy'] + + if any(el in test for el in bad_chars): + return False + + return True + + +def check_doubled_char(input: str) -> bool: + for index, letter in enumerate(input): + if index - 1 > 0 and input[index - 1] == letter: + return True + if index + 1 < len(input) and input[index + 1] == letter: + return True + + return False + + +def check_vowel_count(input: str) -> bool: + '''Give a count of vowels in a string + + Keyword args: + input (str) - String to search to vowels + + Return: + int - The number of found vowels + ''' + sum = 0 + + for letter in input: + if letter in 'aeiou': + sum += 1 + + if sum > 2: + return True + + return False + +def check_if_nice(test: str) -> bool: + if check_contains_no_bad_chars(test) and check_doubled_char(test) and check_vowel_count(test): + return True + + return False + + +def check_nonoverlapping_repeats(test: str) -> bool: + splitup = [test[i:i+2] for i in range(0, len(test), 2)] + + for el in splitup: + check = splitup.pop() + if check in splitup: + return True + + return False + +def check_repeat_everyother(test: str) -> bool: + for index, letter in enumerate(test): + if index + 2 < len(test) and test[index+2] == letter: + return True + + return False + +def check_if_nice_part2(test: str) -> bool: + return check_nonoverlapping_repeats(test) and check_repeat_everyother(test) + +def main(): + with open('input.txt', encoding='utf-8') as fh: + total = 0 + + for line in fh: + # print(line.strip()) + + if check_if_nice(line.strip()): + total += 1 + + print(f'Part1: {total}') + + +if __name__ == "__main__": + main() diff --git a/15/5/test_main.py b/15/5/test_main.py new file mode 100644 index 0000000..92e20b0 --- /dev/null +++ b/15/5/test_main.py @@ -0,0 +1,77 @@ +import pytest + +from main import check_contains_no_bad_chars, check_doubled_char, check_vowel_count, check_if_nice, check_nonoverlapping_repeats, check_repeat_everyother, check_if_nice_part2 + + +@pytest.mark.parametrize('given, expected', + [ + ('aei', True), + ('xazegov', True), + ('aeiouaeiouaeiou', True), + ('mnqpcbt', False) + ]) +def test_vowel_counting(given, expected): + assert check_vowel_count(given) == expected + + +@pytest.mark.parametrize('given, expected', + [ + ('aabbccdd', True), + ('whythisword', False), + ('hegwjzuvuyypxyu', True) + ]) +def test_double_counting(given, expected): + assert check_doubled_char(given) == expected + + +@pytest.mark.parametrize('given, expected', + [ + ('aabbccdd', False), + ('whythisword', True) + ]) +def test_bad_chars(given, expected): + assert check_contains_no_bad_chars(given) == expected + + +@pytest.mark.parametrize('given, expected', + [ + ('ugknbfddgicrmopn', True), + ('aaa', True), + ('jchzalrnumimnmhp', False), + ('haegwjzuvuyypxyu', False), + ('dvszwmarrgswjxmb', False) + ]) +def test_examples(given, expected): + assert check_if_nice(given) == expected + + +@pytest.mark.parametrize('given, expected', + [ + ('xyxy', True), + ('xxyxx', True), + ('aaa', False) + ]) +def test_nonopverlapping(given, expected): + assert check_nonoverlapping_repeats(given) == expected + +@pytest.mark.parametrize('given, expected', + [ + ('xyx', True), + ('xxyxx', True), + ('abcdefeghi', True), + ('aaa', True), + ('blargh', False) + ]) +def test_repeat_everyother(given, expected): + assert check_repeat_everyother(given) == expected + + +@pytest.mark.parametrize('given, expected', + [ + ('qjhvhtzxzqqjkmpb', True), + ('xxyxx', True), + ('uurcxstgmygtbstg', False), + ('ieodomkazucvgmuy', False) + ]) +def test_part2_examples(given, expected): + assert check_if_nice_part2(given) == expected diff --git a/25/1/__pycache__/main.cpython-313.pyc b/25/1/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000..a63f456 Binary files /dev/null and b/25/1/__pycache__/main.cpython-313.pyc differ diff --git a/25/1/__pycache__/test_main.cpython-313-pytest-9.0.1.pyc b/25/1/__pycache__/test_main.cpython-313-pytest-9.0.1.pyc new file mode 100644 index 0000000..ced7ce0 Binary files /dev/null and b/25/1/__pycache__/test_main.cpython-313-pytest-9.0.1.pyc differ diff --git a/25/1/main.py b/25/1/main.py new file mode 100644 index 0000000..b0114f4 --- /dev/null +++ b/25/1/main.py @@ -0,0 +1,59 @@ +'''AoC 2025 Day 1''' +import math + +def turn_dial(curr_pos: int, instruction: str) -> int: + '''Turn dial from current position to the new one''' + direction, amount = process_instruction(instruction) + + new_pos = (curr_pos + (direction * amount)) % 100 + + return new_pos + + +def check_cross_zero(curr_pos: int, instruction: str) -> int: + '''Check for if the instruction crosses 0 and lands on it''' + direction, amount = process_instruction(instruction) + + total_zeros = 0 + + if direction == 1 and curr_pos + amount >= 100: + total_zeros += math.floor((curr_pos + amount) / 100) + if direction == -1 and curr_pos - amount <= 0: + total_zeros += math.floor(abs((curr_pos - amount) / 100)) + if curr_pos != 0: + total_zeros += 1 + + return total_zeros + + +def process_instruction(instruction: str) -> (int, int): + '''Returns the direction and amount of the turn''' + direction = -1 if instruction[0] == 'L' else 1 + amount = int(instruction[1:]) + + return direction, amount + + +def main(): + '''Entrypoint''' + curr_pos = 50 + + num_zero = 0 + crossing_zero = 0 + + with open('input.txt', encoding='utf-8') as fh: + for line in fh: + crossing_zero += check_cross_zero(curr_pos, line.strip()) + + curr_pos = turn_dial(curr_pos, line.strip()) + + if curr_pos == 0: + num_zero += 1 + + print(f'Part1: {num_zero}') + + print(f'Part2: {crossing_zero}') + + +if __name__ == "__main__": + main() diff --git a/25/1/test_main.py b/25/1/test_main.py new file mode 100644 index 0000000..bf0e56a --- /dev/null +++ b/25/1/test_main.py @@ -0,0 +1,41 @@ +'''testing''' +import pytest + +from main import turn_dial, check_cross_zero + +@pytest.mark.parametrize('current, given, expected', + [ + (50, 'L68', 82), + (82, 'L30', 52), + (52, 'R48', 0), + (0, 'L5', 95), + (95, 'R60', 55), + (55, 'L55', 0), + (0, 'L1', 99), + (99, 'L99', 0), + (0, 'R14', 14), + (14, 'L82', 32), + (50, 'R1000', 50), + (50, 'L1000', 50) + ]) +def test_matching_zero(current, given, expected): + assert turn_dial(current, given) == expected + + +@pytest.mark.parametrize('current, given, expected', + [ + (50, 'L68', 1), + (82, 'L30', 0), + (52, 'R48', 1), + (0, 'L5', 0), + (95, 'R60', 1), + (55, 'L55', 1), + (0, 'L1', 0), + (99, 'L99', 1), + (0, 'R14', 0), + (14, 'L82', 1), + (50, 'R1000', 10), + (50, 'L1000', 10) + ]) +def test_crossing_zero(current, given, expected): + assert check_cross_zero(current, given) == expected diff --git a/25/2/__pycache__/main.cpython-313.pyc b/25/2/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000..d055e97 Binary files /dev/null and b/25/2/__pycache__/main.cpython-313.pyc differ diff --git a/25/2/__pycache__/test_main.cpython-313-pytest-9.0.1.pyc b/25/2/__pycache__/test_main.cpython-313-pytest-9.0.1.pyc new file mode 100644 index 0000000..e09353a Binary files /dev/null and b/25/2/__pycache__/test_main.cpython-313-pytest-9.0.1.pyc differ diff --git a/25/2/main.py b/25/2/main.py new file mode 100644 index 0000000..7f2a0b3 --- /dev/null +++ b/25/2/main.py @@ -0,0 +1,48 @@ +'''AoC Day 2''' +import re + +def part1(range_list: list) -> int: + '''Part 1''' + total = 0 + + for id_range in range_list: + for x in range(int(id_range[0]), int(id_range[1]) + 1): + if not check_valid_id(str(x)): + total += x + + return total + +def part2(range_list: list) -> int: + '''Part 2''' + total = 0 + + for id_range in range_list: + for x in range(int(id_range[0]), int(id_range[1]) + 1): + if not check_extended_id(str(x)): + total += x + + return total + +def check_valid_id(id: str) -> bool: + '''Checks a given ID for validity using the silly rules''' + return re.fullmatch(r'(.*?)\1', id) is None + +def check_extended_id(id: str) -> bool: + '''Checks for extended rule checking, at least two repeats''' + return re.fullmatch(r'(.*?)\1+', id) is None + +def main(): + '''Entrypoint''' + with open('input.txt', encoding='utf-8') as fh: + range_list = [(y[0].strip(), y[1].strip()) for y in (x.split('-') for x in fh.readline().split(','))] + + total = part1(range_list) + + print(f'Part1: {total}') + + total2 = part2(range_list) + + print(f'Part2: {total2}') + +if __name__ == "__main__": + main() diff --git a/25/2/test_main.py b/25/2/test_main.py new file mode 100644 index 0000000..d1a813a --- /dev/null +++ b/25/2/test_main.py @@ -0,0 +1,87 @@ +import pytest + +from main import check_valid_id, check_extended_id, part1, part2 + +@pytest.mark.parametrize('given, expected', + [ + ('1', True), + ('11', False), + ('12', True), + ('22', False), + ('1010', False), + ('1012', True), + ('1188511885', False), + ('222222', False), + ('446446', False), + ('565656', True), + ('446447', True), + ('38593859', False), + ('385385385', True) + ]) +def test_check_valid_id(given, expected): + assert check_valid_id(given) == expected + +@pytest.mark.parametrize('given, expected', + [ + ('1', True), + ('12', True), + ('1012', True), + ('446447', True), + ('11', False), + ('22', False), + ('99', False), + ('111', False), + ('999', False), + ('1010', False), + ('1188511885', False), + ('222222', False), + ('446446', False), + ('565656', False), + ('38593859', False), + ('385385385', False), + ('824824824', False), + ('2121212121', False) + ]) +def test_extended_valid_id(given, expected): + assert check_extended_id(given) == expected + +@pytest.mark.parametrize('given, expected', + [ + ( + [ + ('11', '22'), + ('95', '115'), + ('998', '1012'), + ('1188511880', '1188511890'), + ('222220', '222224'), + ('1698522', '1698528'), + ('446443', '446449'), + ('38593856', '38593862') + ], + 1227775554 + ) + ]) +def test_part1_examples(given, expected): + assert part1(given) == expected + +@pytest.mark.parametrize('given, expected', + [ + ( + [ + ('11', '22'), + ('95', '115'), + ('998', '1012'), + ('1188511880', '1188511890'), + ('222220', '222224'), + ('1698522', '1698528'), + ('446443', '446449'), + ('38593856', '38593862'), + ('565653', '565659'), + ('824824821', '824824827'), + ('2121212118', '2121212124') + ], + 4174379265 + ) + ]) +def test_part2_examples(given, expected): + assert part2(given) == expected