Merge pull request #2 from bertptrs/rewrite-2019-python

This commit is contained in:
2021-10-18 22:34:35 +02:00
committed by GitHub
164 changed files with 2009 additions and 3691 deletions

18
.github/workflows/2019.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: AOC 2019 test suite
on:
push:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r 2019/requirements.txt
- name: Run Pytest
run: pytest 2019

View File

@@ -1,19 +0,0 @@
dist: bionic
language: rust
rust:
- stable
- beta
- nightly
jobs:
allow_failures:
- rust: nightly
fast_finish: true
cache:
- cargo
- 2020/target
# Custom directory, for the correct year
before_script:
- cd 2020

142
2019/.gitignore vendored
View File

@@ -1 +1,141 @@
cmake-build-* # Virtual environment
venv
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/

View File

@@ -1,28 +0,0 @@
cmake_minimum_required(VERSION 3.12)
project(aoc2019)
find_package(GTest REQUIRED)
option(ANIMATE_DAY13 "Animate the Arkanoid game in day 13." Off)
file(GLOB DAYS_FILES src/day*.cpp)
add_library(AoCSolutions src/implementations.cpp "${DAYS_FILES}" src/point.hpp src/utils.cpp src/utils.hpp)
target_compile_features(AoCSolutions PUBLIC cxx_std_17)
if (ANIMATE_DAY13)
target_compile_definitions(AoCSolutions ANIMATE_DAY13)
endif ()
add_executable(runner src/runner.cpp)
target_compile_features(runner PUBLIC cxx_std_17)
target_link_libraries(runner AoCSolutions)
add_executable(unit_tests tests/test_solutions.cpp tests/test_intcode.cpp)
target_compile_features(unit_tests PUBLIC cxx_std_17)
target_link_libraries(unit_tests AoCSolutions GTest::GTest GTest::Main)
target_compile_definitions(unit_tests PRIVATE "TEST_SAMPLES_DIR=\"${CMAKE_SOURCE_DIR}/tests/samples\"")
target_include_directories(unit_tests PRIVATE "${CMAKE_SOURCE_DIR}/src")
enable_testing()
gtest_discover_tests(unit_tests NO_PRETTY_VALUES)

View File

@@ -1,31 +1,8 @@
# Advent of Code 2019 # Advent of Code 2019
This project contains my implementations for Advent of Code 2019. The This is a quick-and-dirty implementation of all 2019 problems implemented in
goal is to create reasonably fast C++ implementations in readable and Python because I got fed up with C++ and really couldn't stand the missing
ergonomic C++. At the end of the contest, I will probably do a write- stars on the [events page](https://adventofcode.com/2020/events).
up of some sorts.
I'll try to incorporate unit tests and all because it's just nice programming,
## How to compile but this edition has decidedly less polish than my other attempts.
Install the dependencies:
- [GTest](https://github.com/google/googletest) **Note:** this project
by default tries to dynamically link GTest, and the Ubuntu packages
only provide a statically linked archive. You may need to compile it
for yourself.
```
mkdir build && cd build
cmake ..
make
```
You can then use the generated executable `runner`.
## Running tests
Tests can be executed with `make test`. The `tests` folder contains a
`samples` folder. This folder contains pairs of `XX-Y-something.in` and
`XX-Y-something.out`, which will be taken as the expected input and
output of the implementations. You can add your own samples to this mix.

0
2019/aoc2019/__init__.py Normal file
View File

28
2019/aoc2019/__main__.py Normal file
View File

@@ -0,0 +1,28 @@
import argparse
import importlib
import sys
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument('day', type=int)
parser.add_argument('input', type=argparse.FileType('rt'), nargs='?', default=sys.stdin)
parser.add_argument('-2', '--part2', action='store_true')
args = parser.parse_args()
try:
day = importlib.import_module(f'.day{args.day:02d}', __package__)
if args.part2:
function = day.part2 # type: ignore
else:
function = day.part1 # type: ignore
print(function(args.input))
except ImportError:
sys.exit(f'Invalid day: {args.day}')
main()

23
2019/aoc2019/day01.py Normal file
View File

@@ -0,0 +1,23 @@
from typing import TextIO
def fuel_required(weight: int) -> int:
return max(0, weight // 3 - 2)
def recursive_fuel_required(weight: int) -> int:
total = 0
while weight > 0:
weight = fuel_required(weight)
total += weight
return total
def part1(data: TextIO) -> int:
return sum(fuel_required(int(line)) for line in data)
def part2(data: TextIO) -> int:
return sum(recursive_fuel_required(int(line)) for line in data)

33
2019/aoc2019/day02.py Normal file
View File

@@ -0,0 +1,33 @@
from typing import TextIO
from aoc2019.intcode import read_program, Computer
def part1(data: TextIO) -> int:
program = read_program(data)
program[1] = 12
program[2] = 2
computer = Computer(program)
computer.run()
return computer[0]
def part2(data: TextIO) -> int:
program = read_program(data)
for verb in range(100):
for noun in range(100):
computer = Computer(program.copy())
computer[1] = noun
computer[2] = verb
computer.run()
if computer[0] == 19690720:
return 100 * noun + verb
raise ValueError('Did not find valid combination')

44
2019/aoc2019/day03.py Normal file
View File

@@ -0,0 +1,44 @@
import itertools
from typing import Dict, TextIO
def compute_points(line: str) -> Dict[complex, int]:
points: Dict[complex, int] = {}
steps = itertools.count(1)
pos = complex(0)
directions = {
'U': 1j,
'R': 1,
'D': -1j,
'L': -1,
}
for move in line.strip().split(','):
direction = directions[move[0]]
for _ in range(int(move[1:])):
pos += direction
points.setdefault(pos, next(steps))
return points
def part1(data: TextIO) -> int:
points_a = compute_points(next(data))
points_b = compute_points(next(data))
in_common = set(points_a.keys()) & set(points_b.keys())
return int(min(abs(c.imag) + abs(c.real) for c in in_common))
def part2(data: TextIO) -> int:
points_a = compute_points(next(data))
points_b = compute_points(next(data))
in_common = set(points_a.keys()) & set(points_b.keys())
return min(points_a[pos] + points_b[pos] for pos in in_common)

38
2019/aoc2019/day04.py Normal file
View File

@@ -0,0 +1,38 @@
import itertools
from typing import TextIO
def read_range(data: TextIO) -> range:
a, b = next(data).split('-')
return range(int(a), int(b) + 1) # plus one because inclusive
def valid(number: int, strict: bool) -> bool:
s = str(number)
prev = '/' # is smaller than '0'
has_group = False
if len(s) != 6:
return False
for k, g in itertools.groupby(s):
if k < prev:
return False
prev = k
amount = sum(1 for _ in g)
if amount == 2 or not strict and amount > 2:
has_group = True
return has_group
def part1(data: TextIO) -> int:
return sum(1 for password in read_range(data) if valid(password, False))
def part2(data: TextIO) -> int:
return sum(1 for password in read_range(data) if valid(password, True))

29
2019/aoc2019/day05.py Normal file
View File

@@ -0,0 +1,29 @@
from typing import TextIO
from aoc2019.intcode import read_program, Computer
def part1(data: TextIO) -> int:
program = read_program(data)
computer = Computer(program)
# Enter the required starting code
computer.input.append(1)
computer.run()
return computer.output.pop()
def part2(data: TextIO) -> int:
program = read_program(data)
computer = Computer(program)
# Enter the required starting code
computer.input.append(5)
computer.run()
return computer.output.pop()

27
2019/aoc2019/day06.py Normal file
View File

@@ -0,0 +1,27 @@
from typing import TextIO
import networkx # type: ignore
def read_graph(data: TextIO) -> networkx.DiGraph:
graph = networkx.DiGraph()
for line in data:
a, b = line.strip().split(')')
graph.add_edge(a, b)
return graph
def part1(data: TextIO) -> int:
graph = read_graph(data)
paths = networkx.single_source_shortest_path_length(graph, 'COM')
return sum(paths.values())
def part2(data: TextIO) -> int:
graph = read_graph(data).to_undirected()
return networkx.shortest_path_length(graph, 'YOU', 'SAN') - 2

69
2019/aoc2019/day07.py Normal file
View File

@@ -0,0 +1,69 @@
import itertools
from typing import List, TextIO, Tuple
from aoc2019.intcode import read_program, Computer
def amplify(phases: Tuple[int, ...], program: List[int]) -> int:
amps: List[Computer] = []
for i, phase in enumerate(phases):
amp = Computer(program.copy())
if i > 0:
amp.input = amps[i - 1].output
amp.input.append(phase)
amps.append(amp)
amps[0].input.append(0)
for amp in amps:
amp.run()
return amps[-1].output.pop()
def reamplify(phases: Tuple[int, ...], program: List[int]) -> int:
amps: List[Computer] = []
for i, _ in enumerate(phases):
amp = Computer(program.copy())
if i > 0:
amp.input = amps[i - 1].output
amps.append(amp)
amps[0].input = amps[-1].output
for amp, phase in zip(amps, phases):
amp.input.append(phase)
amps[0].input.append(0)
changes = True
while changes:
changes = False
for amp in amps:
try:
amp.run()
except IndexError:
# Waiting for input
changes = True
return amps[-1].output.pop()
def part1(data: TextIO) -> int:
program = read_program(data)
return max(amplify(phase, program) for phase in itertools.permutations(range(0, 5)))
def part2(data: TextIO) -> int:
program = read_program(data)
return max(reamplify(phase, program) for phase in itertools.permutations(range(5, 10)))

34
2019/aoc2019/day08.py Normal file
View File

@@ -0,0 +1,34 @@
from collections import Counter
from typing import Iterable, TextIO
import numpy # type: ignore
def parse_layers(width: int, height: int, data: TextIO) -> Iterable[numpy.array]:
chunk_size = width * height
content = next(data).strip()
for pos in range(0, len(content), chunk_size):
yield numpy.array([int(c) for c in content[pos:pos + chunk_size]])
def part1(data: TextIO) -> int:
best_layer: Counter[int] = min((Counter(layer) for layer in parse_layers(25, 6, data)), key=lambda c: c[0])
return best_layer[1] * best_layer[2]
def format_row(row: Iterable[int]) -> str:
return ''.join('#' if p == 1 else ' ' for p in row)
def part2(data: TextIO) -> str:
background = numpy.zeros(25 * 6, numpy.int8)
background.fill(2)
for layer in parse_layers(25, 6, data):
mask = background == 2
background[mask] = layer[mask]
return '\n'.join(format_row(row) for row in background.reshape(6, 25))

25
2019/aoc2019/day09.py Normal file
View File

@@ -0,0 +1,25 @@
import sys
from typing import TextIO
from aoc2019.intcode import read_program, Computer
def run_machine(data: TextIO, initializer: int) -> int:
program = read_program(data)
computer = Computer(program)
computer.input.append(initializer)
computer.run()
if len(computer.output) > 1:
sys.exit(computer.output)
else:
return computer.output.pop()
def part1(data: TextIO) -> int:
return run_machine(data, 1)
def part2(data: TextIO) -> int:
return run_machine(data, 2)

93
2019/aoc2019/day10.py Normal file
View File

@@ -0,0 +1,93 @@
import math
from collections import defaultdict
from typing import TextIO, Tuple
import numpy # type: ignore
def read_asteroids(data: TextIO) -> Tuple[numpy.array, numpy.array]:
xs = []
ys = []
for y, line in enumerate(data):
for x, c in enumerate(line):
if c == '#':
xs.append(x)
ys.append(y)
return numpy.array(xs), -numpy.array(ys)
def asteroids_visible(x: int, y: int, xs: numpy.array, ys: numpy.array) -> int:
dx = xs - x
dy = ys - y
div = numpy.abs(numpy.gcd(dx, dy))
mask = div != 0
dx[mask] //= div[mask]
dy[mask] //= div[mask]
unique = set(zip(dx, dy))
return len(unique) - 1 # need to ignore the point itself
def part1(data: TextIO) -> int:
xs, ys = read_asteroids(data)
return max(asteroids_visible(x, y, xs, ys) for x, y in zip(xs, ys))
def part2(data: TextIO) -> int:
xs, ys = read_asteroids(data)
cx, cy = max(zip(xs, ys), key=lambda c: asteroids_visible(c[0], c[1], xs, ys))
dx = xs - cx
dy = ys - cy
angles = numpy.arctan2(dy, dx)
distances = numpy.abs(dx) + numpy.abs(dy)
to_shoot = defaultdict(list)
for angle, distance, dx, dy in zip(angles, distances, dx, dy):
if distance == 0:
# The point itself
continue
to_shoot[angle].append((distance, dx, dy))
for distances in to_shoot.values():
distances.sort(reverse=True)
unique_angles = sorted(set(angles), reverse=True)
shot = 0
# First shoot from up clockwise
for angle in unique_angles:
if angle > math.pi / 2:
continue
shot += 1
_, dx, dy = to_shoot[angle].pop()
# Repeatedly shoot until you reach #200
while True:
for angle in unique_angles:
if not to_shoot[angle]:
# Nothing left to shoot
continue
shot += 1
_, dx, dy = to_shoot[angle].pop()
if shot == 200:
x = cx + dx
y = -(cy + dy)
return 100 * x + y

60
2019/aoc2019/day11.py Normal file
View File

@@ -0,0 +1,60 @@
from typing import Dict, Optional, TextIO
from aoc2019.intcode import Computer, read_program
def run_robot(data: TextIO, painted: Optional[Dict[complex, int]] = None) -> Dict[complex, int]:
if painted is None:
painted = {}
computer = Computer(read_program(data))
pos = 0j
direction = 1j
finished = False
while not finished:
try:
computer.run()
finished = True
except IndexError:
pass
while len(computer.output) >= 2:
paint = computer.output.popleft()
turn = computer.output.popleft()
painted[pos] = paint
if turn:
direction *= -1j
else:
direction *= 1j
pos += direction
computer.input.append(painted.get(pos, 0))
return painted
def part1(data: TextIO) -> int:
return len(run_robot(data))
def part2(data: TextIO) -> str:
painted = run_robot(data, {0j: 1})
xmin = int(min(pos.real for pos in painted.keys()))
xmax = int(max(pos.real for pos in painted.keys()))
ymin = int(min(pos.imag for pos in painted.keys()))
ymax = int(max(pos.imag for pos in painted.keys()))
image = ''
for y in reversed(range(ymin, ymax + 1)):
line = ''.join('#' if painted.get(x + y * 1j) == 1 else ' ' for x in range(xmin, xmax + 1))
image += f'{line}\n'
return image[:-1]

69
2019/aoc2019/day12.py Normal file
View File

@@ -0,0 +1,69 @@
import itertools
import math
import re
from typing import TextIO
import numpy # type: ignore
def read_moons(data: TextIO) -> numpy.array:
moons = []
for line in data:
moon = [int(x) for x in re.split(r"[^-0-9]+", line) if x]
moons.append(moon)
return numpy.array(moons)
def advance(pos: numpy.array, vel: numpy.array) -> None:
""" update pos and vel in place """
pos_prime = numpy.repeat(numpy.reshape(pos, (1, len(pos))), len(pos), axis=0).transpose() - pos
pos_prime = -numpy.sign(pos_prime)
vel += numpy.sum(pos_prime, axis=1)
pos += vel
def simulate_moons(moons: numpy.array, iterations: int) -> int:
moons = numpy.transpose(moons) # Transpose so we have rows of x, y, z
velocity = numpy.zeros_like(moons)
for _ in range(iterations):
for pos, vel in zip(moons, velocity):
advance(pos, vel)
potential = numpy.sum(numpy.abs(moons), axis=0)
kinetic = numpy.sum(numpy.abs(velocity), axis=0)
return int(numpy.sum(kinetic * potential))
def find_repetition(moons: numpy.array) -> int:
moons = numpy.transpose(moons)
velocity = numpy.zeros_like(moons)
needed = 1
for pos, vel in zip(moons, velocity):
pos_prime = numpy.copy(pos)
for i in itertools.count(1):
advance(pos, vel)
if (pos == pos_prime).all() and (vel == 0).all():
needed *= i // math.gcd(needed, i)
break
return needed
def part1(data: TextIO) -> int:
moons = read_moons(data)
return simulate_moons(moons, 1000)
def part2(data: TextIO) -> int:
moons = read_moons(data)
return find_repetition(moons)

57
2019/aoc2019/day13.py Normal file
View File

@@ -0,0 +1,57 @@
import statistics
from typing import TextIO, Tuple, Dict
from aoc2019.intcode import Computer, read_program
def render_screen(computer: Computer, screen: Dict[Tuple[int, int], int]):
while computer.output:
x = computer.output.popleft()
y = computer.output.popleft()
val = computer.output.popleft()
screen[x, y] = val
def part1(data: TextIO) -> int:
computer = Computer(read_program(data))
computer.run()
screen: Dict[Tuple[int, int], int] = {}
render_screen(computer, screen)
return sum(1 for val in screen.values() if val == 2)
def part2(data: TextIO) -> int:
computer = Computer(read_program(data))
computer.program[0] = 2
screen: Dict[Tuple[int, int], int] = {}
finished = False
while not finished:
try:
computer.run()
finished = True
except IndexError:
# Waiting for input
pass
render_screen(computer, screen)
ball_x = next(x for x, y in screen if screen[x, y] == 4)
paddle_x = statistics.mean(x for x, y in screen if screen[x, y] == 3)
if ball_x < paddle_x:
computer.input.append(-1)
elif ball_x > paddle_x:
computer.input.append(1)
else:
computer.input.append(0)
return screen[-1, 0]

71
2019/aoc2019/day14.py Normal file
View File

@@ -0,0 +1,71 @@
import math
from collections import defaultdict
from typing import TextIO, Tuple
from networkx import DiGraph, topological_sort # type: ignore[import]
def read_pair(item: str) -> Tuple[str, int]:
amount, element = item.split(' ')
return element, int(amount)
def read_recipes(data: TextIO) -> DiGraph:
graph = DiGraph()
for line in data:
requisites, production = line.strip().split(' => ')
produced, produced_amount = read_pair(production)
graph.add_node(produced, weight=produced_amount)
for requisite in requisites.split(', '):
required, required_amount = read_pair(requisite)
graph.add_edge(produced, required, weight=required_amount)
return graph
def ore_required(graph: DiGraph, fuel_required: int) -> int:
requirements = defaultdict(int)
requirements['FUEL'] = fuel_required
for element in topological_sort(graph):
if element not in requirements:
continue
if element == 'ORE':
break
element_produced = graph.nodes[element]['weight']
productions_required = math.ceil(requirements[element] / element_produced)
for _, elem_required, amount_required in graph.edges(element, data='weight'):
requirements[elem_required] += amount_required * productions_required
return requirements['ORE']
def part1(data: TextIO) -> int:
return ore_required(read_recipes(data), 1)
def part2(data: TextIO) -> int:
ore_available = 1000000000000
graph = read_recipes(data)
min_possible = 1 # lower bound of ORE / ore_required(graph, 1) exists but is slower
max_possible = ore_available
while min_possible != max_possible:
check = min_possible + (max_possible - min_possible + 1) // 2
required = ore_required(graph, check)
if required <= ore_available:
min_possible = check
else:
max_possible = check - 1
return min_possible

102
2019/aoc2019/day15.py Normal file
View File

@@ -0,0 +1,102 @@
import sys
from collections import defaultdict
from typing import TextIO
import networkx # type: ignore
from aoc2019.intcode import Computer, read_program
def step(computer: Computer, direction: int) -> int:
computer.input.append(direction)
try:
computer.run()
except IndexError:
return computer.get_output()
sys.exit("computer terminated unexpectedly")
def inverse(direction: int):
if direction % 2 == 1:
return direction + 1
else:
return direction - 1
def read_graph(data: TextIO) -> networkx.Graph:
computer = Computer(read_program(data))
pos = (0, 0)
tiles = defaultdict(int)
tiles[0, 0] = 1
prev = [((0, 0), 1)]
while prev:
x, y = pos
if (x, y + 1) not in tiles:
movement = 1
next_pos = (x, y + 1)
elif (x, y - 1) not in tiles:
movement = 2
next_pos = (x, y - 1)
elif (x - 1, y) not in tiles:
movement = 3
next_pos = (x - 1, y)
elif (x + 1, y) not in tiles:
movement = 4
next_pos = (x + 1, y)
else:
# No movement available, backtrack
prev_pos, prev_dir = prev.pop()
step(computer, inverse(prev_dir))
pos = prev_pos
continue
result = step(computer, movement)
tiles[next_pos] = result
if result != 0:
# Movement was successful
prev.append((pos, movement))
pos = next_pos
graph = networkx.Graph()
for pos, value in tiles.items():
if value == 0:
continue
if value == 2:
# Create an imaginary edge to the oxygen
graph.add_edge('O', pos, weight=0)
x, y = pos
neighbours = [
(x - 1, y),
(x + 1, y),
(x, y - 1),
(x, y + 1),
]
for neighbour in neighbours:
if tiles[neighbour] != 0:
graph.add_edge(pos, neighbour)
return graph
def part1(data: TextIO) -> int:
graph = read_graph(data)
return networkx.shortest_path_length(graph, (0, 0), 'O') - 1
def part2(data: TextIO) -> int:
graph = read_graph(data)
return networkx.eccentricity(graph, 'O') - 1

57
2019/aoc2019/day16.py Normal file
View File

@@ -0,0 +1,57 @@
import math
from typing import List, TextIO
import numpy # type: ignore
def read_input(data: TextIO) -> List[int]:
line = next(data).strip()
return [int(c) for c in line]
def simulate(numbers: List[int]) -> str:
numbers = numpy.array(numbers)
pattern = numpy.array([0, 1, 0, -1], dtype=numpy.int32)
matrix = numpy.zeros((len(numbers), len(numbers)), dtype=numpy.int32)
for i in range(len(numbers)):
base = numpy.repeat(pattern, i + 1)
needed_repetitions = math.ceil((len(numbers) + 1) / len(base))
matrix[i, :] = numpy.tile(base, needed_repetitions)[1:len(numbers) + 1]
for _ in range(100):
numbers = numpy.abs(numpy.dot(matrix, numbers)) % 10
return ''.join(str(s) for s in numbers[:8])
def simulate2(numbers: List[int]) -> str:
numbers = numpy.tile(numpy.array(numbers, dtype=numpy.int32), 10000)
starting_index = 0
for n in numbers[:7]:
starting_index *= 10
starting_index += n
assert starting_index > len(numbers) / 2
numbers = numbers[starting_index:]
for _ in range(100):
numbers = numpy.abs(numpy.flip(numpy.cumsum(numpy.flip(numbers)))) % 10
return ''.join(str(s) for s in numbers[:8])
def part1(data: TextIO) -> str:
numbers = read_input(data)
return simulate(numbers)
def part2(data: TextIO) -> str:
numbers = read_input(data)
return simulate2(numbers)

26
2019/aoc2019/day17.py Normal file
View File

@@ -0,0 +1,26 @@
from typing import TextIO
from aoc2019.intcode import Computer, read_program
def part1(data: TextIO) -> int:
computer = Computer(read_program(data))
computer.run()
output = ''.join(chr(c) for c in computer.output)
tiles = set()
for y, line in enumerate(output.splitlines()):
for x, c in enumerate(line):
if c == '#':
tiles.add((x, y))
total = 0
for x, y in tiles:
if (x - 1, y) in tiles and (x + 1, y) in tiles and (x, y - 1) in tiles and (x, y + 1) in tiles:
total += x * y
return total

44
2019/aoc2019/day21.py Normal file
View File

@@ -0,0 +1,44 @@
from typing import TextIO
from aoc2019.intcode import read_program, Computer
def send_input(computer: Computer, program: str) -> None:
for c in program:
computer.send_input(ord(c))
def run(data: TextIO, program: str) -> int:
computer = Computer(read_program(data))
send_input(computer, program)
computer.run()
return computer.output.pop()
def part1(data: TextIO) -> int:
program = """\
OR A J
AND B J
AND C J
NOT J J
AND D J
WALK
"""
return run(data, program)
def part2(data: TextIO) -> int:
program = """\
NOT H J
OR C J
AND A J
AND B J
NOT J J
AND D J
RUN
"""
return run(data, program)

90
2019/aoc2019/day22.py Normal file
View File

@@ -0,0 +1,90 @@
from typing import List, TextIO
def shuffle(instructions: List[str], deck_size: int) -> List[int]:
deck = list(range(0, deck_size))
for instruction in instructions:
if "new stack" in instruction:
deck = list(reversed(deck))
continue
parts = instruction.split(" ")
if parts[0] == "cut":
by = int(parts[1])
new_deck = deck[by:]
new_deck += deck[:by]
deck = new_deck
else:
increment = int(parts[3])
new_deck = list(range(0, deck_size))
target_index = 0
for card in deck:
new_deck[target_index] = card
target_index = (target_index + increment) % len(deck)
deck = new_deck
return deck
def part1(data: TextIO) -> int:
instructions = [line.strip() for line in data]
result = shuffle(instructions, 10007)
for i, card in enumerate(result):
if card == 2019:
return i
raise Exception("Did not find card")
def modpow(a: int, b: int, m: int) -> int:
assert b >= 0
result = 1
n = a
while b > 0:
if b % 2:
result = (result * n) % m
b //= 2
n = (n * n) % m
return result
def inverse(a: int, m: int) -> int:
""" Computes the modulo multiplicative inverse """
return modpow(a, m - 2, m)
def part2(data: TextIO) -> int:
deck_size = 119315717514047
shuffles = 101741582076661
a, b = 1, 0
for line in data:
parts = line.split(' ')
if 'new stack' in line:
la, lb = -1, -1
elif parts[0] == 'deal':
la, lb = int(parts[-1]), 0
else:
la, lb = 1, -int(parts[-1])
a = (la * a) % deck_size
b = (la * b + lb) % deck_size
final_a = modpow(a, shuffles, deck_size)
final_b = ((b * (final_a - 1)) * inverse(a - 1, deck_size)) % deck_size
return ((2020 - final_b) * inverse(final_a, deck_size)) % deck_size

78
2019/aoc2019/day23.py Normal file
View File

@@ -0,0 +1,78 @@
from typing import TextIO
from aoc2019.intcode import read_program, Computer
def part1(data: TextIO) -> int:
program = read_program(data)
computers = [Computer(program.copy()) for _ in range(50)]
for i, computer in enumerate(computers):
computer.send_input(i)
while True:
for computer in computers:
try:
computer.run()
except IndexError:
computer.send_input(-1)
while len(computer.output) >= 3:
dest = computer.get_output()
x = computer.get_output()
y = computer.get_output()
if dest == 255:
return y
computers[dest].send_input(x)
computers[dest].send_input(y)
def part2(data: TextIO) -> int:
program = read_program(data)
computers = [Computer(program.copy()) for _ in range(50)]
for i, computer in enumerate(computers):
computer.send_input(i)
nat_value = None
last_sent = None
while True:
is_idle = True
for computer in computers:
try:
computer.execute_current()
is_idle = False
except IndexError:
computer.send_input(-1)
try:
computer.run()
except IndexError:
pass
while len(computer.output) >= 3:
dest = computer.get_output()
x = computer.get_output()
y = computer.get_output()
if dest == 255:
nat_value = (x, y)
else:
computers[dest].send_input(x)
computers[dest].send_input(y)
if is_idle:
x, y = nat_value
if last_sent == nat_value:
return y
else:
computers[0].send_input(x)
computers[0].send_input(y)
last_sent = nat_value

123
2019/aoc2019/day25.py Normal file
View File

@@ -0,0 +1,123 @@
import itertools
from typing import TextIO, Iterable, Tuple, Set
from aoc2019.intcode import read_program, Computer
def powerset(iterable: Iterable) -> Iterable[Tuple]:
s = list(iterable)
return itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(len(s) + 1))
def print_output(computer: Computer) -> str:
output = ""
while len(computer.output):
output += chr(computer.get_output())
print(output, end='')
return output
def load_save(save, computer: Computer):
computer.pointer, computer.relative_base, computer.program = save
def create_save(computer: Computer):
return computer.pointer, computer.relative_base, computer.program.copy()
def send_command(computer: Computer, command: str):
for c in command:
computer.send_input(ord(c))
computer.send_input(10) # manually send newline
def force(computer: Computer, direction: str, items: Set[str]):
# First, drop everything
for item in items:
send_command(computer, f"drop {item}")
holding = tuple()
for combination in powerset(items):
# drop everything we don't want to keep holding
for item in holding:
if item not in combination:
send_command(computer, f"drop {item}")
# pick up what we want to pick up
for item in combination:
if item not in holding:
send_command(computer, f"take {item}")
send_command(computer, direction)
try:
computer.run()
print_output(computer)
return True
except IndexError:
print_output(computer)
holding = combination
return False
def part1(data: TextIO):
print("This day must use a file as input as it requires the stdin for other things.")
computer = Computer(read_program(data))
items = set()
saves = {}
while True:
try:
computer.run()
print_output(computer)
print("detected a death, loading auto save...")
load_save(saves['auto'], computer)
print("Command?")
except IndexError:
pass
print_output(computer)
saves['auto'] = create_save(computer)
try:
command = input().strip()
except EOFError:
return "exiting"
if command == "exit":
return "exiting"
elif command == "items":
print(items)
continue
elif command.startswith("save "):
save_name = command.removeprefix("save ")
saves[save_name] = create_save(computer)
print(f"Saved game state as {save_name}")
continue
elif command.startswith("load "):
save_name = command.removeprefix("load ")
load_save(saves[save_name], computer)
print(f"Loaded game state from {save_name}")
continue
elif command.startswith("take "):
items.add(command.removeprefix("take "))
elif command.startswith("drop "):
items.remove(command.removeprefix("drop "))
elif command.startswith("force "):
direction = command.removeprefix("force ")
if force(computer, direction, items):
return "success"
continue
send_command(computer, command)

150
2019/aoc2019/intcode.py Normal file
View File

@@ -0,0 +1,150 @@
import collections
from typing import List, TextIO, Tuple, Union
def read_program(data: TextIO) -> List[int]:
line = next(data)
return [int(i) for i in line.split(',')]
class Computer:
program: List[int]
pointer: int
relative_base: int
input: collections.deque[int]
output: collections.deque[int]
def __init__(self, program: List[int], pointer: int = 0) -> None:
self.program = program
self.pointer = pointer
self.relative_base = 0
self.input = collections.deque()
self.output = collections.deque()
def _mode_and_key(self, item: Union[int, Tuple[int, int]]) -> Tuple[int, int]:
if isinstance(item, int):
return 0, item
else:
mode, key = item
return mode, self.program[self.pointer + key]
def __getitem__(self, item: Union[int, Tuple[int, int]]) -> int:
mode, key = self._mode_and_key(item)
if mode == 1:
return key
elif mode == 0:
pass # Nothing to do here, handled below
elif mode == 2:
key += self.relative_base
else:
raise ValueError(f'Unsupported mode "{mode}"')
self._ensure_length(key + 1)
return self.program[key]
def __setitem__(self, item: Union[int, Tuple[int, int]], value: int) -> None:
mode, key = self._mode_and_key(item)
if mode == 1:
raise ValueError('Cannot assign to an immediate')
elif mode == 0:
pass # Nothing to do here, handled below
elif mode == 2:
key += self.relative_base
else:
raise ValueError(f'Unsupported mode "{mode}"')
self._ensure_length(key + 1)
self.program[key] = value
def _ensure_length(self, length: int) -> None:
if len(self.program) < length:
if 2 * len(self.program) >= length:
# Double current program size with 0s
self.program.extend(0 for _ in range(len(self.program)))
else:
# Resize until the desired length
self.program.extend(0 for _ in range(length - len(self.program)))
def run(self) -> None:
""" Run until failure """
while self.execute_current():
pass
def get_output(self) -> int:
return self.output.popleft()
def send_input(self, data: int):
self.input.append(data)
def execute_current(self) -> bool:
"""
Execute a single instruction
:return: True if the program should continue
"""
pointer = self.pointer
instruction = self[pointer]
opcode = instruction % 100
mode = [
(instruction // 100) % 10,
(instruction // 1000) % 10,
(instruction // 10000) % 10,
]
if opcode == 1:
# Add
self[mode[2], 3] = self[mode[0], 1] + self[mode[1], 2]
self.pointer += 4
elif opcode == 2:
# Multiply
self[mode[2], 3] = self[mode[0], 1] * self[mode[1], 2]
self.pointer += 4
elif opcode == 3:
# Input
self[mode[0], 1] = self.input.popleft()
self.pointer += 2
elif opcode == 4:
# Output
self.output.append(self[mode[0], 1])
self.pointer += 2
elif opcode == 5:
# Jump if true
if self[mode[0], 1] != 0:
self.pointer = self[mode[1], 2]
else:
self.pointer += 3
elif opcode == 6:
# Jump if false
if self[mode[0], 1] == 0:
self.pointer = self[mode[1], 2]
else:
self.pointer += 3
elif opcode == 7:
# Less than
if self[mode[0], 1] < self[mode[1], 2]:
self[mode[2], 3] = 1
else:
self[mode[2], 3] = 0
self.pointer += 4
elif opcode == 8:
# Equals
if self[mode[0], 1] == self[mode[1], 2]:
self[mode[2], 3] = 1
else:
self[mode[2], 3] = 0
self.pointer += 4
elif opcode == 9:
# Adjust relative base
self.relative_base += self[mode[0], 1]
self.pointer += 2
elif opcode == 99:
# Halt
return False
else:
raise ValueError(f'Unknown opcode {opcode} at {pointer}')
return True

1
2019/inputs/23.txt Normal file

File diff suppressed because one or more lines are too long

1
2019/inputs/25.txt Normal file

File diff suppressed because one or more lines are too long

3
2019/requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
pytest
networkx
numpy

View File

@@ -1,26 +0,0 @@
#include <iostream>
#include "days.hpp"
static inline int required(int weight) {
return weight / 3 - 2;
}
void aoc2019::day01_part1(std::istream &input, std::ostream &output) {
int total = 0;
for (int current; input >> current;) {
total += required(current);
}
output << total << std::endl;
}
void aoc2019::day01_part2(std::istream &input, std::ostream &output) {
int total = 0;
for (int current; input >> current;) {
for (int fuel = required(current); fuel > 0; fuel = required(fuel)) {
total += fuel;
}
}
output << total << std::endl;
}

View File

@@ -1,36 +0,0 @@
#include <exception>
#include <iostream>
#include <array>
#include <vector>
#include "days.hpp"
#include "utils.hpp"
static int run_program(std::vector<std::int64_t> program) {
aoc2019::IntCodeComputer computer(std::move(program));
computer.run();
return computer[0];
}
void aoc2019::day02_part1(std::istream &input, std::ostream &output) {
auto program = IntCodeComputer::read_intcode(input);
program[1] = 12;
program[2] = 2;
output << run_program(std::move(program)) << std::endl;
}
void aoc2019::day02_part2(std::istream &input, std::ostream &output) {
auto program = IntCodeComputer::read_intcode(input);
for (int noun = 0; noun < 100; ++noun) {
for (int verb = 0; verb < 100; ++verb) {
program[1] = noun;
program[2] = verb;
if (run_program(program) == 19690720) {
output << 100 * noun + verb << std::endl;
return;
}
}
}
throw std::domain_error("No valid solution.");
}

View File

@@ -1,84 +0,0 @@
#include <cassert>
#include <charconv>
#include <iostream>
#include <limits>
#include <unordered_map>
#include <utility>
#include <vector>
#include "days.hpp"
#include "point.hpp"
#include "utils.hpp"
namespace {
typedef aoc2019::Point<int, 2> point_t;
const std::unordered_map<char, point_t> DIRECTION_MAP = {
{'U', {0, -1}},
{'D', {0, 1}},
{'L', {-1, 0}},
{'R', {1, 0}},
};
std::unordered_map<point_t, int> get_points(std::string_view line) {
std::unordered_map<point_t, int> points{};
point_t pos = {};
int steps = 0;
for (auto entry = aoc2019::strtok(line); !line.empty() || !entry.empty(); entry = aoc2019::strtok(line)) {
const auto dir = DIRECTION_MAP.at(entry[0]);
std::size_t amount = 0;
aoc2019::from_chars(entry.substr(1), amount);
assert(amount > 0 && "Must have some valid direction");
for (std::size_t i = 0; i < amount; ++i) {
++steps;
pos += dir;
if (!points.count(pos)) {
points[pos] = steps;
}
}
}
return points;
}
std::pair<std::unordered_map<point_t, int>, std::unordered_map<point_t, int>> read_input(std::istream& input) {
std::string buffer;
std::getline(input, buffer);
auto a = get_points(buffer);
std::getline(input, buffer);
auto b = get_points(buffer);
return { std::move(a), std::move(b) };
}
}
void aoc2019::day03_part1(std::istream &input, std::ostream &output) {
auto [a, b] = read_input(input);
int best = std::numeric_limits<int>::max();
for (const auto& point : a) {
if (b.count(point.first) && point.first.l1() < best) {
best = point.first.l1();
}
}
output << best << std::endl;
}
void aoc2019::day03_part2(std::istream &input, std::ostream &output) {
auto [a, b] = read_input(input);
int best = std::numeric_limits<int>::max();
for (const auto& ap : a) {
const auto bp = b.find(ap.first);
if (bp != b.cend() && (ap.second + bp->second) < best) {
best = ap.second + bp->second;
}
}
output << best << std::endl;
}

View File

@@ -1,94 +0,0 @@
#include <iostream>
#include <utility>
#include "days.hpp"
namespace {
constexpr bool is_valid_pass(int num) {
bool has_double = false;
int prev = 11;
for (; num != 0; num /= 10) {
int digit = num % 10;
if (digit == prev) {
has_double = true;
}
if (digit > prev) {
return false;
}
prev = digit;
}
return has_double;
}
constexpr bool is_valid_pass2(int num) {
int prev = 11;
bool has_double = false;
int run = 1;
for (; num != 0; num /= 10) {
int digit = num % 10;
if (digit == prev) {
++run;
} else {
if (run == 2) {
has_double = true;
}
run = 1;
}
if (digit > prev) {
return false;
}
prev = digit;
}
return has_double || run == 2;
}
std::pair<int, int> read_input(std::istream& input) {
int a, b;
input >> a;
input.ignore();
input >> b;
return {a, b};
}
}
void aoc2019::day04_part1(std::istream &input, std::ostream &output) {
auto [start_range, end_range] = read_input(input);
int num_valid = 0;
for (; start_range <= end_range; ++start_range) {
num_valid += is_valid_pass(start_range);
}
output << num_valid << std::endl;
}
void aoc2019::day04_part2(std::istream &input, std::ostream &output) {
auto [start_range, end_range] = read_input(input);
int num_valid = 0;
for (; start_range <= end_range; ++start_range) {
num_valid += is_valid_pass2(start_range);
}
output << num_valid << std::endl;
}
// Poor man's unit tests
static_assert(is_valid_pass(122345));
static_assert(is_valid_pass(111111));
static_assert(!is_valid_pass(223450));
static_assert(!is_valid_pass(123678));
static_assert(is_valid_pass2(112233));
static_assert(!is_valid_pass2(123444));
static_assert(is_valid_pass2(111122));

View File

@@ -1,15 +0,0 @@
#include <iostream>
#include "days.hpp"
#include "utils.hpp"
void aoc2019::day05_part1(std::istream &input, std::ostream &output) {
auto program = IntCodeComputer::read_intcode(input);
auto result = run_intcode(program, { 1 });
output << result.back() << std::endl;
}
void aoc2019::day05_part2(std::istream &input, std::ostream &output) {
auto program = IntCodeComputer::read_intcode(input);
auto result = run_intcode(program, { 5 });
output << result.back() << std::endl;
}

View File

@@ -1,68 +0,0 @@
#include <deque>
#include <iostream>
#include <unordered_map>
#include <vector>
#include "days.hpp"
namespace {
std::vector<std::pair<std::string, std::string>> read_orbits(std::istream &input) {
std::vector<std::pair<std::string, std::string>> result;
std::string name1, name2;
while (std::getline(input, name1, ')')) {
std::getline(input, name2);
result.emplace_back(name1, name2);
}
return result;
}
}
void aoc2019::day06_part1(std::istream &input, std::ostream &output) {
std::unordered_map<std::string, std::vector<std::string>> orbits;
for (auto[a, b] : read_orbits(input)) {
orbits[std::move(a)].emplace_back(std::move(b));
}
std::deque<std::pair<std::string, int>> todo = {{"COM", 0}};
int total_orbits = 0;
while (!todo.empty()) {
auto[name, offset] = todo.front();
todo.pop_front();
total_orbits += offset;
for (const auto& partner : orbits[name]) {
todo.emplace_back(partner, offset + 1);
}
}
output << total_orbits << std::endl;
}
void aoc2019::day06_part2(std::istream &input, std::ostream &output) {
std::unordered_map<std::string, std::string> ancestors;
for (auto[a, b] : read_orbits(input)) {
ancestors[std::move(b)] = std::move(a);
}
std::unordered_map<std::string, int> santa_ancestors;
for (auto current = ancestors["SAN"]; current != "COM"; current = ancestors[current]) {
santa_ancestors[ancestors[current]] = santa_ancestors[current] + 1;
}
int dist = 0;
for (auto current = ancestors["YOU"]; current != "COM"; current = ancestors[current], ++dist) {
if (auto it = santa_ancestors.find(current); it != santa_ancestors.end()) {
output << dist + it->second << std::endl;
return;
}
}
throw std::domain_error("No valid path.");
}

View File

@@ -1,69 +0,0 @@
#include <algorithm>
#include <array>
#include <iostream>
#include "days.hpp"
#include "utils.hpp"
namespace {
using aoc2019::IntCodeComputer;
std::int64_t simulate(const std::vector<std::int64_t> &program, const std::array<std::int64_t, 5> &phases) {
std::int64_t state = 0;
for (auto phase : phases) {
std::deque<std::int64_t> outputs;
IntCodeComputer computer{program, {phase, state}};
computer.connectOutput(outputs);
computer.run();
state = outputs.front();
}
return state;
}
int simulate2(const std::vector<std::int64_t> &program, const std::array<int, 5> &phases) {
std::vector<IntCodeComputer> computers;
for (int phase : phases) {
computers.emplace_back(program, std::deque<int64_t>{phase});
}
for (int i = 0; i < computers.size(); ++i) {
computers[i].connectOutput(computers[(i + 1) % 5]);
}
computers[0].sendInput(0);
while (std::any_of(computers.begin(), computers.end(), [](const auto &c) { return !c.isTerminated();})) {
for (auto& computer : computers) {
computer.run();
}
}
return computers[0].currentInputs().back();
}
}
void aoc2019::day07_part1(std::istream &input, std::ostream &output) {
const auto program = aoc2019::IntCodeComputer::read_intcode(input);
std::array<std::int64_t, 5> phases{0, 1, 2, 3, 4};
std::int64_t best = 0;
do {
best = std::max(simulate(program, phases), best);
} while (std::next_permutation(phases.begin(), phases.end()));
output << best << std::endl;
}
void aoc2019::day07_part2(std::istream &input, std::ostream &output) {
const auto program = aoc2019::IntCodeComputer::read_intcode(input);
std::array<int, 5> phases{5, 6, 7, 8, 9};
int best = 0;
do {
best = std::max(simulate2(program, phases), best);
} while (std::next_permutation(phases.begin(), phases.end()));
output << best << std::endl;
}

View File

@@ -1,69 +0,0 @@
#include <algorithm>
#include <iostream>
#include <limits>
#include <string>
#include <string_view>
#include "days.hpp"
namespace {
constexpr std::size_t WIDTH = 25;
constexpr std::size_t HEIGHT = 6;
constexpr std::size_t TILE_SIZE = WIDTH * HEIGHT;
enum Color {
BLACK = '0',
WHITE = '1',
TRANSPARENT = '2',
};
}
void aoc2019::day08_part1(std::istream &input, std::ostream &output) {
std::string buffer;
std::getline(input, buffer);
std::string_view image = buffer;
auto best = std::numeric_limits<int>::max();
auto best_score = 0;
for (std::size_t i = 0; i < buffer.length(); i += TILE_SIZE) {
auto tile = image.substr(i, TILE_SIZE);
auto zeros = std::count(tile.begin(), tile.end(), '0');
if (zeros < best) {
best = zeros;
best_score = std::count(tile.begin(), tile.end(), '1') * std::count(tile.begin(), tile.end(), '2');
}
}
output << best_score << std::endl;
}
void aoc2019::day08_part2(std::istream &input, std::ostream &output) {
std::string buffer;
std::getline(input, buffer);
std::string_view image = buffer;
std::array<Color, TILE_SIZE> final_image;
std::fill(final_image.begin(), final_image.end(), TRANSPARENT);
for (std::size_t i = 0; i < buffer.length(); i += TILE_SIZE) {
auto tile = image.substr(i, TILE_SIZE);
for (int j = 0; j < TILE_SIZE; ++j) {
if (final_image[j] == TRANSPARENT) {
final_image[j] = static_cast<Color>(tile[j]);
}
}
}
for (std::size_t i = 0; i < final_image.size(); ++i) {
output << (final_image[i] == WHITE ? '#' : ' ');
if (i % WIDTH == WIDTH - 1) {
output << '\n';
}
}
}

View File

@@ -1,33 +0,0 @@
#include <iostream>
#include <deque>
#include "days.hpp"
#include "utils.hpp"
void aoc2019::day09_part1(std::istream &input, std::ostream &output) {
std::deque<std::int64_t> outputs;
IntCodeComputer computer(input, { 1 });
computer.connectOutput(outputs);
computer.run();
if (outputs.size() != 1) {
std::cerr << "Error: " << outputs.size() << std::endl;
for (auto c : outputs) {
std::cerr << c << std::endl;
}
} else {
output << outputs.front() << std::endl;
}
}
void aoc2019::day09_part2(std::istream &input, std::ostream &output) {
std::deque<std::int64_t> outputs;
IntCodeComputer computer(input, { 2 });
computer.connectOutput(outputs);
computer.run();
output << outputs.front() << std::endl;
}

View File

@@ -1,118 +0,0 @@
#include <algorithm>
#include <iostream>
#include <numeric>
#include <unordered_set>
#include <cmath>
#include "days.hpp"
#include "point.hpp"
namespace {
typedef aoc2019::Point<int, 2> point_t;
std::vector<point_t> read_points(std::istream &input) {
std::vector<point_t> result;
int y = 0;
for (std::string buffer; std::getline(input, buffer); ++y) {
std::size_t x = 0;
while ((x = buffer.find('#', x)) != std::string::npos) {
result.push_back({(int) x, y});
x += 1;
}
}
return result;
}
point_t simplify(point_t x) {
auto gcd = std::abs(std::gcd(x[0], x[1]));
if (gcd > 1) {
return {x[0] / gcd, x[1] / gcd};
}
return x;
}
std::pair<std::size_t, std::size_t> part1(const std::vector<point_t> &points) {
std::size_t best = 0;
std::size_t best_index = 0;
std::unordered_set<point_t> visible;
for (std::size_t i = 0; i < points.size(); ++i) {
visible.clear();
const auto point = points[i];
for (auto asteroid : points) {
if (asteroid == point) continue;
visible.insert(simplify(asteroid - point));
}
if (visible.size() > best) {
best = visible.size();
best_index = i;
}
best = std::max(visible.size(), best);
}
return {best, best_index};
}
}
void aoc2019::day10_part1(std::istream &input, std::ostream &output) {
const auto points = read_points(input);
auto[best, _] = part1(points);
output << best << std::endl;
}
void aoc2019::day10_part2(std::istream &input, std::ostream &output) {
const auto points = read_points(input);
const auto[_, base] = part1(points);
const auto base_point = points[base];
std::unordered_map<point_t, std::vector<point_t>> angle_points;
for (auto point : points) {
if (point == base_point) continue;
auto diff = point - base_point;
angle_points[simplify(diff)].push_back(diff);
}
std::vector<std::pair<float, point_t>> angles;
for (auto &entry : angle_points) {
angles.emplace_back(std::atan2(entry.first[1], entry.first[0]), entry.first);
// Sort entries in descending order of distance so we can pop_back() them
std::sort(entry.second.begin(), entry.second.end(), [](auto a, auto b) { return a.l1() > b.l1(); });
}
std::sort(angles.begin(), angles.end(), std::greater<>{});
const auto starting_point = std::make_pair(float(0.5 * M_PI),
point_t{std::numeric_limits<int>::max(),
std::numeric_limits<int>::max()});
auto it = std::lower_bound(angles.begin(), angles.end(), starting_point, std::greater<>{});
for (int hits = 0; hits < 199; ++hits) {
angle_points[it->second].pop_back();
// Advance it to the next asteroid we can hit.
while (angle_points[it->second].empty()) {
++it;
if (it == angles.end()) {
it = angles.begin();
}
}
}
auto final_asteroid = angle_points[it->second].back() + base_point;
output << final_asteroid[0] * 100 + final_asteroid[1] << std::endl;
}

View File

@@ -1,80 +0,0 @@
#include <iostream>
#include <cassert>
#include "days.hpp"
#include "utils.hpp"
#include "point.hpp"
namespace {
typedef aoc2019::Point<int, 2> point_t;
using aoc2019::IntCodeComputer;
inline point_t turn_right(point_t direction) {
return {-direction[1], direction[0]};
}
inline point_t turn_left(point_t direction) {
return {direction[1], -direction[0]};
}
std::unordered_map<point_t, bool> simulate(std::istream &input, bool background = false) {
std::unordered_map<point_t, bool> image;
point_t direction{0, -1};
point_t pos = {0, 0};
IntCodeComputer computer(IntCodeComputer::read_intcode(input), {});
std::deque<std::int64_t> outputs;
computer.connectOutput(outputs);
while (!computer.isTerminated()) {
const auto it = image.find(pos);
computer.sendInput(it != image.end() ? it->second : background);
computer.run();
if (!outputs.empty()) {
assert(outputs.size() == 2);
auto color = outputs.front();
auto turn = outputs.back();
outputs.clear();
image[pos] = color;
if (turn) {
direction = turn_right(direction);
} else {
direction = turn_left(direction);
}
pos += direction;
}
}
return image;
}
}
void aoc2019::day11_part1(std::istream &input, std::ostream &output) {
const auto result = simulate(input);
output << result.size() << std::endl;
}
void aoc2019::day11_part2(std::istream &input, std::ostream &output) {
const auto result = simulate(input, true);
// Determine bounding box
auto[lower,upper] = aoc2019::bounding_box(result);
for (int y = lower[1]; y <= upper[1]; ++y) {
for (int x = lower[0]; x <= upper[0]; ++x) {
if (auto it = result.find({x, y}); it != result.end() && it->second) {
output << '#';
} else {
output << ' ';
}
}
output << '\n';
}
}

View File

@@ -1,100 +0,0 @@
#include <iostream>
#include <numeric>
#include <vector>
#include "days.hpp"
#include "point.hpp"
namespace {
typedef aoc2019::Point<int, 3> point_t;
using aoc2019::from_chars;
std::vector<point_t> read_moons(std::istream &input) {
std::vector<point_t> moons;
point_t moon;
while (aoc2019::read_line_numbers_and_garbage<int>(input, moon.begin())) {
moons.push_back(moon);
}
return moons;
}
void update_velocity(const point_t &a, point_t &va, const point_t &b, point_t &vb) {
for (int i = 0; i < a.size(); ++i) {
if (a[i] < b[i]) {
va[i]++;
vb[i]--;
} else if (a[i] > b[i]) {
va[i]--;
vb[i]++;
}
}
}
void update_velocities(const std::vector<point_t> &positions, std::vector<point_t> &velocities) {
for (int i = 0; i < positions.size(); ++i) {
for (int j = i + 1; j < positions.size(); ++j) {
update_velocity(positions[i], velocities[i], positions[j], velocities[j]);
}
}
}
void simulate_step(std::vector<point_t> &moons, std::vector<point_t> &velocities) {
update_velocities(moons, velocities);
for (int j = 0; j < moons.size(); ++j) {
moons[j] += velocities[j];
}
}
}
void aoc2019::day12_part1(std::istream &input, std::ostream &output) {
auto moons = read_moons(input);
std::vector<point_t> velocities(moons.size());
for (int i = 0; i < 1000; ++i) {
simulate_step(moons, velocities);
}
int energy = 0;
for (int i = 0; i < moons.size(); ++i) {
energy += moons[i].l1() * velocities[i].l1();
}
output << energy << std::endl;
}
void aoc2019::day12_part2(std::istream &input, std::ostream &output) {
const auto moons = read_moons(input);
auto moons_mut = moons;
std::vector<point_t> velocities(moons.size());
std::array<uint64_t, 3> recurrence = {0, 0, 0};
std::uint64_t steps = 0;
while (!std::all_of(recurrence.begin(), recurrence.end(), [](auto x) { return x > 0; })) {
simulate_step(moons_mut, velocities);
++steps;
for (int i = 0; i < 3; ++i) {
if (!recurrence[i]) {
bool back_again =
std::all_of(velocities.begin(), velocities.end(), [i](const auto &x) { return !x[i]; })
&& std::equal(moons_mut.begin(), moons_mut.end(), moons.begin(),
[i](const auto &a, const auto &b) {
return a[i] == b[i];
});
if (back_again) {
recurrence[i] = steps;
}
}
}
}
auto result = std::lcm(recurrence[0], std::lcm(recurrence[1], recurrence[2]));
output << result << std::endl;
}

View File

@@ -1,138 +0,0 @@
#include <iostream>
#ifdef ANIMATE_DAY13
#include <chrono>
#include <thread>
#endif
#include "days.hpp"
#include "utils.hpp"
#include "point.hpp"
namespace {
typedef aoc2019::Point<int64_t, 2> point_t;
enum class Tile {
EMPTY,
WALL,
BLOCK,
PADDLE,
BALL,
};
typedef std::unordered_map<point_t, Tile> Screen;
std::optional<std::int64_t> update_screen(std::deque<std::int64_t> &output_buffer, Screen &screen) {
std::optional<std::int64_t> score;
while (!output_buffer.empty()) {
auto x = output_buffer.front(); output_buffer.pop_front();
auto y = output_buffer.front(); output_buffer.pop_front();
auto type = output_buffer.front(); output_buffer.pop_front();
if (x == -1 && y == 0) {
score = type;
continue;
}
screen[{x, y}] = static_cast<Tile>(type);
}
return score;
}
void draw_screen(const Screen &screen, std::ostream& output) {
// Determine bounding box
using limits = std::numeric_limits<int>;
const auto [lower, upper] = aoc2019::bounding_box(screen);
for (auto y = lower[1]; y <= upper[1]; ++y) {
for (auto x = lower[0]; x <= upper[0]; ++x) {
char c = ' ';
if (auto it = screen.find({x, y}); it != screen.end()) {
switch (it->second) {
case Tile::EMPTY:
c = ' ';
break;
case Tile::BALL:
c = '*';
break;
case Tile::BLOCK:
c = '=';
break;
case Tile::PADDLE:
c = '_';
break;
case Tile::WALL:
c = '#';
break;
}
}
output << c;
}
output << '\n';
}
}
auto find_pos(const Screen &screen, Tile to_find) {
return std::find_if(screen.begin(), screen.end(), [to_find](const auto& x) {
return x.second == to_find;
});
}
}
void aoc2019::day13_part1(std::istream &input, std::ostream &output) {
Screen screen;
aoc2019::IntCodeComputer computer(aoc2019::IntCodeComputer::read_intcode(input));
std::deque<std::int64_t> output_buffer;
computer.connectOutput(output_buffer);
computer.run();
update_screen(output_buffer, screen);
output << std::count_if(screen.begin(), screen.end(), [](const auto &x) { return x.second == Tile::BLOCK; })
<< std::endl;
}
void aoc2019::day13_part2(std::istream &input, std::ostream &output) {
auto program = aoc2019::IntCodeComputer::read_intcode(input);
program[0] = 2;
aoc2019::IntCodeComputer computer(std::move(program));
std::deque<std::int64_t> output_buffer;
computer.connectOutput(output_buffer);
computer.run();
Screen screen;
std::int64_t score = 0;
while (!computer.isTerminated()) {
computer.run();
auto new_score = update_screen(output_buffer, screen);
if (new_score) {
score = *new_score;
}
#ifdef ANIMATE_DAY13
output << "Score: " << score << std::endl;
draw_screen(screen, output);
std::this_thread::sleep_for(std::chrono::milliseconds(40));
#endif
auto ball_pos = find_pos(screen, Tile::BALL)->first;
auto paddle_pos = find_pos(screen, Tile::PADDLE)->first;
if (ball_pos[0] < paddle_pos[0]) {
computer.sendInput(-1);
} else if (ball_pos[0] > paddle_pos[0]) {
computer.sendInput(1);
} else {
computer.sendInput(0);
}
}
output << score << std::endl;
}

View File

@@ -1,146 +0,0 @@
#include <iostream>
#include <cstdint>
#include <vector>
#include <map>
#include <unordered_map>
#include <algorithm>
#include <regex>
#include <charconv>
#include "days.hpp"
#include "utils.hpp"
namespace {
typedef std::pair<std::string, std::int64_t> requirement_t;
typedef std::vector<requirement_t> reqlist_t;
std::map<reqlist_t, reqlist_t> read_recipes(std::istream &input) {
std::map<reqlist_t, reqlist_t> recipes;
std::string buffer;
std::regex listing_regex("(\\d+) ([A-Z]+)");
std::int64_t amount;
while (std::getline(input, buffer)) {
reqlist_t requirements, production;
std::string_view line = buffer;
auto split_point = line.find(" => ");
auto requirements_part = line.substr(0, split_point);
auto production_part = line.substr(split_point + 4);
for (auto it = std::regex_token_iterator(requirements_part.begin(), requirements_part.end(), listing_regex,
{1, 2}); it != std::cregex_token_iterator(); ++it) {
std::from_chars(it->first, it->second, amount);
++it;
requirements.emplace_back(*it, amount);
}
for (auto it = std::regex_token_iterator(production_part.begin(), production_part.end(), listing_regex,
{1, 2}); it != std::cregex_token_iterator(); ++it) {
std::from_chars(it->first, it->second, amount);
++it;
production.emplace_back(*it, amount);
}
recipes[std::move(production)] = std::move(requirements);
}
return recipes;
}
template<class Map>
std::unordered_map<std::string, reqlist_t> element_creators(const Map &map) {
std::unordered_map<std::string, reqlist_t> inverted;
for (auto &entry : map) {
for (auto &x : entry.first) {
inverted[x.first] = entry.first;
}
}
return inverted;
}
std::vector<std::string> topological_order(const std::map<reqlist_t, reqlist_t> &recipes) {
std::vector<std::string> order;
std::unordered_map<std::string, std::vector<std::string>> edges;
for (auto &entry : recipes) {
for (auto &production : entry.first) {
std::transform(entry.second.begin(), entry.second.end(), std::back_inserter(edges[production.first]),
[](const auto &x) {
return x.first;
});
}
}
return aoc2019::topological_sort(edges);
}
std::int64_t ore_to_fuel(const std::map<reqlist_t, reqlist_t> &recipes, std::int64_t amount = 1) {
auto inverted = element_creators(recipes);
auto order = topological_order(recipes);
std::unordered_map<std::string_view, std::int64_t> total_requirements;
total_requirements["FUEL"] = amount;
for (const auto &element : order) {
if (element == "ORE") {
break;
}
const auto number_required = total_requirements[element];
if (number_required <= 0) {
continue;
}
const auto &productions = inverted.at(element);
const auto &requirements = recipes.at(productions);
auto number_produced = std::find_if(productions.begin(), productions.end(),
[element](const auto &x) { return x.first == element; })->second;
auto productions_needed = number_required / number_produced + (number_required % number_produced ? 1 : 0);
for (auto &requirement : requirements) {
total_requirements[requirement.first] += requirement.second * productions_needed;
}
for (auto &production : productions) {
total_requirements[production.first] -= productions_needed * production.second;
}
}
return total_requirements["ORE"];
}
}
void aoc2019::day14_part1(std::istream &input, std::ostream &output) {
auto recipes = read_recipes(input);
output << ore_to_fuel(recipes) << std::endl;
}
void aoc2019::day14_part2(std::istream &input, std::ostream &output) {
auto recipes = read_recipes(input);
constexpr std::int64_t ore_stock = 1000000000000;
std::int64_t min = 1, max = ore_stock + 1; // assumption: 1 ore produces < 1 fuel.
while (max - min > 1) {
auto cur = (max + min) / 2;
if (ore_to_fuel(recipes, cur) < ore_stock) {
min = cur;
} else {
max = cur - 1;
}
}
output << (max + min) / 2 << std::endl;
}

View File

@@ -1,153 +0,0 @@
#include <iostream>
#include <cassert>
#include <unordered_set>
#include "days.hpp"
#include "utils.hpp"
#include "point.hpp"
namespace {
typedef aoc2019::Point<int, 2> point_t;
enum class Tile {
Wall,
Empty,
Oxygen,
};
enum class Mark {
None,
Temp,
Permanent,
};
const std::unordered_map<point_t, std::int64_t> DIRECTIONS{
{{0, -1}, 1},
{{0, 1}, 2},
{{-1, 0}, 3},
{{1, 0}, 4},
};
std::unordered_map<point_t, Tile> read_map(std::istream &input) {
aoc2019::IntCodeComputer computer(input);
std::deque<std::int64_t> output_buffer;
computer.connectOutput(output_buffer);
point_t pos = {0, 0};
std::deque<point_t> prev;
std::unordered_map<point_t, Tile> map{{pos, Tile::Empty}};
std::unordered_map<point_t, Mark> markings{{pos, Mark::Temp}};
computer.run();
while (true) {
std::optional<point_t> next_step;
for (auto &direction : DIRECTIONS) {
if (markings[pos + direction.first] == Mark::None) {
next_step = direction.first;
break;
}
}
if (next_step) {
const auto next_pos = pos + *next_step;
computer.sendInput(DIRECTIONS.at(*next_step));
computer.run();
assert(!output_buffer.empty());
switch (output_buffer.front()) {
case 0:
markings[next_pos] = Mark::Permanent;
map[next_pos] = Tile::Wall;
break;
case 1:
case 2:
prev.push_front(pos);
markings[next_pos] = Mark::Temp;
map[next_pos] = static_cast<Tile>(output_buffer.front());
pos = next_pos;
break;
default:
throw std::domain_error("Invalid data from remote");
}
output_buffer.pop_front();
assert(output_buffer.empty());
} else {
markings[pos] = Mark::Permanent;
// Nowhere left to go, move back.
if (prev.empty()) {
return map;
}
auto prev_pos = prev.front();
auto step = DIRECTIONS.at(prev_pos - pos);
prev.pop_front();
computer.sendInput(step);
computer.run();
// We should be able to travel back
assert(output_buffer.front() == 1);
output_buffer.pop_front();
pos = prev_pos;
}
}
}
template<class Callback>
int bfs(const std::unordered_map<point_t, Tile> &map, point_t starting_point, Callback callback) {
std::deque<std::pair<point_t, int>> todo{{starting_point, 0}};
std::unordered_set<point_t> visited{{0, 0}};
int max_dist = 0;
while (!todo.empty()) {
auto[cur, dist] = todo.front();
todo.pop_front();
max_dist = std::max(max_dist, dist);
for (auto &dir : DIRECTIONS) {
auto new_pos = cur + dir.first;
if (!visited.count(new_pos)) {
visited.insert(new_pos);
if (callback(map.at(new_pos))) {
return dist + 1;
}
switch (map.at(new_pos)) {
case Tile::Oxygen:
case Tile::Empty:
todo.emplace_back(new_pos, dist + 1);
break;
default:
break;
}
}
}
}
return max_dist;
}
}
void aoc2019::day15_part1(std::istream &input, std::ostream &output) {
const auto map = read_map(input);
auto dist = bfs(map, {0, 0}, [](Tile x) { return x == Tile::Oxygen; });
output << dist << std::endl;
}
void aoc2019::day15_part2(std::istream &input, std::ostream &output) {
const auto map = read_map(input);
auto starting_point = std::find_if(map.begin(), map.end(), [](auto &x) { return x.second == Tile::Oxygen; })->first;
auto dist = bfs(map, starting_point, [](Tile x) { return false; });
output << dist << std::endl;
}

View File

@@ -1,117 +0,0 @@
#include <iostream>
#include <array>
#include <vector>
#include <cassert>
#include <iterator>
#include <numeric>
#include "days.hpp"
#include "utils.hpp"
namespace {
std::array<int, 4> base_pattern{0, 1, 0, -1};
int get_modifier(int rank, int pos) {
pos += 1;
pos /= rank + 1;
return base_pattern[pos % 4];
}
std::vector<int> read_input(std::istream &input) {
std::vector<int> result;
for (char c; input >> c;) {
assert(std::isdigit(c));
result.push_back(c - '0');
}
return result;
}
void simulate(std::vector<int> numbers, std::ostream &output) {
std::vector<int> new_numbers(numbers.size());
std::vector<int> partial_sums(numbers.size());
for (int i = 0; i < 100; ++i) {
for (int rank = 0; rank < numbers.size(); ++rank) {
std::partial_sum(numbers.begin() + rank, numbers.end(), partial_sums.begin() + rank);
int n = 0;
for (int pos = rank; pos < numbers.size(); pos += rank + 1) {
int run = std::min(rank + 1, (int) numbers.size() - pos);
if (int modifier = get_modifier(rank, pos); modifier) {
n += modifier * (partial_sums[pos + run - 1] - partial_sums[pos] + numbers[pos]);
}
}
n = std::abs(n % 10);
new_numbers[rank] = n;
}
std::swap(numbers, new_numbers);
}
std::copy(numbers.begin(), numbers.begin() + 8, std::ostream_iterator<int>(output));
output << std::endl;
}
int get_offset(const std::vector<int> &numbers) {
int offset = 0;
for (int i = 0; i < 7; ++i) {
offset *= 10;
offset += numbers[i];
}
return offset;
}
std::vector<int> numbers_from_offset(const std::vector<int> &numbers, unsigned int offset) {
constexpr auto repetitions = 10000;
const auto desired_length = repetitions * numbers.size() - offset;
std::vector<int> numbers_after;
numbers_after.reserve(desired_length);
numbers_after.insert(numbers_after.end(), numbers.begin() + (offset % numbers.size()), numbers.end());
while (numbers_after.size() < desired_length) {
auto remaining = desired_length - numbers_after.size();
if (remaining >= numbers.size()) {
numbers_after.insert(numbers_after.end(), numbers.begin(), numbers.end());
} else {
numbers_after.insert(numbers_after.end(), numbers.begin(), numbers.end() + remaining);
}
}
return numbers_after;
}
}
void aoc2019::day16_part1(std::istream &input, std::ostream &output) {
auto numbers = read_input(input);
simulate(std::move(numbers), output);
}
void aoc2019::day16_part2(std::istream &input, std::ostream &output) {
auto numbers = read_input(input);
const int offset = get_offset(numbers);
numbers = numbers_from_offset(numbers, offset);
std::vector<int> new_numbers(numbers.size());
std::vector<int> partial_sums(numbers.size());
for (int i = 0; i < 100; ++i) {
std::partial_sum(numbers.rbegin(), numbers.rend(), partial_sums.rbegin());
std::transform(partial_sums.begin(), partial_sums.end(), new_numbers.begin(), [](int x) {
return x % 10;
});
std::swap(numbers, new_numbers);
}
std::copy(numbers.begin(), numbers.begin() + 8, std::ostream_iterator<int>(output));
output << std::endl;
}

View File

@@ -1,103 +0,0 @@
#include <iostream>
#include <string_view>
#include <cassert>
#include "days.hpp"
#include "utils.hpp"
#include "point.hpp"
namespace {
typedef aoc2019::Point<int, 2> point_t;
const std::unordered_map<char, point_t> DIRECTIONS{
{'^', {0, -1}},
{'>', {0, 1}},
{'v', {1, 0}},
{'<', {-1, 0}},
};
std::unordered_map<point_t, char> read_scaffold(const std::deque<std::int64_t> &data) {
int x = 0;
int y = 0;
std::unordered_map<point_t, char> map;
for (auto n : data) {
if (n == '\n') {
if (x == 0) {
// Double newline, end of map
break;
}
++y;
x = 0;
continue;
} else {
map[{x, y}] = (char) n;
++x;
}
}
return map;
}
}
void aoc2019::day17_part1(std::istream &input, std::ostream &output) {
IntCodeComputer computer(input);
std::deque<std::int64_t> output_buffer;
computer.connectOutput(output_buffer);
computer.run();
const auto map = read_scaffold(output_buffer);
std::int64_t total = 0;
for (auto &entry : map) {
if (entry.second == '.') continue;
bool is_intersection = std::all_of(DIRECTIONS.begin(), DIRECTIONS.end(), [&map, &entry](auto &x) {
auto it = map.find(x.second + entry.first);
return it != map.end() && it->second != '.';
});
if (is_intersection) {
total += entry.first[0] * entry.first[1];
}
}
output << total << std::endl;
}
void aoc2019::day17_part2(std::istream &input, std::ostream &output) {
using namespace std::literals;
aoc2019::IntCodeComputer computer(input);
computer[0] = 2;
std::deque<std::int64_t> output_buffer;
computer.connectOutput(output_buffer);
std::array<std::string_view, 3> programs = {
"L,6,R,8,L,4,R,8,L,12\n",
"L,12,R,10,L,4\n",
"L,12,L,6,L,4,L,4\n",
};
auto combined_programs = "A,B,B,C,B,C,B,C,A,A\n"sv;
computer.sendInputs(combined_programs);
for (auto program : programs) {
computer.sendInputs(program);
}
// Don't give me output.
computer.sendInputs("n\n");
computer.run();
assert(!output_buffer.empty());
if (output_buffer.size() == 1) {
output << output_buffer.front() << std::endl;
} else {
std::copy(output_buffer.begin(), output_buffer.end(), std::ostreambuf_iterator<char>(output));
output << output_buffer.back() << std::endl;
}
}

View File

@@ -1,76 +0,0 @@
import fileinput
import sys
def turn_left(direction):
x, y = direction
return (y, -x)
def turn_right(direction):
x, y = direction
return (-y, x)
def add(pos, direction):
return tuple(a + b for a, b in zip(pos, direction))
def main():
chart = [line.strip() for line in fileinput.input()]
pos = None
for y, line in enumerate(chart):
x = line.find('^')
if x >= 0:
pos = (x, y)
break
if not pos:
sys.exit('starting point not found')
route = ['L']
direction = (-1, 0)
def bounds_check(pos):
x, y = pos
return x >= 0 and y >= 0 and y < len(chart)
while True:
# try to move forward
next_pos = add(direction, pos)
dist = 0
while bounds_check(next_pos) and chart[next_pos[1]][next_pos[0]] == '#':
dist += 1
pos = next_pos
next_pos = add(direction, pos)
if dist:
route.append(dist)
else:
break
for move, new_dir in zip(('L', 'R'), (turn_left(direction), turn_right(direction))):
next_pos = add(pos, new_dir)
if bounds_check(next_pos) and chart[next_pos[1]][next_pos[0]] == '#':
route.append(move)
direction = new_dir
break
printable_route = []
for x in route:
if x == 'L' or x == 'R':
printable_route.append(x)
else:
printable_route += ['M'] * x
print(','.join(str(x) for x in route))
print(','.join(printable_route))
if __name__ == '__main__':
main()

View File

@@ -1,226 +0,0 @@
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <bit>
#include <map>
#include "days.hpp"
#include "point.hpp"
static_assert(sizeof(int) >= 4, "Int should be at least 32 bits.");
using namespace std::string_view_literals;
namespace {
typedef aoc2019::Point<int, 2> point_t;
typedef std::vector<std::string> map_t;
std::array<point_t, 4> DIRECTIONS = {{
{0, -1},
{0, 1},
{-1, 0},
{1, 0},
}};
map_t read_map(std::istream &input) {
std::string buffer;
map_t map;
while (std::getline(input, buffer)) {
map.push_back(buffer);
}
return map;
}
point_t find(const std::vector<std::string> &map, char needle) {
for (int y = 0; y < map.size(); ++y) {
auto x = map[y].find(needle);
if (x != std::string::npos) {
return {(int) x, y};
}
}
throw std::invalid_argument("Can't find it!");
}
std::vector<std::pair<char, int>> find_edges(const map_t &map, point_t starting_point) {
std::vector<std::pair<char, int>> edges;
std::queue<std::pair<int, point_t>> todo;
todo.emplace(0, starting_point);
std::unordered_set<point_t> visited{starting_point};
while (!todo.empty()) {
const auto[dist, pos] = todo.front();
todo.pop();
for (auto &direction : DIRECTIONS) {
auto next_pos = pos + direction;
const char at = map[next_pos[1]][next_pos[0]];
if (at == '#' || visited.count(next_pos)) {
// Wall or already visited, ignore
continue;
}
visited.insert(next_pos);
if (std::isalpha(at)) {
// Don't walk through stuff
edges.emplace_back(at, dist + 1);
} else {
todo.emplace(dist + 1, next_pos);
}
}
}
return edges;
}
auto compute_implied_graph(const map_t &map) {
std::unordered_map<char, std::vector<std::pair<char, int>>> implied_graph;
for (int y = 0; y < map.size(); ++y) {
for (int x = 0; x < map[y].size(); ++x) {
char at = map[y][x];
if ("@/^*"sv.find(at) != std::string_view::npos || std::isalpha(at)) {
implied_graph[at] = find_edges(map, {x, y});
}
}
}
return implied_graph;
}
inline unsigned int key_index(char c) {
return 1u << static_cast<unsigned int>(c - 'A');
}
}
void aoc2019::day18_part1(std::istream &input, std::ostream &output) {
using state_t = std::tuple<unsigned int, char>;
const auto map = read_map(input);
auto implied_graph = compute_implied_graph(map);
std::priority_queue<std::pair<int, state_t>, std::vector<std::pair<int, state_t>>, std::greater<>> todo;
std::map<state_t, int> visited;
todo.emplace(0, std::make_pair(0, '@'));
auto target_size = std::count_if(implied_graph.cbegin(), implied_graph.cend(),
[](auto &x) { return std::islower(x.first); });
while (!todo.empty()) {
const auto[dist, state] = todo.top();
todo.pop();
if (visited[state] < dist) {
continue;
}
auto[keys, pos] = state;
if (std::__popcount(keys) == target_size) {
output << dist << std::endl;
return;
}
for (const auto &edge : implied_graph.at(pos)) {
auto next_dist = dist + edge.second;
auto next_keys = keys;
if (std::islower(edge.first)) {
// Add the key to our collection
next_keys |= key_index(edge.first);;
} else if (std::isupper(edge.first)) {
// Check if we have the required key already
if (!(next_keys & key_index(edge.first))) {
continue;
}
}
state_t next_state = {next_keys, edge.first};
if (auto it = visited.find(next_state); it == visited.end() || it->second > next_dist) {
visited[next_state] = next_dist;
todo.emplace(next_dist, next_state);
}
}
}
throw std::logic_error("Should have terminated by now.");
}
void aoc2019::day18_part2(std::istream &input, std::ostream &output) {
using state_t = std::pair<unsigned int, std::array<char, 4>>;
auto map = read_map(input);
// problem statement says to duplicate @ but where's the fun in that
const auto initial_pos = find(map, '@');
// problem statement says to duplicate @ but where's the fun in that, let's have different starting positions
std::array<std::string_view, 3> overlay = {
"@#*",
"###",
"^#/",
};
for (int y = 0; y < 3; ++y) {
auto &row = map[initial_pos[1] + y - 1];
std::copy(overlay[y].begin(), overlay[y].end(), row.data() + initial_pos[0] - 1);
}
const auto implied_graph = compute_implied_graph(map);
std::priority_queue<std::pair<int, state_t>, std::vector<std::pair<int, state_t>>, std::greater<>> todo;
std::map<std::pair<unsigned int, char>, int> visited;
todo.emplace(0, state_t(0, {'@', '*', '^', '/'}));
auto target_size = std::count_if(implied_graph.cbegin(), implied_graph.cend(),
[](auto &x) { return std::islower(x.first); });
while (!todo.empty()) {
const auto[dist, state] = todo.top();
todo.pop();
auto[keys, pos] = state;
if (std::__popcount(keys) == target_size) {
output << dist << std::endl;
return;
}
for (int i = 0; i < 4; ++i) {
auto next_pos = pos;
for (const auto &edge : implied_graph.at(pos[i])) {
auto next_dist = dist + edge.second;
auto next_keys = keys;
if (std::islower(edge.first)) {
// Add the key to our collection
next_keys |= key_index(edge.first);;
} else if (std::isupper(edge.first)) {
// Check if we have the required key already
if (!(next_keys & key_index(edge.first))) {
continue;
}
}
next_pos[i] = edge.first;
state_t next_state = {next_keys, next_pos};
if (auto it = visited.find({next_keys, next_pos[i]}); it == visited.end() || it->second > next_dist) {
visited[{next_keys, next_pos[i]}] = next_dist;
todo.emplace(next_dist, next_state);
}
}
}
}
output << "Not implemented\n";
}

View File

@@ -1,89 +0,0 @@
#include <iostream>
#include <cassert>
#include <queue>
#include "days.hpp"
#include "utils.hpp"
namespace {
bool bounds_check(aoc2019::IntCodeComputer computer, std::int64_t x, std::int64_t y) {
std::deque<std::int64_t> output_buffer;
computer.connectOutput(output_buffer);
computer.sendInput(x);
computer.sendInput(y);
computer.run();
assert(computer.isTerminated());
assert(!output_buffer.empty());
return output_buffer.front();
}
class Beam {
private:
aoc2019::IntCodeComputer computer;
std::int64_t last_width = 1;
std::int64_t last_start = 0;
std::int64_t y = 0;
public:
Beam(std::istream &input) : computer(input) {};
std::pair<std::int64_t, std::int64_t> next() {
auto x = last_start;
while (!bounds_check(computer, x, y)) {
++x;
}
last_start = x;
x += last_width - 1;
while (bounds_check(computer, x, y)) {
++x;
}
last_width = x - last_start;
++y;
return {last_start, last_width};
}
};
}
void aoc2019::day19_part1(std::istream &input, std::ostream &output) {
Beam beam(input);
std::int64_t covered = 0;
for (std::int64_t y = 0; y < 50; ++y) {
const auto[start, width] = beam.next();
if (start >= 50) break;
covered += std::min(50 - start, width);
}
output << covered << std::endl;
}
void aoc2019::day19_part2(std::istream &input, std::ostream &output) {
Beam beam(input);
std::queue<std::int64_t> beam_ends;
constexpr std::int64_t DIMENSION = 100;
for (std::int64_t y = 0; true; ++y) {
const auto[start, width] = beam.next();
beam_ends.push(start + width);
if (beam_ends.size() == DIMENSION) {
auto end = beam_ends.front();
if (end - start >= DIMENSION) {
auto result = start * 10000 + y - DIMENSION + 1;
output << result << std::endl;
return;
}
beam_ends.pop();
}
}
}

View File

@@ -1,215 +0,0 @@
#include <iostream>
#include <vector>
#include <map>
#include <unordered_set>
#include <queue>
#include <set>
#include "days.hpp"
#include "point.hpp"
namespace {
typedef aoc2019::Point<int, 2> point_t;
typedef std::vector<std::string> map_t;
std::array<point_t, 4> DIRECTIONS = {{
{0, -1},
{0, 1},
{-1, 0},
{1, 0},
}};
std::vector<std::string> read_map(std::istream &input) {
std::string buffer;
std::vector<std::string> map;
while (std::getline(input, buffer)) {
map.push_back(buffer);
}
return map;
}
auto get_portals(const map_t &map) {
std::unordered_map<point_t, std::string> portals;
// First find horizontal portals
for (int y = 0; y < map.size(); ++y) {
for (int x = 0; x < map[y].size() - 1; ++x) {
if (std::isalpha(map[y][x]) && std::isalpha(map[y][x + 1])) {
// find out the entry point
point_t entry_point = {0, y};
if (x > 0 && map[y][x - 1] == '.') {
entry_point[0] = x - 1;
} else {
entry_point[0] = x + 2;
}
portals[entry_point] = map[y].substr(x, 2);
}
}
}
char name[3] = {0, 0, 0};
for (int x = 0; x < map[0].size(); ++x) {
for (int y = 0; y < map.size() - 1; ++y) {
if (std::isalpha(map[y][x]) && std::isalpha(map[y + 1][x])) {
name[0] = map[y][x];
name[1] = map[y + 1][x];
point_t entry_point = {x, 0};
if (y > 0 && map[y - 1][x] == '.') {
entry_point[1] = y - 1;
} else {
entry_point[1] = y + 2;
}
portals[entry_point] = name;
}
}
}
return portals;
}
std::unordered_map<point_t, std::vector<std::pair<int, point_t>>>
get_implicit_graph(const map_t &map, const std::unordered_map<point_t, std::string> &portals) {
std::unordered_map<std::string, point_t> half_links;
std::unordered_map<point_t, std::vector<std::pair<int, point_t>>> graph;
for (auto &entry : portals) {
if (auto it = half_links.find(entry.second); it != half_links.end()) {
// Connect up the portals
graph[it->second].emplace_back(1, entry.first);
graph[entry.first].emplace_back(1, it->second);
} else {
half_links[entry.second] = entry.first;
}
// Do a BFS from the node to see what we can reach.
std::deque<std::pair<int, point_t>> todo{{0, entry.first}};
std::unordered_set<point_t> visited{entry.first};
while (!todo.empty()) {
const auto[dist, pos] = todo.front();
todo.pop_front();
for (auto &direction : DIRECTIONS) {
auto next_pos = pos + direction;
if (map[next_pos[1]][next_pos[0]] != '.' || visited.count(next_pos)) {
continue;
}
if (portals.count(next_pos)) {
graph[entry.first].emplace_back(dist + 1, next_pos);
}
todo.emplace_back(dist + 1, next_pos);
visited.insert(next_pos);
}
}
}
return graph;
}
}
void aoc2019::day20_part1(std::istream &input, std::ostream &output) {
const auto map = read_map(input);
const auto portals = get_portals(map);
const auto starting_point = std::find_if(portals.begin(), portals.end(), [](auto &x) {
return x.second == "AA";
})->first;
auto graph = get_implicit_graph(map, portals);
std::unordered_set<point_t> visited;
std::priority_queue<std::pair<int, point_t>, std::vector<std::pair<int, point_t>>, std::greater<>> todo;
todo.emplace(0, starting_point);
while (!todo.empty()) {
const auto[dist, pos] = todo.top();
todo.pop();
if (visited.count(pos)) {
continue;
}
visited.insert(pos);
if (portals.at(pos) == "ZZ") {
output << dist << std::endl;
return;
}
for (auto &edge : graph[pos]) {
if (visited.count(edge.second)) {
continue;
}
todo.emplace(dist + edge.first, edge.second);
}
}
throw std::domain_error("No valid route.");
}
void aoc2019::day20_part2(std::istream &input, std::ostream &output) {
const auto map = read_map(input);
const auto portals = get_portals(map);
using state_t = std::pair<int, point_t>;
const auto starting_point = std::find_if(portals.begin(), portals.end(), [](auto &x) {
return x.second == "AA";
})->first;
auto graph = get_implicit_graph(map, portals);
std::set<state_t> visited;
std::priority_queue<std::tuple<int, int, point_t>, std::vector<std::tuple<int, int, point_t>>, std::greater<>> todo;
todo.emplace(0, 0, starting_point);
const int outer_x_min = 2;
const int outer_x_max = map[0].size() - 3;
const int outer_y_min = 2;
const int outer_y_max = map.size() - 3;
while (!todo.empty()) {
const auto[dist, level, pos] = todo.top();
todo.pop();
if (visited.count({level, pos})) {
continue;
}
visited.emplace(level, pos);
if (level == 0 && portals.at(pos) == "ZZ") {
output << dist << std::endl;
return;
}
for (auto &edge : graph[pos]) {
int mod = 0;
if (edge.first == 1) {
// Taking a portal, determine which level we're going to
if (pos[0] == outer_x_max || pos[0] == outer_x_min || pos[1] == outer_y_max || pos[1] == outer_y_min) {
mod = -1;
} else {
mod = 1;
}
}
if (level + mod < 0 || visited.count({level + mod, edge.second})) {
continue;
}
todo.emplace(dist + edge.first, level + mod, edge.second);
}
}
throw std::domain_error("No valid route.");
}

View File

@@ -1,47 +0,0 @@
#include <iostream>
#include "days.hpp"
#include "utils.hpp"
namespace {
void solve(std::istream &input, std::string_view program, std::ostream &output) {
aoc2019::IntCodeComputer computer(input);
std::deque<std::int64_t> output_buffer;
computer.connectOutput(output_buffer);
computer.run();
output_buffer.clear();
computer.sendInputs(program);
computer.run();
if (output_buffer.back() < 127) {
for (char c : output_buffer) {
output << c;
}
} else {
output << output_buffer.back() << std::endl;
}
}
}
void aoc2019::day21_part1(std::istream &input, std::ostream &output) {
std::string_view program = "OR A J\n" // Check if any of the next 3 places is a hole
"AND B J\n"
"AND C J\n"
"NOT J J\n"
"AND D J\n" // Jump if the landing space is clear
"WALK\n";
solve(input, program, output);
}
void aoc2019::day21_part2(std::istream &input, std::ostream &output) {
std::string_view program = "NOT H J\n" // If you can safely jump twice
"OR C J\n" // And either of the next 3 places contains a hole
"AND A J\n"
"AND B J\n"
"NOT J J\n"
"AND D J\n" // And we can land our first jump, then jump.
"RUN\n";
solve(input, program, output);
}

View File

@@ -1,149 +0,0 @@
#include <iostream>
#include <cassert>
#include "days.hpp"
#include "utils.hpp"
namespace {
enum class Operation {
Stack,
Deal,
Cut
};
using Move = std::pair<Operation, int>;
std::vector<Move> read_moves(std::istream &input) {
std::string buffer;
std::vector<Move> moves;
while (std::getline(input, buffer)) {
std::string_view line = buffer;
if (!line.find("deal into new stack")) {
moves.emplace_back(Operation::Stack, 0);
} else if (!line.find("deal with increment ")) {
int new_increment;
aoc2019::from_chars(line.substr(20), new_increment);
moves.emplace_back(Operation::Deal, new_increment);
} else {
// cut
int new_offset;
aoc2019::from_chars(line.substr(4), new_offset);
moves.emplace_back(Operation::Cut, new_offset);
}
}
return moves;
}
constexpr std::int64_t mmi(std::int64_t a, std::int64_t n) {
std::int64_t t = 0, newt = 1, r = n, newr = a;
while (newr != 0) {
auto q = r / newr;
// Poor man's simultaneous assignment
std::tie(t, newt) = std::make_tuple(newt, t - q * newt);
std::tie(r, newr) = std::make_tuple(newr, r - q * newr);
}
if (r > 1) {
throw std::invalid_argument("Not invertible.");
}
if (t < 0) t += n;
assert((t * a) % n == 1);
return t;
}
constexpr std::pair<std::int64_t, std::int64_t> pow(std::int64_t a, std::int64_t b, std::int64_t n, const std::int64_t M) {
__int128 ra = 0, rb = 0;
while (n > 0) {
if (n % 2) {
ra = (ra + a) % M;
rb = (rb + b) % M;
}
// f(x) = ax + b
// f(f(x)) = a(ax + b) + b
// = aax + ab + b
__int128 na = a * (__int128) a;
__int128 nb = b * (__int128) a + b;
a = na % M;
b = nb % M;
n /= 2;
}
return {ra, rb};
}
}
void aoc2019::day22_part1(std::istream &input, std::ostream &output) {
constexpr int DECK_SIZE = 10007;
int pos = 2019;
for (auto move : read_moves(input)) {
int argument = move.second;
switch (move.first) {
case Operation::Stack:
pos = DECK_SIZE - 1 - pos;
break;
case Operation::Deal:
pos = pos * argument % DECK_SIZE;
break;
case Operation::Cut:
pos = (pos - argument) % DECK_SIZE;
if (pos < 0) pos += DECK_SIZE;
break;
}
}
output << pos << std::endl;
}
void aoc2019::day22_part2(std::istream &input, std::ostream &output) {
constexpr std::int64_t DECK_SIZE = 119315717514047;
constexpr std::int64_t SHUFFLES = 101741582076661;
assert(mmi(3, 11) == 4);
std::int64_t a = 1, b = 0;
for (auto move : read_moves(input)) {
std::int64_t argument = move.second;
switch (move.first) {
case Operation::Stack:
a = -a;
b = DECK_SIZE - b - 1;
break;
case Operation::Cut:
b = (b + argument) % DECK_SIZE;
break;
case Operation::Deal:
__int128 inv = mmi(argument, DECK_SIZE);
a = (a * inv) % DECK_SIZE;
b = (b * inv) % DECK_SIZE;
break;
}
}
const auto[ra, rb] = pow(a, b, SHUFFLES, DECK_SIZE);
output << ra << ',' << rb << std::endl;
auto result = (2020 * ra + rb) % DECK_SIZE;
if (result < 0) {
result += DECK_SIZE;
}
output << result << std::endl;
}

View File

@@ -1,10 +0,0 @@
#include <iostream>
#include "days.hpp"
void aoc2019::day23_part1(std::istream &input, std::ostream &output) {
output << "Not implemented\n";
}
void aoc2019::day23_part2(std::istream &input, std::ostream &output) {
output << "Not implemented\n";
}

View File

@@ -1,188 +0,0 @@
#include <iostream>
#include <vector>
#include <algorithm>
#include <set>
#include <numeric>
#include "days.hpp"
namespace {
using field_t = std::array<std::array<bool, 5>, 5>;
constexpr int EDGE = 4;
constexpr int MIDPOINT = 2;
field_t read_input(std::istream &input) {
std::string buffer;
field_t map;
int y = 0;
while (std::getline(input, buffer)) {
auto &row = map[y++];
std::transform(buffer.begin(), buffer.end(), row.begin(), [](char c) { return c == '#'; });
}
return map;
}
void next_gen(const field_t &source, field_t &sink) {
for (int y = 0; y < source.size(); ++y) {
for (int x = 0; x < source[y].size(); ++x) {
int neighbours = source[y][x] ? -1 : 0;
for (int dy = -1; dy <= 1; ++dy) {
if (dy + y < 0 || dy + y >= source.size()) {
continue;
}
for (int dx = -1; dx <= 1; ++dx) {
if (dx + x < 0 || dx + x >= source[y].size() || dx * dy) {
continue;
}
neighbours += source[y + dy][x + dx];
}
}
sink[y][x] = neighbours == 1 || (!source[y][x] && neighbours == 2);
}
}
}
int num_bees(const field_t &field) {
int total = 0;
for (auto &row : field) {
total += std::count(row.begin(), row.end(), true);
}
return total;
}
std::unordered_map<int, field_t> advance(const std::unordered_map<int, field_t> &state) {
const auto dimension_range = std::minmax_element(state.begin(), state.end());
const auto min = dimension_range.first->first - 1;
const auto max = dimension_range.second->first + 1;
std::unordered_map<int, field_t> next_gen;
auto has_bee = [&state](int dimension, int x, int y) {
if (auto it = state.find(dimension); it != state.end()) {
return it->second[y][x];
}
return false;
};
for (int dimension = min; dimension <= max; ++dimension) {
field_t field{};
if (auto it = state.find(dimension); it != state.end()) {
field = it->second;
}
auto get_neighbours = [has_bee,dimension](int x, int y) {
int neighbours = 0;
// Cell above
if (y == 0) {
neighbours += has_bee(dimension + 1, MIDPOINT, 1);
} else if (y == 3 && x == MIDPOINT) {
for (int sx = 0; sx < 5; ++sx) {
neighbours += has_bee(dimension - 1, sx, EDGE);
}
} else {
neighbours += has_bee(dimension, x, y - 1);
}
// Cell below
if (y == EDGE) {
neighbours += has_bee(dimension + 1, MIDPOINT, 3);
} else if (y == 1 && x == MIDPOINT) {
for (int sx = 0; sx < 5; ++sx) {
neighbours += has_bee(dimension - 1, sx, 0);
}
} else {
neighbours += has_bee(dimension, x, y + 1);
}
// Cell left
if (x == 0) {
neighbours += has_bee(dimension + 1, 1, 2);
} else if (x == 3 && y == MIDPOINT) {
for (int sy = 0; sy < 5; ++sy) {
neighbours += has_bee(dimension - 1, EDGE, sy);
}
} else {
neighbours += has_bee(dimension, x - 1, y);
}
// Cell right
if (x == EDGE) {
neighbours += has_bee(dimension + 1, 3, MIDPOINT);
} else if (x == 1 && y == MIDPOINT) {
for (int sy = 0; sy < 5; ++sy) {
neighbours += has_bee(dimension - 1, 0, sy);
}
} else {
neighbours += has_bee(dimension, x + 1, y);
}
return neighbours;
};
for (int y = 0; y < 5; ++y) {
for (int x = 0; x < 5; ++x) {
auto neighbours = get_neighbours(x, y);
field[y][x] = neighbours == 1 || (neighbours == 2 && !field[y][x]);
}
}
// Don't evolve the midpoint.
field[2][2] = false;
if (num_bees(field) || (dimension != min && dimension != max)) {
next_gen[dimension] = field;
}
}
return next_gen;
}
}
void aoc2019::day24_part1(std::istream &input, std::ostream &output) {
auto map = read_input(input);
auto copy = map;
std::set<field_t> seen;
do {
seen.insert(map);
next_gen(map, copy);
std::swap(map, copy);
} while (!seen.count(map));
unsigned int pow = 1;
unsigned int diversity = 0;
for (auto &row : map) {
for (auto b : row) {
if (b) {
diversity += pow;
}
pow <<= 1u;
}
}
output << diversity << std::endl;
}
void aoc2019::day24_part2(std::istream &input, std::ostream &output) {
std::unordered_map<int, field_t> fields;
fields[0] = read_input(input);
for (int gen = 0; gen < 200; ++gen) {
fields = advance(fields);
}
int total = std::accumulate(fields.begin(), fields.end(), 0, [](auto cur, const auto &it) {
return cur + num_bees(it.second);
});
output << total << std::endl;
}

View File

@@ -1,10 +0,0 @@
#include <iostream>
#include "days.hpp"
void aoc2019::day25_part1(std::istream &input, std::ostream &output) {
output << "Not implemented\n";
}
void aoc2019::day25_part2(std::istream &input, std::ostream &output) {
output << "Not implemented\n";
}

View File

@@ -1,57 +0,0 @@
#pragma once
#include <iosfwd>
namespace aoc2019 {
// Declarations of all implemented days.
void day01_part1(std::istream &input, std::ostream &output);
void day01_part2(std::istream &input, std::ostream &output);
void day02_part1(std::istream &input, std::ostream &output);
void day02_part2(std::istream &input, std::ostream &output);
void day03_part1(std::istream &input, std::ostream &output);
void day03_part2(std::istream &input, std::ostream &output);
void day04_part1(std::istream &input, std::ostream &output);
void day04_part2(std::istream &input, std::ostream &output);
void day05_part1(std::istream &input, std::ostream &output);
void day05_part2(std::istream &input, std::ostream &output);
void day06_part1(std::istream &input, std::ostream &output);
void day06_part2(std::istream &input, std::ostream &output);
void day07_part1(std::istream &input, std::ostream &output);
void day07_part2(std::istream &input, std::ostream &output);
void day08_part1(std::istream &input, std::ostream &output);
void day08_part2(std::istream &input, std::ostream &output);
void day09_part1(std::istream &input, std::ostream &output);
void day09_part2(std::istream &input, std::ostream &output);
void day10_part1(std::istream &input, std::ostream &output);
void day10_part2(std::istream &input, std::ostream &output);
void day11_part1(std::istream &input, std::ostream &output);
void day11_part2(std::istream &input, std::ostream &output);
void day12_part1(std::istream &input, std::ostream &output);
void day12_part2(std::istream &input, std::ostream &output);
void day13_part1(std::istream &input, std::ostream &output);
void day13_part2(std::istream &input, std::ostream &output);
void day14_part1(std::istream &input, std::ostream &output);
void day14_part2(std::istream &input, std::ostream &output);
void day15_part1(std::istream &input, std::ostream &output);
void day15_part2(std::istream &input, std::ostream &output);
void day16_part1(std::istream &input, std::ostream &output);
void day16_part2(std::istream &input, std::ostream &output);
void day17_part1(std::istream &input, std::ostream &output);
void day17_part2(std::istream &input, std::ostream &output);
void day18_part1(std::istream &input, std::ostream &output);
void day18_part2(std::istream &input, std::ostream &output);
void day19_part1(std::istream &input, std::ostream &output);
void day19_part2(std::istream &input, std::ostream &output);
void day20_part1(std::istream &input, std::ostream &output);
void day20_part2(std::istream &input, std::ostream &output);
void day21_part1(std::istream &input, std::ostream &output);
void day21_part2(std::istream &input, std::ostream &output);
void day22_part1(std::istream &input, std::ostream &output);
void day22_part2(std::istream &input, std::ostream &output);
void day23_part1(std::istream &input, std::ostream &output);
void day23_part2(std::istream &input, std::ostream &output);
void day24_part1(std::istream &input, std::ostream &output);
void day24_part2(std::istream &input, std::ostream &output);
void day25_part1(std::istream &input, std::ostream &output);
void day25_part2(std::istream &input, std::ostream &output);
}

View File

@@ -1,35 +0,0 @@
#include <array>
#include "days.hpp"
#include "implementations.hpp"
constexpr const std::array<std::array<aoc2019::solution_t, 2>, 25> SOLUTIONS = {{
{aoc2019::day01_part1, aoc2019::day01_part2},
{aoc2019::day02_part1, aoc2019::day02_part2},
{aoc2019::day03_part1, aoc2019::day03_part2},
{aoc2019::day04_part1, aoc2019::day04_part2},
{aoc2019::day05_part1, aoc2019::day05_part2},
{aoc2019::day06_part1, aoc2019::day06_part2},
{aoc2019::day07_part1, aoc2019::day07_part2},
{aoc2019::day08_part1, aoc2019::day08_part2},
{aoc2019::day09_part1, aoc2019::day09_part2},
{aoc2019::day10_part1, aoc2019::day10_part2},
{aoc2019::day11_part1, aoc2019::day11_part2},
{aoc2019::day12_part1, aoc2019::day12_part2},
{aoc2019::day13_part1, aoc2019::day13_part2},
{aoc2019::day14_part1, aoc2019::day14_part2},
{aoc2019::day15_part1, aoc2019::day15_part2},
{aoc2019::day16_part1, aoc2019::day16_part2},
{aoc2019::day17_part1, aoc2019::day17_part2},
{aoc2019::day18_part1, aoc2019::day18_part2},
{aoc2019::day19_part1, aoc2019::day19_part2},
{aoc2019::day20_part1, aoc2019::day20_part2},
{aoc2019::day21_part1, aoc2019::day21_part2},
{aoc2019::day22_part1, aoc2019::day22_part2},
{aoc2019::day23_part1, aoc2019::day23_part2},
{aoc2019::day24_part1, aoc2019::day24_part2},
{aoc2019::day25_part1, aoc2019::day25_part2},
}};
aoc2019::solution_t aoc2019::get_implementation(int day, bool part2) {
return SOLUTIONS.at(day - 1).at((int) part2);
}

View File

@@ -1,9 +0,0 @@
#pragma once
#include <iosfwd>
namespace aoc2019 {
typedef void (*solution_t)(std::istream &, std::ostream &);
solution_t get_implementation(int day, bool part2 = false);
}

View File

@@ -1,78 +0,0 @@
#pragma once
#include <array>
#include <cstdlib>
#include "utils.hpp"
namespace aoc2019 {
template<class T, std::size_t L>
class Point : public std::array<T, L> {
public:
constexpr Point& operator +=(Point other) {
for (std::size_t i = 0; i < L; ++i) {
(*this)[i] += other[i];
}
return *this;
}
constexpr Point operator+(Point other) const {
auto result = *this;
result += other;
return result;
}
constexpr Point& operator -=(Point other) {
for (std::size_t i = 0; i < L; ++i) {
(*this)[i] -= other[i];
}
return *this;
}
constexpr Point operator-(Point other) const {
auto result = *this;
result -= other;
return result;
}
constexpr T l1() const {
T result = 0;
for (auto e : *this) {
result += std::abs(e);
}
return result;
}
};
template<typename ValueType, std::size_t N, typename Ignored>
std::pair<Point<ValueType, N>, Point<ValueType, N>> bounding_box(const std::unordered_map<Point<ValueType, N>, Ignored> &data) {
Point<ValueType, N> lower, upper;
std::fill(lower.begin(), lower.end(), std::numeric_limits<ValueType>::max());
std::fill(upper.begin(), upper.end(), std::numeric_limits<ValueType>::min());
for (auto &entry : data) {
for (int i = 0; i < N; ++i) {
lower[i] = std::min(entry.first[i], lower[i]);
upper[i] = std::max(entry.first[i], upper[i]);
}
}
return {lower, upper};
}
}
namespace std {
// Make point usable with unordered collections.
template<class T, std::size_t L> struct hash<aoc2019::Point<T, L>> {
size_t operator()(const aoc2019::Point<T, L> &o) const {
size_t seed = 0;
for (auto i : o) {
aoc2019::combine_hash(seed, i);
}
return seed;
}
};
}

View File

@@ -1,135 +0,0 @@
#include "implementations.hpp"
#include <charconv>
#include <chrono>
#include <iostream>
#include <string_view>
#include <optional>
#include <fstream>
struct AoCOptions {
aoc2019::solution_t implementation;
bool run_timer;
std::optional<std::ifstream> input_file;
};
static AoCOptions parse_options(const int argc, const char* argv[]) {
using namespace std::literals;
AoCOptions options{};
auto show_help = [argv] (int exit_status = 0) {
std::cerr << "Usage: " << argv[0] << " [--timer|-t] [--part2|-2] [--help|-h] DAY\n"
<< "\t--timer|-t: print execution time\n"
<< "\t--input ARG|-fARG: use given input file as puzzle input"
<< "\t--part2|-2: run part 2\n"
<< "\t --help|-h: show this message\n";
std::exit(exit_status);
};
int day = -1;
bool part2 = false;
// Here follows a manual implementation of getopt, since getopt doesn't work on windows…
for (int i = 1; i < argc; ++i) {
std::string_view arg(argv[i]);
if (arg[0] == '-') {
// Handle flag arguments
if (arg[1] != '-') {
// Shorthand flags
for (int j = 1; j < arg.size(); ++j) {
switch (arg[j]) {
case '2':
part2 = true;
break;
case 't':
options.run_timer = true;
break;
case 'h':
show_help();
break;
case 'f':
if (j == arg.size() - 1) {
if (i == argc - 1) {
std::cerr << "Option -f requires an argument.";
show_help(1);
} else {
options.input_file = std::ifstream(argv[i + 1]);
++i;
}
} else {
options.input_file = std::ifstream(std::string(arg.substr(j)));
j = arg.size();
}
break;
default:
std::cerr << "Unknown flag '" << arg[j] << "'.\n\n";
show_help(1);
}
}
} else {
// Handle long form versions
if (arg == "--timer"sv) {
part2 = true;
} else if (arg == "--timer"sv) {
options.run_timer = true;
} else if (arg == "--help"sv) {
show_help();
} else if (arg == "--input"sv) {
if (i == argc - 1) {
std::cerr << "Option -f requires an argument.";
show_help(1);
} else {
options.input_file = std::ifstream(argv[i + 1]);
++i;
}
} else {
show_help(1);
}
}
} else {
if (day != -1) {
// Double date specification, bail.
show_help(1);
}
// Try to parse the date number
if (auto res = std::from_chars(arg.data(), arg.data() + arg.size(), day); res.ec != std::errc()) {
auto error_code = std::make_error_code(res.ec);
std::cerr << error_code.message() << "\n\n";
show_help(1);
}
}
}
if (day == -1) {
std::cerr << "Argument DAY is required.\n\n";
show_help(1);
} else if (day < 1 || day > 25) {
std::cerr << "Invalid day. Valid range: [1, 25]\n";
show_help(1);
}
options.implementation = aoc2019::get_implementation(day, part2);
return options;
}
int main(int argc, const char *argv[]) {
auto options = parse_options(argc, argv);
if (options.implementation != nullptr) {
const auto start = std::chrono::high_resolution_clock::now();
options.implementation(options.input_file ? *options.input_file : std::cin, std::cout);
if (options.run_timer) {
const std::chrono::duration<double> duration = std::chrono::high_resolution_clock::now() - start;
std::cerr << "Time taken: " << duration.count() << "s\n";
}
return 0;
} else {
std::cerr << "Unimplemented.\n";
return 1;
}
}

View File

@@ -1,191 +0,0 @@
#include <iostream>
#include "utils.hpp"
std::string_view aoc2019::strtok(std::string_view &str, char token) {
auto next_delim = str.find(token);
auto next = str.substr(0, next_delim);
if (next_delim == std::string_view::npos) {
str = {};
} else {
str = str.substr(next_delim + 1);
}
return next;
}
std::deque<int64_t> aoc2019::run_intcode(std::vector<int64_t> program, std::deque<int64_t> inputs) {
std::deque<std::int64_t> outputs;
IntCodeComputer computer(std::move(program), std::move(inputs));
computer.connectOutput(outputs);
computer.run();
return outputs;
}
aoc2019::IntCodeComputer::value_t &aoc2019::IntCodeComputer::interpret_value(int pos) {
value_t immediate;
switch (pos) {
case 1:
immediate = program[ip] / 100 % 10;
break;
case 2:
immediate = program[ip] / 1000 % 10;
break;
case 3:
immediate = program[ip] / 10000 % 10;
break;
default:
throw std::out_of_range("Invalid position");
}
value_t index;
switch (immediate) {
case 0:
index = program[ip + pos];
break;
case 1:
index = ip + pos;
break;
case 2:
index = program[ip + pos] + relative;
break;
default:
throw std::out_of_range("Invalid mode");
}
if (program.size() <= index) {
program.resize(index + 1);
}
return program[index];
}
void aoc2019::IntCodeComputer::connectOutput(aoc2019::IntCodeComputer &computer) {
outputSink = &computer.inputs;
}
void aoc2019::IntCodeComputer::connectOutput(std::deque<value_t> &sink) {
outputSink = &sink;
}
bool aoc2019::IntCodeComputer::isTerminated() const {
return halted;
}
const std::deque<aoc2019::IntCodeComputer::value_t> &aoc2019::IntCodeComputer::currentInputs() const {
return inputs;
}
std::vector<aoc2019::IntCodeComputer::value_t> aoc2019::IntCodeComputer::read_intcode(std::istream &input) {
std::vector<value_t> program;
for (value_t current; input >> current; input.ignore()) {
program.push_back(current);
}
return program;
}
void aoc2019::IntCodeComputer::run() {
while (ip < program.size()) {
switch (program[ip] % 100) {
case 1:
interpret_value(3) = interpret_value(1) + interpret_value(2);
ip += 4;
break;
case 2:
interpret_value(3) = interpret_value(1) * interpret_value(2);
ip += 4;
break;
case 3:
if (inputs.empty()) {
return;
}
interpret_value(1) = inputs.front();
inputs.pop_front();
ip += 2;
break;
case 4:
outputSink->push_back(interpret_value(1));
ip += 2;
break;
case 5: // Jump if non-zero
if (interpret_value(1)) {
ip = interpret_value(2);
} else {
ip += 3;
}
break;
case 6: // Jump if zero
if (!interpret_value(1)) {
ip = interpret_value(2);
} else {
ip += 3;
}
break;
case 7: // less than
interpret_value(3) = interpret_value(1) < interpret_value(2);
ip += 4;
break;
case 8: // equality
interpret_value(3) = interpret_value(1) == interpret_value(2) ? 1 : 0;
ip += 4;
break;
case 9:
relative += interpret_value(1);
ip += 2;
break;
case 99:
halted = true;
return;
default:
char buffer[30];
std::snprintf(buffer, sizeof(buffer), "Invalid opcode: %d", program[ip]);
throw std::domain_error(buffer);
}
}
}
aoc2019::IntCodeComputer::IntCodeComputer(std::vector<value_t> program, std::deque<value_t> initial_inputs) :
program{std::move(program)}, inputs{std::move(initial_inputs)} {
}
aoc2019::IntCodeComputer::IntCodeComputer(std::istream &program_stream, std::deque<value_t> initial_inputs) :
program(read_intcode(program_stream)), inputs(std::move(initial_inputs)) {
}
void aoc2019::IntCodeComputer::sendInput(aoc2019::IntCodeComputer::value_t input) {
inputs.push_back(input);
}
aoc2019::IntCodeComputer::value_t &aoc2019::IntCodeComputer::operator[](std::size_t index) {
return program[index];
}
const aoc2019::IntCodeComputer::value_t &aoc2019::IntCodeComputer::operator[](std::size_t index) const {
return program[index];
}
void aoc2019::IntCodeComputer::sendInputs(std::string_view str) {
for (char c : str) {
sendInput(c);
}
}

View File

@@ -1,123 +0,0 @@
#pragma once
#include <charconv>
#include <deque>
#include <functional>
#include <iosfwd>
#include <string_view>
#include <vector>
namespace aoc2019 {
template<typename T>
inline std::from_chars_result from_chars(std::string_view str, T &value) {
return std::from_chars(str.data(), str.data() + str.size(), value);
}
template<typename T>
void combine_hash(std::size_t &seed, const T &o) {
// Algorithm taken from boost::combine_hash.
std::hash<T> hash{};
seed ^= hash(o) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
template<typename ValueType, typename OutputIt>
std::istream &read_line_numbers_and_garbage(std::istream &input, OutputIt output) {
ValueType v;
char c;
while (input && (c = input.peek()) != '\n') {
if (c == '-' || std::isdigit(c)) {
input >> v;
*output = v;
++output;
} else {
input.ignore();
}
}
input.get();
return input;
}
std::string_view strtok(std::string_view &str, char token = ',');
std::deque<int64_t> run_intcode(std::vector<std::int64_t> program, std::deque<std::int64_t> inputs = {});
template<class Node>
std::vector<Node> topological_sort(const std::unordered_map<Node, std::vector<Node>> &edge_list) {
std::unordered_map<Node, int> incoming_edges;
for (auto &entry : edge_list) {
// Ensure entry for parent exist
incoming_edges[entry.first] += 0;
for (auto &node : entry.second) {
incoming_edges[node]++;
}
}
std::vector<Node> order;
std::deque<Node> childless;
for (auto &entry : incoming_edges) {
if (!entry.second) {
childless.push_back(entry.first);
}
}
while (!childless.empty()) {
auto current = childless.front();
childless.pop_front();
order.emplace_back(current);
if (auto it = edge_list.find(current); it != edge_list.end()) {
for (const auto &parent : it->second) {
if (--incoming_edges[parent] == 0) {
childless.push_back(parent);
}
}
}
}
if (order.size() != incoming_edges.size()) {
throw std::domain_error("Not a DAG.");
}
return order;
}
class IntCodeComputer {
public:
typedef std::int64_t value_t;
explicit IntCodeComputer(std::vector<value_t> program, std::deque<value_t> initial_inputs = {});
explicit IntCodeComputer(std::istream &program_stream, std::deque<value_t> initial_inputs = {});
void run();
void connectOutput(IntCodeComputer &computer);
void connectOutput(std::deque<value_t> &sink);
void sendInput(value_t input);
void sendInputs(std::string_view str);
[[nodiscard]] bool isTerminated() const;
[[nodiscard]] const std::deque<value_t> &currentInputs() const;
value_t &operator[](std::size_t index);
const value_t &operator[](std::size_t index) const;
static std::vector<value_t> read_intcode(std::istream &input);
private:
std::vector<value_t> program;
std::deque<value_t> inputs = {};
std::deque<value_t> *outputSink = nullptr;
int ip = 0;
int relative = 0;
bool halted = false;
[[nodiscard]] value_t &interpret_value(int pos);
};
}

0
2019/tests/__init__.py Normal file
View File

View File

@@ -1 +0,0 @@

View File

@@ -1 +0,0 @@
100756

View File

@@ -1 +0,0 @@
33583

View File

@@ -1,2 +0,0 @@
R75,D30,R83,U83,L12,D49,R71,U7,L72
U62,R66,U55,R34,D71,R55,D58,R83

View File

@@ -1 +0,0 @@
159

View File

@@ -1,2 +0,0 @@
R98,U47,R26,D63,R33,U87,L62,D20,R33,U53,R51
U98,R91,D20,R16,D67,R40,U7,R15,U6,R7

View File

@@ -1 +0,0 @@
135

View File

@@ -1,2 +0,0 @@
R8,U5,L5,D3
U7,R6,D4,L4

View File

@@ -1 +0,0 @@
6

View File

@@ -1 +0,0 @@
03-1-1.in

View File

@@ -1 +0,0 @@
610

View File

@@ -1 +0,0 @@
03-1-2.in

View File

@@ -1 +0,0 @@
410

View File

@@ -1 +0,0 @@
03-1-3.in

View File

@@ -1 +0,0 @@
30

View File

@@ -1,11 +0,0 @@
COM)B
B)C
C)D
D)E
E)F
B)G
G)H
D)I
E)J
J)K
K)L

View File

@@ -1 +0,0 @@
42

View File

@@ -1,13 +0,0 @@
COM)B
B)C
C)D
D)E
E)F
B)G
G)H
D)I
E)J
J)K
K)L
K)YOU
I)SAN

View File

@@ -1 +0,0 @@
4

View File

@@ -1 +0,0 @@
3,15,3,16,1002,16,10,16,1,16,15,15,4,15,99,0,0

View File

@@ -1 +0,0 @@
43210

View File

@@ -1,2 +0,0 @@
3,23,3,24,1002,24,10,24,1002,23,-1,23,
101,5,23,23,1,24,23,23,4,23,99,0,0

View File

@@ -1 +0,0 @@
54321

View File

@@ -1,2 +0,0 @@
3,31,3,32,1002,32,10,32,1001,31,-2,31,1007,31,0,33,
1002,33,7,33,1,33,31,31,1,32,31,31,4,31,99,0,0,0

View File

@@ -1 +0,0 @@
65210

View File

@@ -1,2 +0,0 @@
3,26,1001,26,-4,26,3,27,1002,27,2,27,1,27,26,
27,4,27,1001,28,-1,28,1005,28,6,99,0,0,5

View File

@@ -1 +0,0 @@
139629729

View File

@@ -1,3 +0,0 @@
3,52,1001,52,-5,52,3,53,1,52,56,54,1007,54,5,55,1005,55,26,1001,54,
-5,54,1105,1,12,1,53,54,53,1008,54,0,55,1001,55,1,55,2,53,55,53,4,
53,1001,56,-1,56,1005,56,6,99,0,0,0,0,10

View File

@@ -1 +0,0 @@
18216

View File

@@ -1,5 +0,0 @@
.#..#
.....
#####
....#
...##

View File

@@ -1 +0,0 @@
8

View File

@@ -1,10 +0,0 @@
......#.#.
#..#.#....
..#######.
.#.#.###..
.#..#.....
..#....#.#
#..#....#.
.##.#..###
##...#..#.
.#....####

View File

@@ -1 +0,0 @@
33

View File

@@ -1,10 +0,0 @@
#.#...#.#.
.###....#.
.#....#...
##.#.#.#.#
....#.#.#.
.##..###.#
..#...##..
..##....##
......#...
.####.###.

Some files were not shown because too many files have changed in this diff Show More