From 6611c8c4c129314f73821e98c836e0ea925c2490 Mon Sep 17 00:00:00 2001 From: James Lawlor Date: Tue, 9 Dec 2025 09:03:51 +0200 Subject: [PATCH 1/3] 2025d3-2 --- src/advent_of_code/year_2025/day_03.py | 37 ++++++++++++++----------- tests/year_2025/test_day_03.py | 38 ++++++++++++++++---------- 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/src/advent_of_code/year_2025/day_03.py b/src/advent_of_code/year_2025/day_03.py index d5f4a23..30b92be 100644 --- a/src/advent_of_code/year_2025/day_03.py +++ b/src/advent_of_code/year_2025/day_03.py @@ -4,44 +4,49 @@ def parse_battery_bank_to_ints_list(battery_bank_str): return [int(x) for x in battery_bank_str] -def combine_joltages(j1, j2): - return int(f"{j1}{j2}") +def combine_joltages(list_of_joltages): + return int("".join([str(x) for x in list_of_joltages])) def calculate_largest_joltage(battery_bank_as_ints): largest_joltage_1 = 0 - - print(f"Calculating largest joltage for battery bank: {battery_bank_as_ints}") - for battery_index, battery_value in enumerate(battery_bank_as_ints): - print(f"battery_value={battery_value}, battery_index={battery_index}") if (battery_value > largest_joltage_1) and (battery_index < len(battery_bank_as_ints) - 1): largest_joltage_1 = battery_value largest_joltage_2 = 0 - print(f"Larger value found! Setting largest_joltage_1 to {largest_joltage_1}") for second_battery_value in battery_bank_as_ints[battery_index+1:]: - print(f"second_battery_value={second_battery_value}") if second_battery_value > largest_joltage_2: largest_joltage_2 = second_battery_value - print(f"Larger value found! Setting largest_joltage_2 to {largest_joltage_2}") - - print(f"Largest joltage combination: {largest_joltage_1}, {largest_joltage_2}") - return combine_joltages(largest_joltage_1, largest_joltage_2) + return combine_joltages([largest_joltage_1, largest_joltage_2]) + -def solve_part_1(input): +def calculate_largest_joltage_part_2(battery_bank_as_ints, n_batteries=12): + sorted_batteries = sorted(battery_bank_as_ints, reverse=True) + return combine_joltages(sorted_batteries[:n_batteries]) + + +def solve_part(input, part=1): joltage_list = [] + + if part == 1: + calculate_largest_joltage_function = calculate_largest_joltage + elif part == 2: + calculate_largest_joltage_function = calculate_largest_joltage_part_2 + else: + raise ValueError("Part must be 1 or 2") + for battery_bank_str in input: battery_bank_as_ints = parse_battery_bank_to_ints_list(battery_bank_str) - largest_joltage = calculate_largest_joltage(battery_bank_as_ints) + largest_joltage = calculate_largest_joltage_function(battery_bank_as_ints) joltage_list.append(largest_joltage) return sum(joltage_list) def solve(input): - part_1_solution = solve_part_1(input) - part_2_solution = None + part_1_solution = solve_part(input, part=1) + part_2_solution = solve_part(input, part=2) return (part_1_solution, part_2_solution) diff --git a/tests/year_2025/test_day_03.py b/tests/year_2025/test_day_03.py index 0d27bc5..151dd6b 100644 --- a/tests/year_2025/test_day_03.py +++ b/tests/year_2025/test_day_03.py @@ -5,6 +5,7 @@ calculate_largest_joltage, parse_battery_bank_to_ints_list, combine_joltages, + calculate_largest_joltage_part_2, ) @@ -20,29 +21,38 @@ def day_03_test_input(): @pytest.fixture def day_03_expected_output(): - return (357, None) + return (357, 3121910778619) def test_solver(day_03_test_input, day_03_expected_output): result = solve(day_03_test_input) assert result == day_03_expected_output -@pytest.mark.parametrize( - "input, expected_output", - [ - [[9,8,7,6,5,4,3,2,1,1,1,1,1,1,1],98], - [[8,1,1,1,1,1,1,1,1,1,1,1,1,1,9],89], - [[2,3,4,2,3,4,2,3,4,2,3,4,2,7,8],78], - [[8,1,8,1,8,1,9,1,1,1,1,2,1,1,1],92], - ], -) -def test_calculate_largest_joltage(input, expected_output): - parsed_inputs = parse_battery_bank_to_ints_list(input) - assert expected_output == calculate_largest_joltage(parsed_inputs) + + +@pytest.fixture +def test_calculate_largest_joltage_params(): + return [ + ("987654321111111", 98, 987654321111), + ("811111111111119", 89, 811111111119), + ("234234234234278", 78, 434234234278), + ("818181911112111", 92, 888911112111), + ] + +def test_calculate_largest_joltage_part_1(test_calculate_largest_joltage_params): + for input_str, expected_output, _ in test_calculate_largest_joltage_params: + parsed_inputs = parse_battery_bank_to_ints_list(input_str) + assert expected_output == calculate_largest_joltage(parsed_inputs) + + +def test_calculate_largest_joltage_part_2(test_calculate_largest_joltage_params): + for input_str, _, expected_output in test_calculate_largest_joltage_params: + parsed_inputs = parse_battery_bank_to_ints_list(input_str) + assert expected_output == calculate_largest_joltage_part_2(parsed_inputs) def test_parse_battery_bank_to_ints_list(): assert parse_battery_bank_to_ints_list("98765") == [9,8,7,6,5] def test_combine_joltages(): - assert combine_joltages(9, 8) == 98 \ No newline at end of file + assert combine_joltages([9, 8, 7]) == 987 \ No newline at end of file From 9014532b40953de859eb31af987a3f9f5c1620a8 Mon Sep 17 00:00:00 2001 From: James Lawlor Date: Tue, 9 Dec 2025 09:15:36 +0200 Subject: [PATCH 2/3] refactor for geneeral n_batteries case --- src/advent_of_code/year_2025/day_03.py | 22 +++++----------------- tests/year_2025/test_day_03.py | 16 ++++------------ 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/src/advent_of_code/year_2025/day_03.py b/src/advent_of_code/year_2025/day_03.py index 30b92be..b6ec80e 100644 --- a/src/advent_of_code/year_2025/day_03.py +++ b/src/advent_of_code/year_2025/day_03.py @@ -8,7 +8,7 @@ def combine_joltages(list_of_joltages): return int("".join([str(x) for x in list_of_joltages])) -def calculate_largest_joltage(battery_bank_as_ints): +def calculate_largest_joltage(battery_bank_as_ints, n_batteries): largest_joltage_1 = 0 for battery_index, battery_value in enumerate(battery_bank_as_ints): @@ -22,31 +22,19 @@ def calculate_largest_joltage(battery_bank_as_ints): return combine_joltages([largest_joltage_1, largest_joltage_2]) -def calculate_largest_joltage_part_2(battery_bank_as_ints, n_batteries=12): - sorted_batteries = sorted(battery_bank_as_ints, reverse=True) - return combine_joltages(sorted_batteries[:n_batteries]) - - -def solve_part(input, part=1): +def solve_part(input, n_batteries): joltage_list = [] - if part == 1: - calculate_largest_joltage_function = calculate_largest_joltage - elif part == 2: - calculate_largest_joltage_function = calculate_largest_joltage_part_2 - else: - raise ValueError("Part must be 1 or 2") - for battery_bank_str in input: battery_bank_as_ints = parse_battery_bank_to_ints_list(battery_bank_str) - largest_joltage = calculate_largest_joltage_function(battery_bank_as_ints) + largest_joltage = calculate_largest_joltage(battery_bank_as_ints, n_batteries) joltage_list.append(largest_joltage) return sum(joltage_list) def solve(input): - part_1_solution = solve_part(input, part=1) - part_2_solution = solve_part(input, part=2) + part_1_solution = solve_part(input, n_batteries=2) + part_2_solution = solve_part(input, n_batteries=12) return (part_1_solution, part_2_solution) diff --git a/tests/year_2025/test_day_03.py b/tests/year_2025/test_day_03.py index 151dd6b..0c9c8d6 100644 --- a/tests/year_2025/test_day_03.py +++ b/tests/year_2025/test_day_03.py @@ -5,7 +5,6 @@ calculate_largest_joltage, parse_battery_bank_to_ints_list, combine_joltages, - calculate_largest_joltage_part_2, ) @@ -29,7 +28,6 @@ def test_solver(day_03_test_input, day_03_expected_output): assert result == day_03_expected_output - @pytest.fixture def test_calculate_largest_joltage_params(): return [ @@ -39,17 +37,11 @@ def test_calculate_largest_joltage_params(): ("818181911112111", 92, 888911112111), ] -def test_calculate_largest_joltage_part_1(test_calculate_largest_joltage_params): - for input_str, expected_output, _ in test_calculate_largest_joltage_params: +def test_calculate_largest_joltage(test_calculate_largest_joltage_params): + for input_str, expected_output_part_1, expected_output_part_2 in test_calculate_largest_joltage_params: parsed_inputs = parse_battery_bank_to_ints_list(input_str) - assert expected_output == calculate_largest_joltage(parsed_inputs) - - -def test_calculate_largest_joltage_part_2(test_calculate_largest_joltage_params): - for input_str, _, expected_output in test_calculate_largest_joltage_params: - parsed_inputs = parse_battery_bank_to_ints_list(input_str) - assert expected_output == calculate_largest_joltage_part_2(parsed_inputs) - + assert expected_output_part_1 == calculate_largest_joltage(parsed_inputs, n_batteries=2) + assert expected_output_part_2 == calculate_largest_joltage(parsed_inputs, n_batteries=12) def test_parse_battery_bank_to_ints_list(): assert parse_battery_bank_to_ints_list("98765") == [9,8,7,6,5] From e2d92a93b2a0544c5b18e9d8602b925a897d5086 Mon Sep 17 00:00:00 2001 From: James Lawlor Date: Wed, 10 Dec 2025 07:18:51 +0200 Subject: [PATCH 3/3] d3 part 2 --- src/advent_of_code/year_2024/day_04.py | 1 - src/advent_of_code/year_2025/day_02.py | 19 +++++++--- src/advent_of_code/year_2025/day_03.py | 40 +++++++++++++++------ tests/year_2023/test_day_01.py | 1 - tests/year_2023/test_day_02.py | 1 - tests/year_2023/test_day_03.py | 1 - tests/year_2023/test_day_04.py | 1 - tests/year_2023/test_day_06.py | 1 - tests/year_2024/test_day_01.py | 1 - tests/year_2024/test_day_02.py | 1 - tests/year_2024/test_day_03.py | 1 - tests/year_2024/test_day_04.py | 1 - tests/year_2025/test_day_01.py | 1 - tests/year_2025/test_day_02.py | 25 +++++++++---- tests/year_2025/test_day_03.py | 49 ++++++++++++++++++-------- 15 files changed, 96 insertions(+), 48 deletions(-) diff --git a/src/advent_of_code/year_2024/day_04.py b/src/advent_of_code/year_2024/day_04.py index 66f824e..97e8372 100644 --- a/src/advent_of_code/year_2024/day_04.py +++ b/src/advent_of_code/year_2024/day_04.py @@ -1,5 +1,4 @@ import numpy as np - from advent_of_code.utils.input_handling import read_input STRING_TO_FIND = "XMAS" diff --git a/src/advent_of_code/year_2025/day_02.py b/src/advent_of_code/year_2025/day_02.py index 2e44830..5b8b6f1 100644 --- a/src/advent_of_code/year_2025/day_02.py +++ b/src/advent_of_code/year_2025/day_02.py @@ -1,6 +1,7 @@ from advent_of_code.utils.input_handling import read_input -VALID_PARTS = (1,2) +VALID_PARTS = (1, 2) + def parse_input(raw): raw_split = raw[0].split(",") @@ -25,17 +26,23 @@ def check_id_valid(id): else: return True + def check_id_valid_v2(id): id_len = len(str(id)) half_len = id_len // 2 - for sub_str_len in range(1,half_len+1): + for sub_str_len in range(1, half_len + 1): sub_str = id[:sub_str_len] str_to_search = id[sub_str_len:] - if (len(str_to_search) % sub_str_len == 0): # substr divisible by search str length - if (sub_str * (id_len // sub_str_len) == id): # substr matches N * search string + if ( + len(str_to_search) % sub_str_len == 0 + ): # substr divisible by search str length + if ( + sub_str * (id_len // sub_str_len) == id + ): # substr matches N * search string return False return True + def get_invalid_ids_in_range(range_pair, part): full_range = range(range_pair[0], range_pair[1] + 1) @@ -45,7 +52,7 @@ def get_invalid_ids_in_range(range_pair, part): id_checker_function = check_id_valid_v2 else: raise ValueError("Specify a valid part number") - + invalid_ids = [] for x in full_range: if not id_checker_function(str(x)): @@ -74,10 +81,12 @@ def solve_part(parsed_input, part): invalid_ids = find_all_invalid_ids(parsed_input, part) return sum_invalid_ids(invalid_ids) + def solve_part_2(parsed_input): invalid_ids = find_all_invalid_ids(parsed_input) return sum_invalid_ids(invalid_ids) + def solve(parsed_input): part_1_solution = solve_part(parsed_input, part=1) part_2_solution = solve_part(parsed_input, part=2) diff --git a/src/advent_of_code/year_2025/day_03.py b/src/advent_of_code/year_2025/day_03.py index b6ec80e..4bb8bc3 100644 --- a/src/advent_of_code/year_2025/day_03.py +++ b/src/advent_of_code/year_2025/day_03.py @@ -1,5 +1,6 @@ from advent_of_code.utils.input_handling import read_input + def parse_battery_bank_to_ints_list(battery_bank_str): return [int(x) for x in battery_bank_str] @@ -8,18 +9,34 @@ def combine_joltages(list_of_joltages): return int("".join([str(x) for x in list_of_joltages])) +def find_max_and_leftmost_index(input_list): + max_value = max(input_list) + leftmost_index = input_list.index(max_value) + return max_value, leftmost_index + + def calculate_largest_joltage(battery_bank_as_ints, n_batteries): - largest_joltage_1 = 0 - - for battery_index, battery_value in enumerate(battery_bank_as_ints): - if (battery_value > largest_joltage_1) and (battery_index < len(battery_bank_as_ints) - 1): - largest_joltage_1 = battery_value - largest_joltage_2 = 0 - for second_battery_value in battery_bank_as_ints[battery_index+1:]: - if second_battery_value > largest_joltage_2: - largest_joltage_2 = second_battery_value - - return combine_joltages([largest_joltage_1, largest_joltage_2]) + + batteries_remaining = n_batteries + bank_len = len(battery_bank_as_ints) + list_of_joltages = [] + start_ix = 0 + + while batteries_remaining > 0: + end_ix = bank_len - batteries_remaining + 1 + window_to_check = battery_bank_as_ints[start_ix:end_ix] + + # find largest value in window and the left-most index for dupes + max_value, leftmost_index_window = find_max_and_leftmost_index(window_to_check) + leftmost_index = leftmost_index_window + start_ix + list_of_joltages.append(max_value) + + # reassign start_ix and update remaining batteries + start_ix = leftmost_index + 1 + batteries_remaining -= 1 + + print(f"list_of_joltages={list_of_joltages}") + return combine_joltages(list_of_joltages) def solve_part(input, n_batteries): @@ -32,6 +49,7 @@ def solve_part(input, n_batteries): return sum(joltage_list) + def solve(input): part_1_solution = solve_part(input, n_batteries=2) part_2_solution = solve_part(input, n_batteries=12) diff --git a/tests/year_2023/test_day_01.py b/tests/year_2023/test_day_01.py index 0a7451b..8f62d96 100644 --- a/tests/year_2023/test_day_01.py +++ b/tests/year_2023/test_day_01.py @@ -1,5 +1,4 @@ import pytest - from advent_of_code.year_2023.day_01 import ( convert_first_last_patterns_to_calibration_value, convert_str_to_numerical, diff --git a/tests/year_2023/test_day_02.py b/tests/year_2023/test_day_02.py index 453f98f..2fbe4b3 100644 --- a/tests/year_2023/test_day_02.py +++ b/tests/year_2023/test_day_02.py @@ -1,5 +1,4 @@ import pytest - from advent_of_code.year_2023.day_02 import ( check_game_is_possible, find_max_shown_for_colour, diff --git a/tests/year_2023/test_day_03.py b/tests/year_2023/test_day_03.py index f4d3756..100e62e 100644 --- a/tests/year_2023/test_day_03.py +++ b/tests/year_2023/test_day_03.py @@ -1,5 +1,4 @@ import pytest - from advent_of_code.year_2023.day_03 import ( Part, Symbol, diff --git a/tests/year_2023/test_day_04.py b/tests/year_2023/test_day_04.py index e52eeec..7a6470f 100644 --- a/tests/year_2023/test_day_04.py +++ b/tests/year_2023/test_day_04.py @@ -1,5 +1,4 @@ import pytest - from advent_of_code.year_2023.day_04 import ( compute_copies, compute_total_score, diff --git a/tests/year_2023/test_day_06.py b/tests/year_2023/test_day_06.py index 09c4366..d9b524c 100644 --- a/tests/year_2023/test_day_06.py +++ b/tests/year_2023/test_day_06.py @@ -1,7 +1,6 @@ import math import pytest - from advent_of_code.year_2023.day_06 import ( Race, Races, diff --git a/tests/year_2024/test_day_01.py b/tests/year_2024/test_day_01.py index fd121da..bcc1919 100644 --- a/tests/year_2024/test_day_01.py +++ b/tests/year_2024/test_day_01.py @@ -1,5 +1,4 @@ import pytest - from advent_of_code.year_2024.day_01 import ( solve, ) diff --git a/tests/year_2024/test_day_02.py b/tests/year_2024/test_day_02.py index eb436a1..d62701d 100644 --- a/tests/year_2024/test_day_02.py +++ b/tests/year_2024/test_day_02.py @@ -1,5 +1,4 @@ import pytest - from advent_of_code.year_2024.day_02 import ( parse_input, solve, diff --git a/tests/year_2024/test_day_03.py b/tests/year_2024/test_day_03.py index 4c96bc1..73dc225 100644 --- a/tests/year_2024/test_day_03.py +++ b/tests/year_2024/test_day_03.py @@ -1,5 +1,4 @@ import pytest - from advent_of_code.year_2024.day_03 import ( instruction_compute, ) diff --git a/tests/year_2024/test_day_04.py b/tests/year_2024/test_day_04.py index 03d37b6..db80e52 100644 --- a/tests/year_2024/test_day_04.py +++ b/tests/year_2024/test_day_04.py @@ -1,5 +1,4 @@ import pytest - from advent_of_code.year_2024.day_04 import ( check_direction_safety, solve, diff --git a/tests/year_2025/test_day_01.py b/tests/year_2025/test_day_01.py index 920f7f9..e0c5f3d 100644 --- a/tests/year_2025/test_day_01.py +++ b/tests/year_2025/test_day_01.py @@ -1,5 +1,4 @@ import pytest - from advent_of_code.year_2025.day_01 import ( points_at_zero_counter, solve, diff --git a/tests/year_2025/test_day_02.py b/tests/year_2025/test_day_02.py index 44098e3..9ad0f54 100644 --- a/tests/year_2025/test_day_02.py +++ b/tests/year_2025/test_day_02.py @@ -1,5 +1,4 @@ import pytest - from advent_of_code.year_2025.day_02 import ( check_id_valid, check_id_valid_v2, @@ -29,6 +28,7 @@ def test_solver(input, expected_output): result = solve(input_parsed) assert result == expected_output + # 11-22 still has two invalid IDs, 11 and 22. # 95-115 now has two invalid IDs, 99 and 111. # 998-1012 now has two invalid IDs, 999 and 1010. @@ -41,6 +41,7 @@ def test_solver(input, expected_output): # 824824821-824824827 now has one invalid ID, 824824824. # 2121212118-2121212124 now has one invalid ID, 2121212121. + @pytest.mark.parametrize( "input, expected_invalid_ids", [ @@ -60,6 +61,7 @@ def test_solver(input, expected_output): def test_find_invalid_ids_in_range_string_part_2(input, expected_invalid_ids): assert expected_invalid_ids == find_invalid_ids_in_range_string(input, part=2) + @pytest.mark.parametrize( "input, expected_invalid_ids", [ @@ -105,19 +107,28 @@ def test_convert_range_string_to_pair(input, expected_pair): @pytest.mark.parametrize( "input, is_valid", - [("11", False), ("12", True), ("22", False), ("998", True), ("999", True), ("1010", False), ("565656", True)], + [ + ("11", False), + ("12", True), + ("22", False), + ("998", True), + ("999", True), + ("1010", False), + ("565656", True), + ], ) def test_check_id_valid(input, is_valid): assert is_valid == check_id_valid(input) + @pytest.mark.parametrize( "input, is_valid", [ - ("11", False), - ("12", True), - ("22", False), - ("998", True), - ("999", False), + ("11", False), + ("12", True), + ("22", False), + ("998", True), + ("999", False), ("1010", False), ("565656", False), ], diff --git a/tests/year_2025/test_day_03.py b/tests/year_2025/test_day_03.py index 0c9c8d6..1e8c3fe 100644 --- a/tests/year_2025/test_day_03.py +++ b/tests/year_2025/test_day_03.py @@ -1,10 +1,9 @@ import pytest - from advent_of_code.year_2025.day_03 import ( - solve, calculate_largest_joltage, - parse_battery_bank_to_ints_list, combine_joltages, + parse_battery_bank_to_ints_list, + solve, ) @@ -28,23 +27,45 @@ def test_solver(day_03_test_input, day_03_expected_output): assert result == day_03_expected_output -@pytest.fixture -def test_calculate_largest_joltage_params(): - return [ +@pytest.mark.parametrize( + "input_str, expected_output_part_1, expected_output_part_2", + [ ("987654321111111", 98, 987654321111), ("811111111111119", 89, 811111111119), ("234234234234278", 78, 434234234278), ("818181911112111", 92, 888911112111), - ] + ], +) +def test_calculate_largest_joltage( + input_str, expected_output_part_1, expected_output_part_2 +): + parsed_inputs = parse_battery_bank_to_ints_list(input_str) + assert expected_output_part_1 == calculate_largest_joltage( + parsed_inputs, n_batteries=2 + ) + assert expected_output_part_2 == calculate_largest_joltage( + parsed_inputs, n_batteries=12 + ) -def test_calculate_largest_joltage(test_calculate_largest_joltage_params): - for input_str, expected_output_part_1, expected_output_part_2 in test_calculate_largest_joltage_params: - parsed_inputs = parse_battery_bank_to_ints_list(input_str) - assert expected_output_part_1 == calculate_largest_joltage(parsed_inputs, n_batteries=2) - assert expected_output_part_2 == calculate_largest_joltage(parsed_inputs, n_batteries=12) def test_parse_battery_bank_to_ints_list(): - assert parse_battery_bank_to_ints_list("98765") == [9,8,7,6,5] + assert parse_battery_bank_to_ints_list("98765") == [9, 8, 7, 6, 5] + def test_combine_joltages(): - assert combine_joltages([9, 8, 7]) == 987 \ No newline at end of file + assert combine_joltages([9, 8, 7]) == 987 + + +@pytest.mark.parametrize( + "input_str, n_batteries, expected_output", + [ + ("987", 2, 98), + ("6781", 2, 81), + ("19181", 2, 98), + ("19182", 3, 982), + ("191821", 3, 982), + ], +) +def test_calculate_largest_joltage_extra_cases(input_str, expected_output, n_batteries): + parsed_inputs = parse_battery_bank_to_ints_list(input_str) + assert expected_output == calculate_largest_joltage(parsed_inputs, n_batteries)