2 Advent Of Code
3 Advent of Code: Python으로 퍼즐 풀기
이 리포지토리는 Real Python의 Advent of Code: Python으로 퍼즐 풀기 튜토리얼 코드를 담고 있습니다.
3.1 의존성
테스트에는 Pytest가 사용됩니다. 먼저 가상 환경을 만들어야 합니다:
$ python -m venv venv
$ source venv/bin/activate
그런 다음 pip로 pytest를 설치할 수 있습니다:
(venv) $ python -m pip install pytest
aoc_grid.py 예제는 Colorama와 NumPy를 사용합니다. 해당 예제를 실행하려면 해당 패키지도 환경에 설치해야 합니다:
(venv) $ python -m pip install colorama numpy
퍼즐 솔루션은 Python의 표준 라이브러리만 사용합니다. 2021년 5일차 솔루션은 Python 3.10 이상에서만 사용할 수 있는 구조적 패턴 매칭을 사용합니다.
3.2 저자
- Geir Arne Hjelle, 이메일: geirarne@realpython.com
3.3 라이선스
MIT 라이선스에 따라 배포됩니다. 자세한 내용은 LICENSE를 참조하세요.
3.4 파일: aoc_grid.py
import numpy as np
from colorama import Cursor
grid = np.array(
[
[1, 1, 1, 1, 1],
[1, 0, 0, 0, 1],
[1, 1, 1, 0, 1],
[1, 0, 0, 2, 1],
[1, 1, 1, 1, 1],
]
)
num_rows, num_cols = grid.shape
for row in range(num_rows):
for col in range(num_cols):
symbol = " *o"[grid[row, col]]
print(f"{Cursor.POS(col + 1, row + 2)}{symbol}")3.5 파일: aoc_state_machine.py
from dataclasses import dataclass
@dataclass
class StateMachine:
memory: dict[str, int]
program: list[str]
def run(self):
"""Run the program"""
current_line = 0
while current_line < len(self.program):
instruction = self.program[current_line]
# Set a register to a value
if instruction.startswith("set "):
register, value = instruction[4], int(instruction[6:])
self.memory[register] = value
# Increase the value in a register by 1
elif instruction.startswith("inc "):
register = instruction[4]
self.memory[register] += 1
# Move the line pointer
current_line += 13.6 파일: solutions/2019/01_the_tyranny_of_the_rocket_equation/aoc201901.py
AoC 1, 2019: 로켓 방정식의 폭정
"""AoC 1, 2019: The Tyranny of the Rocket Equation"""
# Standard library imports
import pathlib
import sys
def parse(puzzle_input):
"""Parse input"""
return [int(line) for line in puzzle_input.split("\n")]
def part1(module_masses):
"""Solve part 1"""
return sum(mass // 3 - 2 for mass in module_masses)
def part2(module_masses):
"""Solve part 2"""
return sum(all_fuel(mass) for mass in module_masses)
def all_fuel(mass):
"""Calculate fuel while taking mass of the fuel into account.
## Example:
>>> all_fuel(1969)
966
"""
fuel = mass // 3 - 2
if fuel <= 0:
return 0
else:
return fuel + all_fuel(mass=fuel)
def solve(puzzle_input):
"""Solve the puzzle for the given input"""
data = parse(puzzle_input)
solution1 = part1(data)
solution2 = part2(data)
return solution1, solution2
if __name__ == "__main__":
for path in sys.argv[1:]:
print(f"\n{path}:")
solutions = solve(puzzle_input=pathlib.Path(path).read_text().strip())
print("\n".join(str(solution) for solution in solutions))3.7 파일: solutions/2019/01_the_tyranny_of_the_rocket_equation/test_aoc201901.py
AoC 1, 2019 테스트: 로켓 방정식의 폭정
"""Tests for AoC 1, 2019: The Tyranny of the Rocket Equation"""
# Standard library imports
import pathlib
# Third party imports
import aoc201901 as aoc
import pytest
PUZZLE_DIR = pathlib.Path(__file__).parent
@pytest.fixture
def example1():
puzzle_input = (PUZZLE_DIR / "example1.txt").read_text().strip()
return aoc.parse(puzzle_input)
def test_parse_example1(example1):
"""Test that input is parsed properly"""
assert example1 == [12, 14, 1969, 100756]
def test_part1_example1(example1):
"""Test part 1 on example input"""
assert aoc.part1(example1) == 2 + 2 + 654 + 33583
def test_part2_example1(example1):
"""Test part 2 on example input"""
assert aoc.part2(example1) == 2 + 2 + 966 + 503463.8 파일: solutions/2020/01_report_repair/aoc202001.py
AoC 1, 2020: 보고서 수정
"""AoC 1, 2020: Report Repair"""
# Standard library imports
import pathlib
import sys
def parse(puzzle_input):
"""Parse input"""
return [int(line) for line in puzzle_input.split()]
def part1(numbers):
"""Solve part 1"""
for num1 in numbers:
for num2 in numbers:
if num1 < num2 and num1 + num2 == 2020:
return num1 * num2
def part2(numbers):
"""Solve part 2"""
def solve(puzzle_input):
"""Solve the puzzle for the given input"""
data = parse(puzzle_input)
solution1 = part1(data)
solution2 = part2(data)
return solution1, solution2
if __name__ == "__main__":
for path in sys.argv[1:]:
print(f"\n{path}:")
solutions = solve(puzzle_input=pathlib.Path(path).read_text().strip())
print("\n".join(str(solution) for solution in solutions))3.9 파일: solutions/2020/01_report_repair/test_aoc202001.py
AoC 1, 2020 테스트: 보고서 수정
"""Tests for AoC 1, 2020: Report Repair"""
# Standard library imports
import pathlib
# Third party imports
import aoc202001 as aoc
import pytest
PUZZLE_DIR = pathlib.Path(__file__).parent
@pytest.fixture
def example1():
puzzle_input = (PUZZLE_DIR / "example1.txt").read_text().strip()
return aoc.parse(puzzle_input)
def test_parse_example1(example1):
"""Test that input is parsed properly"""
assert example1 == [1721, 979, 366, 299, 675, 1456]
def test_part1_example1(example1):
"""Test part 1 on example input"""
assert aoc.part1(example1) == 514579
@pytest.mark.skip(reason="Not implemented")
def test_part2_example1(example1):
"""Test part 2 on example input"""
assert aoc.part2(example1) == ...3.10 파일: solutions/2020/05_binary_boarding/aoc202005.py
AoC 5, 2020: 바이너리 탑승
"""AoC 5, 2020: Binary Boarding"""
# Standard library imports
import pathlib
import sys
BP2BINARY = str.maketrans({"F": "0", "B": "1", "L": "0", "R": "1"})
def parse(puzzle_input):
"""Parse input"""
return [
int(bp.translate(BP2BINARY), base=2) for bp in puzzle_input.split("\n")
]
def part1(seat_ids):
"""Solve part 1"""
return max(seat_ids)
def part2(seat_ids):
"""Solve part 2"""
all_ids = set(range(min(seat_ids), max(seat_ids) + 1))
return (all_ids - set(seat_ids)).pop()
def solve(puzzle_input):
"""Solve the puzzle for the given input"""
data = parse(puzzle_input)
solution1 = part1(data)
solution2 = part2(data)
return solution1, solution2
if __name__ == "__main__":
for path in sys.argv[1:]:
print(f"\n{path}:")
solutions = solve(puzzle_input=pathlib.Path(path).read_text().strip())
print("\n".join(str(solution) for solution in solutions))3.11 파일: solutions/2020/05_binary_boarding/test_aoc202005.py
AoC 5, 2020 테스트: 바이너리 탑승
"""Tests for AoC 5, 2020: Binary Boarding"""
# Standard library imports
import pathlib
# Third party imports
import aoc202005 as aoc
import pytest
PUZZLE_DIR = pathlib.Path(__file__).parent
@pytest.fixture
def example1():
puzzle_input = (PUZZLE_DIR / "example1.txt").read_text().strip()
return aoc.parse(puzzle_input)
def test_parse_example1(example1):
"""Test that input is parsed properly"""
assert example1 == [357, 567, 119, 820]
def test_part1_example1(example1):
"""Test part 1 on example input"""
assert aoc.part1(example1) == 820
def test_part2():
"""Test part 2 on example input"""
seat_ids = [3, 9, 4, 8, 5, 10, 7, 11]
assert aoc.part2(seat_ids) == 63.12 파일: solutions/2021/05_hydrothermal_venture/aoc202105.py
AoC 5, 2021: 열수 벤처
"""AoC 5, 2021: Hydrothermal Venture"""
# Standard library imports
import collections
import pathlib
import sys
def parse(puzzle_input):
"""Parse input"""
lines = []
for line in puzzle_input.split("\n"):
point1, point2 = line.split(" -> ")
x1, y1 = point1.split(",")
x2, y2 = point2.split(",")
lines.append((int(x1), int(y1), int(x2), int(y2)))
return lines
def part1(lines):
"""Solve part 1"""
vertical = [(x1, y1, x2, y2) for x1, y1, x2, y2 in lines if x1 == x2]
horizontal = [(x1, y1, x2, y2) for x1, y1, x2, y2 in lines if y1 == y2]
return count_overlaps(vertical + horizontal)
def part2(lines):
"""Solve part 2"""
return count_overlaps(lines)
def count_overlaps(lines):
"""Count overlapping points between a list of lines
## Example:
>>> count_overlaps(
... [(3, 3, 3, 5), (3, 3, 6, 3), (6, 6, 6, 3), (4, 5, 6, 5)]
... )
3
"""
overlaps = collections.Counter(
point for line in lines for point in points(line)
)
return sum(num_points >= 2 for num_points in overlaps.values())
def points(line):
"""List all points making up a line
## Examples:
>>> points((0, 3, 3, 3)) # Horizontal line
[(0, 3), (1, 3), (2, 3), (3, 3)]
>>> points((3, 3, 3, 0)) # Vertical line
[(3, 3), (3, 2), (3, 1), (3, 0)]
>>> points((1, 2, 3, 4)) # Diagonal line
[(1, 2), (2, 3), (3, 4)]
"""
match line:
case (x1, y1, x2, y2) if x1 == x2:
return [(x1, y) for y in coords(y1, y2)]
case (x1, y1, x2, y2) if y1 == y2:
return [(x, y1) for x in coords(x1, x2)]
case (x1, y1, x2, y2):
return [
(x, y)
for x, y in zip(coords(x1, x2), coords(y1, y2), strict=False)
]
def coords(start, stop):
"""List coordinates between start and stop, inclusive.
## Examples:
>>> list(coords(0, 3))
[0, 1, 2, 3]
>>> list(coords(2, -2))
[2, 1, 0, -1, -2]
"""
step = 1 if start <= stop else -1
return range(start, stop + step, step)
def solve(puzzle_input):
"""Solve the puzzle for the given input"""
data = parse(puzzle_input)
solution1 = part1(data)
solution2 = part2(data)
return solution1, solution2
if __name__ == "__main__":
for path in sys.argv[1:]:
print(f"\n{path}:")
solutions = solve(puzzle_input=pathlib.Path(path).read_text().strip())
print("\n".join(str(solution) for solution in solutions))3.13 파일: solutions/2021/05_hydrothermal_venture/test_aoc202105.py
AoC 5, 2021 테스트: 열수 벤처
"""Tests for AoC 5, 2021: Hydrothermal Venture"""
# Standard library imports
import pathlib
# Third party imports
import aoc202105 as aoc
import pytest
PUZZLE_DIR = pathlib.Path(__file__).parent
@pytest.fixture
def example1():
puzzle_input = (PUZZLE_DIR / "example1.txt").read_text().strip()
return aoc.parse(puzzle_input)
@pytest.fixture
def example2():
puzzle_input = (PUZZLE_DIR / "example2.txt").read_text().strip()
return aoc.parse(puzzle_input)
def test_parse_example1(example1):
"""Test that input is parsed properly"""
assert example1 == [(2, 0, 0, 2), (0, 2, 2, 2), (0, 0, 0, 2), (0, 0, 2, 2)]
def test_part1_example1(example1):
"""Test part 1 on example input"""
assert aoc.part1(example1) == 1
def test_part1_example2(example2):
"""Test part 1 on example input"""
assert aoc.part1(example2) == 5
def test_part2_example1(example1):
"""Test part 2 on example input"""
assert aoc.part2(example1) == 4
def test_part2_example2(example2):
"""Test part 2 on example input"""
assert aoc.part2(example2) == 123.14 파일: templates/aoc_template.py
import pathlib
import sys
def parse(puzzle_input):
"""Parse input"""
def part1(data):
"""Solve part 1"""
def part2(data):
"""Solve part 2"""
def solve(puzzle_input):
"""Solve the puzzle for the given input"""
data = parse(puzzle_input)
solution1 = part1(data)
solution2 = part2(data)
return solution1, solution2
if __name__ == "__main__":
for path in sys.argv[1:]:
print(f"{path}:")
puzzle_input = pathlib.Path(path).read_text().strip()
solutions = solve(puzzle_input)
print("\n".join(str(solution) for solution in solutions))3.15 파일: templates/test_aoc_template.py
import pathlib
import aoc_template as aoc
import pytest
PUZZLE_DIR = pathlib.Path(__file__).parent
@pytest.fixture
def example1():
puzzle_input = (PUZZLE_DIR / "example1.txt").read_text().strip()
return aoc.parse(puzzle_input)
@pytest.fixture
def example2():
puzzle_input = (PUZZLE_DIR / "example2.txt").read_text().strip()
return aoc.parse(puzzle_input)
@pytest.mark.skip(reason="Not implemented")
def test_parse_example1(example1):
"""Test that input is parsed properly"""
assert example1 == ...
@pytest.mark.skip(reason="Not implemented")
def test_part1_example1(example1):
"""Test part 1 on example input"""
assert aoc.part1(example1) == ...
@pytest.mark.skip(reason="Not implemented")
def test_part2_example1(example1):
"""Test part 2 on example input"""
assert aoc.part2(example1) == ...
@pytest.mark.skip(reason="Not implemented")
def test_part2_example2(example2):
"""Test part 2 on example input"""
assert aoc.part2(example2) == ...