Part 1
Cute problem. It seemed very simple from the very beginning, and the examples even suggested a sort of symmetry (0 to 6 to 10 to 12 and repeating on reverse order). It took only a brief pen sketching, and I was confident enough on the code.
Tests
TEST_DATA = """
Time: 7 15 30
Distance: 9 40 200
"""
class Test202305_01(unittest.TestCase):
def test_transform_input_into_game_data(self):
games = get_game_data(TEST_DATA)
self.assertEqual(
games,
[(7, 9), (15, 40), (30, 200)]
)
def test_get_list_of_all_game_possibilities(self):
games = get_game_data(TEST_DATA)
possibilities = get_all_game_possibilities(games[0])
self.assertEqual(
possibilities,
[6, 10, 12, 12, 10, 6]
)
def test_get_list_of_all_winning_times_for_one_game(self):
games = get_game_data(TEST_DATA)
possibilities = get_winning_times(games[0])
self.assertEqual(
possibilities,
[10, 12, 12, 10]
)
def test_get_list_of_all_winning_times_for_all_games(self):
games = get_game_data(TEST_DATA)
possibilities = get_winning_times_for_all_games(games)
self.assertEqual(
possibilities,
[4, 8, 9]
)
def test_solve_part_one(self):
self.assertEqual(
solve_part_one(TEST_DATA),
288
)
Code
On get_distance_upon_release
, I noticed the hint of a quadratic formula (time_elapsed
would be squared). Some people on the AOC's subreddit pursued this method, which is, of course, a little more interesting. I went by bruteforcing anyway, but I do realise this would probably be the best course of action.
def get_game_data(data: str):
nbr_lists = map(lambda line: list(map(int, line)),
map(lambda line: line.split(':')[1].split(),
data.strip().splitlines()))
return list(zip(*nbr_lists))
def get_distance_upon_release(
total_time: int,
time_elapsed: int
) -> int:
return time_elapsed * (total_time - time_elapsed)
def get_all_game_possibilities(
game: list[int, int]
):
total_time, distance = game
return list(map(lambda elapsed: get_distance_upon_release(total_time, elapsed),
range(1, total_time)))
def get_winning_times(
game: list[int, int]
):
total_time, distance = game
return list(filter(lambda elapsed: elapsed > distance,
get_all_game_possibilities(game)))
def get_winning_times_for_all_games(
games: list
):
return list(map(len, map(get_winning_times, games)))
def solve_part_one(
data: str
) -> int:
games = get_game_data(data)
return reduce(lambda x, y: x * y,
get_winning_times_for_all_games(games))
with open('input') as f:
TEST_DATA = f.read()
print(solve_part_one(TEST_DATA))
Part 2
For Part 2, it was basically the same exercise — and in fact one of the very few cases where Part 2 entails less code than the first. Bruteforcing using the same algorithm takes a while (approximately 5 to 7 seconds on lardafada); still, not enough to make me implement the more sensible, mathematical solution.
Tests
TEST_DATA = """
Time: 7 15 30
Distance: 9 40 200
"""
class Test202305_02(unittest.TestCase):
def test_transform_input_into_game_data(self):
game = get_game_data(TEST_DATA)
self.assertEqual(
game,
[71530, 940200]
)
def test_get_list_of_all_winning_times_for_one_game(self):
game = get_game_data(TEST_DATA)
possibilities = get_winning_times(game)
self.assertEqual(
len(possibilities),
71503
)
Code
from functools import reduce
def get_game_data(data: str):
nbr_lists = map(lambda line: int("".join(line)),
map(lambda line: line.split(':')[1].split(),
data.strip().splitlines()))
return list(nbr_lists)
def get_distance_upon_release(
total_time: int,
time_elapsed: int
) -> int:
return time_elapsed * (total_time - time_elapsed)
def get_all_game_possibilities(
game: list[int, int]
):
total_time, distance = game
return list(map(lambda elapsed: get_distance_upon_release(total_time, elapsed),
range(1, total_time)))
def get_winning_times(
game: list[int, int]
):
total_time, distance = game
return list(filter(lambda elapsed: elapsed > distance,
get_all_game_possibilities(game)))
def solve_part_two(
data: str
) -> int:
game = get_game_data(data)
return len(get_winning_times(game))
with open('input') as f:
DATA = f.read()
print(solve_part_two(DATA))