Part 1
This was fairly easy to understand and implement. I suppose the hardest part was trying to figure out an elegant, functional way to apply the same function to the two resulting lists of splitting the card line at the |
char.
Tests
import unittest
from d0401 import (
get_game_data_from_input,
get_score_from_card,
calculate_sum_of_cards
)
TEST_DATA = """
Card 1: 41 48 83 86 17 | 83 86 6 31 17 9 48 53
Card 2: 13 32 20 16 61 | 61 30 68 82 17 32 24 19
Card 3: 1 21 53 59 44 | 69 82 63 72 16 21 14 1
Card 4: 41 92 73 84 69 | 59 84 76 51 58 5 54 83
Card 5: 87 83 26 28 32 | 88 30 70 12 93 22 82 36
Card 6: 31 18 13 56 72 | 74 77 10 23 35 67 36 11
"""
class Test202304_01(unittest.TestCase):
def test_transform_input_into_game_data(self):
cards = get_game_data_from_input(TEST_DATA)
self.assertEqual(
cards,
[[[41, 48, 83, 86, 17],
[83, 86, 6, 31, 17, 9, 48, 53]],
[[13, 32, 20, 16, 61], [61, 30, 68, 82, 17, 32, 24, 19]], [[1, 21, 53, 59, 44], [69, 82, 63, 72, 16, 21, 14, 1]], [[41, 92, 73, 84, 69], [59, 84, 76, 51, 58, 5, 54, 83]], [[87, 83, 26, 28, 32], [88, 30, 70, 12, 93, 22, 82, 36]], [[31, 18, 13, 56, 72], [74, 77, 10, 23, 35, 67, 36, 11]]]
)
def test_get_score_from_card(self):
cards = get_game_data_from_input(TEST_DATA)
self.assertEqual(
get_score_from_card(cards[0]),
8
)
cards = get_game_data_from_input(TEST_DATA)
self.assertEqual(
get_score_from_card(cards[1]),
2
)
cards = get_game_data_from_input(TEST_DATA)
self.assertEqual(
get_score_from_card(cards[3]),
1
)
cards = get_game_data_from_input(TEST_DATA)
self.assertEqual(
get_score_from_card(cards[4]),
0
)
def test_sum_of_cards(self):
cards = get_game_data_from_input(TEST_DATA)
self.assertEqual(
calculate_sum_of_cards(cards),
13
)
if __name__ == '__main__':
unittest.main()
Code
def str_to_int_list(s: str) -> list[int]:
return list(map(int, s.split()))
def get_game_data_from_input(
data: str
) -> list[list[int]]:
res = list(map(lambda pair: [str_to_int_list(pair[0]),
str_to_int_list(pair[1])],
map(lambda line: line.split(":")[1].split("|"),
data.strip().splitlines())))
return res
def get_score_from_card(
card: list[list[int]]
) -> int:
winning, chance = card
found = [x for x in chance if x in winning]
return (2 ** (len(found) - 1) if len(found) > 1 else len(found))
def calculate_sum_of_cards(
cards: list[list[list[int]]]
) -> int:
return sum(map(lambda card: get_score_from_card(card),
cards))
def solve_part_1():
with open("input") as f:
data = f.read()
cards = get_game_data_from_input(data)
return calculate_sum_of_cards(cards)
Part 2
Part two introduces the fun twist of accumulating more scratchcards. There are many possibilities to implement this algorithm, but repeated calculations will ideally be avoided; eg. card 1 duplicates cards 2, 3, 4 and 5; card 2 will duplicate 3 and 4; etc.
I'm not quite sure this was the right approach.
Tests
class Test202304_02(unittest.TestCase):
def test_transform_input_into_game_data(self):
cards = get_game_data_from_input_with_card_nbr(TEST_DATA)
self.assertEqual(
cards[:3],
((1, [[41, 48, 83, 86, 17],
[83, 86, 6, 31, 17, 9, 48, 53]]),
(2, [[13, 32, 20, 16, 61],
[61, 30, 68, 82, 17, 32, 24, 19]]),
(3, [[1, 21, 53, 59, 44],
[69, 82, 63, 72, 16, 21, 14, 1]]))
)
def test_get_card_nbrs_to_repeat(self):
cards = get_game_data_from_input_with_card_nbr(TEST_DATA)
self.assertEqual(
get_card_nbrs_to_repeat(cards[0]),
[2, 3, 4, 5]
)
self.assertEqual(
get_card_nbrs_to_repeat(cards[1]),
[3, 4]
)
self.assertEqual(
get_card_nbrs_to_repeat(cards[2]),
[4, 5]
)
self.assertEqual(
get_card_nbrs_to_repeat(cards[3]),
[5]
)
self.assertEqual(
get_card_nbrs_to_repeat(cards[4]),
[]
)
def test_get_new_card_counts(self):
cards = get_game_data_from_input_with_card_nbr(TEST_DATA)
current_counts = [1 for _ in range(len(cards))]
all_counts = [
[1, 2, 2, 2, 2, 1],
[1, 2, 4, 4, 2, 1],
[1, 2, 4, 8, 6, 1],
[1, 2, 4, 8, 14, 1],
[1, 2, 4, 8, 14, 1],
[1, 2, 4, 8, 14, 1]
]
for i in range(len(cards)):
card = cards[i]
current_counts = get_new_card_count(card, current_counts)
self.assertEqual(
current_counts,
all_counts[i]
)
def test_get_total_card_count(self):
cards = get_game_data_from_input_with_card_nbr(TEST_DATA)
self.assertEqual(
calculate_total_card_count(
cards,
[1 for _ in range(len(cards))],
),
30)
```
<h1 style="text-align: center">Code</h1>
```python
def str_to_int_list(s: str) -> list[int]:
return list(map(int, s.split()))
def get_game_data_from_input_with_card_nbr(
data: str
) -> tuple[int, tuple[list[int], list[int]]]:
cards = tuple(map(lambda pair: [str_to_int_list(pair[0]),
str_to_int_list(pair[1])],
map(lambda line: line.split(":")[1].split("|"),
data.strip().splitlines())))
return tuple(zip(range(1, len(cards) + 1),
cards))
def get_card_nbrs_to_repeat(
card: tuple[int, tuple[list[int], list[int]]],
) -> list:
nbr, [winning, chance] = card
found = [x for x in chance if x in winning]
return (list(range(nbr + 1, nbr + 1 + len(found)))
if len(found) >= 1 else [])
def get_new_card_count(
card: tuple[int, tuple[list[int], list[int]]],
counts: list[int]
) -> list[int]:
card_nbr, _ = card
dups = get_card_nbrs_to_repeat(card)
return (counts[:card_nbr] +
[counts[card_nbr - 1] + counts[nbr - 1]
for nbr in dups]
+ counts[card_nbr + len(dups):])
def calculate_total_card_count(
cards: list[list[list[int]]],
current_counts: list[int],
) -> int:
if len(cards) == 0:
return sum(current_counts)
else:
return calculate_total_card_count(
cards[1:],
get_new_card_count(cards[0], current_counts)
)
def solve_part_2():
with open("input") as f:
data = f.read()
cards = get_game_data_from_input_with_card_nbr(data)
return calculate_total_card_count(
cards,
[1 for _ in range(len(cards))])