Skip to content

Commit c850d43

Browse files
committed
Add materials for Advent of Code tutorial
1 parent 09239e7 commit c850d43

File tree

20 files changed

+540
-0
lines changed

20 files changed

+540
-0
lines changed

advent-of-code/README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Advent of Code: Solving Your Puzzles With Python
2+
3+
This repository holds the code for the Real Python [Advent of Code: Solving Your Puzzles With Python](https://realpython.com/python-advent-of-code/) tutorial.
4+
5+
## Dependencies
6+
7+
[Pytest](https://realpython.com/pytest-python-testing/) is used for testing. You should first create a virtual environment:
8+
9+
```console
10+
$ python -m venv venv
11+
$ source venv/bin/activate
12+
```
13+
14+
You can then install `pytest` with `pip`:
15+
16+
```console
17+
(venv) $ python -m pip install pytest
18+
```
19+
20+
The [aoc_grid.py](aoc_grid.py) example uses [Colorama](https://pypi.org/project/colorama/) and [NumPy](https://realpython.com/numpy-tutorial/). To run that example, you should also install those packages into your environment:
21+
22+
```console
23+
(venv) $ python -m pip install colorama numpy
24+
```
25+
26+
The puzzle solutions only use Python's standard library. Note that the solution to [Day 5, 2021](solutions/2021/05_hydrothermal_venture/) uses [structural pattern matching](https://realpython.com/python310-new-features/#structural-pattern-matching) which is only available in [Python 3.10](https://realpython.com/python310-new-features/) and later.
27+
28+
## Author
29+
30+
- **Geir Arne Hjelle**, E-mail: [[email protected]]([email protected])
31+
32+
## License
33+
34+
Distributed under the MIT license. See [`LICENSE`](../LICENSE) for more information.

advent-of-code/aoc_grid.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# aoc_grid.py
2+
3+
import numpy as np
4+
from colorama import Cursor
5+
6+
grid = np.array(
7+
[
8+
[1, 1, 1, 1, 1],
9+
[1, 0, 0, 0, 1],
10+
[1, 1, 1, 0, 1],
11+
[1, 0, 0, 2, 1],
12+
[1, 1, 1, 1, 1],
13+
]
14+
)
15+
16+
num_rows, num_cols = grid.shape
17+
for row in range(num_rows):
18+
for col in range(num_cols):
19+
symbol = " *o"[grid[row, col]]
20+
print(f"{Cursor.POS(col + 1, row + 2)}{symbol}")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# aoc_state_machine.py
2+
3+
from dataclasses import dataclass
4+
5+
@dataclass
6+
class StateMachine:
7+
memory: dict[str, int]
8+
program: list[str]
9+
10+
def run(self):
11+
"""Run the program"""
12+
current_line = 0
13+
while current_line < len(self.program):
14+
instruction = self.program[current_line]
15+
16+
# Set a register to a value
17+
if instruction.startswith("set "):
18+
register, value = instruction[4], int(instruction[6:])
19+
self.memory[register] = value
20+
21+
# Increase the value in a register by 1
22+
elif instruction.startswith("inc "):
23+
register = instruction[4]
24+
self.memory[register] += 1
25+
26+
# Move the line pointer
27+
current_line += 1
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""AoC 1, 2019: The Tyranny of the Rocket Equation"""
2+
3+
# Standard library imports
4+
import pathlib
5+
import sys
6+
7+
8+
def parse(puzzle_input):
9+
"""Parse input"""
10+
return [int(line) for line in puzzle_input.split("\n")]
11+
12+
13+
def part1(modules):
14+
"""Solve part 1"""
15+
return sum(mass // 3 - 2 for mass in modules)
16+
17+
18+
def part2(modules):
19+
"""Solve part 2"""
20+
return sum(all_fuel(mass) for mass in modules)
21+
22+
23+
def all_fuel(mass):
24+
"""Calculate fuel while taking mass of the fuel into account.
25+
26+
## Example:
27+
28+
>>> all_fuel(1969)
29+
966
30+
"""
31+
fuel = mass // 3 - 2
32+
if fuel <= 0:
33+
return 0
34+
else:
35+
return fuel + all_fuel(mass=fuel)
36+
37+
38+
def solve(puzzle_input):
39+
"""Solve the puzzle for the given input"""
40+
data = parse(puzzle_input)
41+
solution1 = part1(data)
42+
solution2 = part2(data)
43+
44+
return solution1, solution2
45+
46+
47+
if __name__ == "__main__":
48+
for path in sys.argv[1:]:
49+
print(f"\n{path}:")
50+
solutions = solve(puzzle_input=pathlib.Path(path).read_text().strip())
51+
print("\n".join(str(solution) for solution in solutions))
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
12
2+
14
3+
1969
4+
100756
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""Tests for AoC 1, 2019: The Tyranny of the Rocket Equation"""
2+
3+
# Standard library imports
4+
import pathlib
5+
6+
# Third party imports
7+
import aoc201901 as aoc
8+
import pytest
9+
10+
PUZZLE_DIR = pathlib.Path(__file__).parent
11+
12+
13+
@pytest.fixture
14+
def example1():
15+
puzzle_input = (PUZZLE_DIR / "example1.txt").read_text().strip()
16+
return aoc.parse(puzzle_input)
17+
18+
19+
def test_parse_example1(example1):
20+
"""Test that input is parsed properly"""
21+
assert example1 == [12, 14, 1969, 100756]
22+
23+
24+
def test_part1_example1(example1):
25+
"""Test part 1 on example input"""
26+
assert aoc.part1(example1) == 2 + 2 + 654 + 33583
27+
28+
29+
def test_part2_example1(example1):
30+
"""Test part 2 on example input"""
31+
assert aoc.part2(example1) == 2 + 2 + 966 + 50346
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""AoC 1, 2020: Report Repair"""
2+
3+
# Standard library imports
4+
import pathlib
5+
import sys
6+
7+
8+
def parse(puzzle_input):
9+
"""Parse input"""
10+
return [int(line) for line in puzzle_input.split()]
11+
12+
13+
def part1(numbers):
14+
"""Solve part 1"""
15+
for num1 in numbers:
16+
for num2 in numbers:
17+
if num1 < num2 and num1 + num2 == 2020:
18+
return num1 * num2
19+
20+
21+
def part2(numbers):
22+
"""Solve part 2"""
23+
24+
25+
def solve(puzzle_input):
26+
"""Solve the puzzle for the given input"""
27+
data = parse(puzzle_input)
28+
solution1 = part1(data)
29+
solution2 = part2(data)
30+
31+
return solution1, solution2
32+
33+
34+
if __name__ == "__main__":
35+
for path in sys.argv[1:]:
36+
print(f"\n{path}:")
37+
solutions = solve(puzzle_input=pathlib.Path(path).read_text().strip())
38+
print("\n".join(str(solution) for solution in solutions))
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
1721
2+
979
3+
366
4+
299
5+
675
6+
1456
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Tests for AoC 1, 2020: Report Repair"""
2+
3+
# Standard library imports
4+
import pathlib
5+
6+
# Third party imports
7+
import aoc202001 as aoc
8+
import pytest
9+
10+
PUZZLE_DIR = pathlib.Path(__file__).parent
11+
12+
13+
@pytest.fixture
14+
def example1():
15+
puzzle_input = (PUZZLE_DIR / "example1.txt").read_text().strip()
16+
return aoc.parse(puzzle_input)
17+
18+
19+
def test_parse_example1(example1):
20+
"""Test that input is parsed properly"""
21+
assert example1 == [1721, 979, 366, 299, 675, 1456]
22+
23+
24+
def test_part1_example1(example1):
25+
"""Test part 1 on example input"""
26+
assert aoc.part1(example1) == 514579
27+
28+
29+
@pytest.mark.skip(reason="Not implemented")
30+
def test_part2_example1(example1):
31+
"""Test part 2 on example input"""
32+
assert aoc.part2(example1) == ...
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""AoC 5, 2020: Binary Boarding"""
2+
3+
# Standard library imports
4+
import pathlib
5+
import sys
6+
7+
BP2BINARY = str.maketrans({"F": "0", "B": "1", "L": "0", "R": "1"})
8+
9+
10+
def parse(puzzle_input):
11+
"""Parse input"""
12+
return [
13+
int(bp.translate(BP2BINARY), base=2) for bp in puzzle_input.split("\n")
14+
]
15+
16+
17+
def part1(seat_ids):
18+
"""Solve part 1"""
19+
return max(seat_ids)
20+
21+
22+
def part2(seat_ids):
23+
"""Solve part 2"""
24+
all_ids = set(range(min(seat_ids), max(seat_ids) + 1))
25+
return (all_ids - set(seat_ids)).pop()
26+
27+
28+
def solve(puzzle_input):
29+
"""Solve the puzzle for the given input"""
30+
data = parse(puzzle_input)
31+
solution1 = part1(data)
32+
solution2 = part2(data)
33+
34+
return solution1, solution2
35+
36+
37+
if __name__ == "__main__":
38+
for path in sys.argv[1:]:
39+
print(f"\n{path}:")
40+
solutions = solve(puzzle_input=pathlib.Path(path).read_text().strip())
41+
print("\n".join(str(solution) for solution in solutions))

0 commit comments

Comments
 (0)