Part 1
I put off solving the exercise right away, but this was actually one of the easiest exercises so far. I might have been lucky to get the approach right without any major issues. My approach to solving these exercises — being test-driven – leaves very little room for error, although I end up producing much more code than needed.
Halfway through solving I realised there was no need to completely follow up until the array of zeroes; getting to the constant array was enough.
Tests
TEST_DATA = """
0 3 6 9 12 15
1 3 6 10 15 21
10 13 16 21 30 45
"""
class Test20230901(unittest.TestCase):
def test_can_get_data_from_input(self):
self.assertEqual(
get_data_from_input(TEST_DATA),
[[0, 3, 6, 9, 12, 15],
[1, 3, 6, 10, 15, 21],
[10, 13, 16, 21, 30, 45]]
)
def test_can_get_sequence_of_differences(self):
lines = get_data_from_input(TEST_DATA)
self.assertEqual(
get_sequence_of_differences(lines[0]),
[3, 3, 3, 3, 3]
)
def test_can_get_successive_sequences_of_differences(self):
lines = get_data_from_input(TEST_DATA)
self.assertEqual(
get_successive_sequences_of_differences(
[lines[0]]),
[
lines[0],
[3, 3, 3, 3, 3],
[0, 0, 0, 0]
]
)
self.assertEqual(
get_successive_sequences_of_differences(
[lines[1]]),
[
lines[1],
[2, 3, 4, 5, 6],
[1, 1, 1, 1],
[0, 0, 0]
]
)
def test_can_extrapolate_one_value(self):
lines = get_data_from_input(TEST_DATA)
all_sequences = get_successive_sequences_of_differences(
[lines[0]]
)
self.assertEqual(
extrapolate_value_on_sequence(
all_sequences[-3],
# gets constant numbers list
all_sequences[-2]
),
[0, 3, 6, 9, 12, 15, 18]
)
def test_can_extrapolate_all_values_of_one_sequence(self):
lines = get_data_from_input(TEST_DATA)
results = [
[0, 3, 6, 9, 12, 15, 18],
[1, 3, 6, 10, 15, 21, 28],
[10, 13, 16, 21, 30, 45, 68]
]
for i, line in enumerate(lines):
all_sequences = get_successive_sequences_of_differences([line])
self.assertEqual(
get_new_value_on_main_sequence(
all_sequences[:-1],
all_sequences[-1]
),
results[i]
)
def test_can_extrapolate_all_values_of_all_sequences(self):
lines = get_data_from_input(TEST_DATA)
self.assertEqual(
get_new_values_on_all_main_sequences(lines, []),
[
[0, 3, 6, 9, 12, 15, 18],
[1, 3, 6, 10, 15, 21, 28],
[10, 13, 16, 21, 30, 45, 68]
]
)
def test_can_get_sum_of_new_values(self):
lines = get_data_from_input(TEST_DATA)
self.assertEqual(
get_sum_of_new_values_on_all_main_sequences(lines),
114
)
Code
def get_data_from_input(data_input: str):
return list(map(lambda x: list(map(int, x.split())),
data_input.strip().splitlines()))
def get_sequence_of_differences(numbers: list[int]):
return list(map(lambda x: x[1] - x[0],
zip(numbers, numbers[1:])))
def get_successive_sequences_of_differences(
past: list[list[int]],
) -> list[list[int]]:
if set(past[-1]) == {0}:
return past
return get_successive_sequences_of_differences(
past + [get_sequence_of_differences(past[-1])]
)
def extrapolate_value_on_sequence(
sequence: list[int],
extrapolate_from: list[int],
) -> list[int]:
return sequence + [sequence[-1] + extrapolate_from[-1]]
def get_new_value_on_main_sequence(
old_sequences: list[list[int]],
extrapolate_from: list[int],
) -> list[int]:
if len(old_sequences) == 0:
return extrapolate_from
return get_new_value_on_main_sequence(
old_sequences[:-1],
extrapolate_value_on_sequence(
old_sequences[-1],
extrapolate_from
)
)
def get_new_values_on_all_main_sequences(
sequences: list[list[int]],
new_sequences: list[list[int]]
) -> list[list[int]]:
if sequences == []:
return new_sequences
all_extrapolated_sequences = (
get_successive_sequences_of_differences([sequences[0]]))
return get_new_values_on_all_main_sequences(
sequences[1:],
new_sequences + [
get_new_value_on_main_sequence(
all_extrapolated_sequences,
all_extrapolated_sequences[-1]
)
]
)
def get_sum_of_new_values_on_all_main_sequences(
sequences: list[list[int]]
) -> int:
return sum(map(lambda x: x[-1],
get_new_values_on_all_main_sequences(
sequences,
[])))
with open("input") as f:
print(get_sum_of_new_values_on_all_main_sequences(
get_data_from_input(f.read())))
Part 2
Long before I gave much thought to Part 2, I wondered whether it would be just a matter of inverting the lines. And since the result immediately matched the assignment example, I submitted my answer right away. And it worked. So I ended up doing just a small test for inverting the input lines, and nothing more, really. What a nice Part 2!
Code
def get_data_from_input(data_input: str):
return list(map(lambda x: list(map(int, x.split()))[::-1],
data_input.strip().splitlines()))
...