38 Commits

Author SHA1 Message Date
4e3c9c75c5 Merge pull request #2 from bertptrs/rewrite-2019-python 2021-10-18 22:34:35 +02:00
583b590a6c Remove last C++ remnant. 2021-10-18 21:50:22 +02:00
ef302bbeb7 Trivial reimplementation day 21 2021-10-18 19:45:36 +02:00
5791760412 More efficiently pick items 2021-10-18 16:00:32 +02:00
e66f3df949 Implement brute force for 2019 day 25 2021-10-18 15:51:44 +02:00
71f54bac6b Add minimal day 25 runner 2021-10-16 17:46:55 +02:00
dbf6b2eb17 Add input day 23 2021-07-03 17:59:58 +02:00
05e2a4f3a7 intcode: make execute current not protected 2021-07-03 17:59:43 +02:00
ca43475a44 Implement day 23 2021-07-03 17:57:40 +02:00
3f8c9505c3 Fix numpy deprecation 2021-07-03 15:26:38 +02:00
befa2eefaa Replace Travis with Github Actions 2021-07-03 15:20:52 +02:00
22091fc8c3 Implement day 22 part 2 2021-07-03 15:05:49 +02:00
abc742e2ad Implement day 22 part 1 2021-07-03 14:15:11 +02:00
a43f260e1b Implement day 17 part 1 2021-02-09 20:20:17 +01:00
beae045f55 Implement 2019 day 16 part 2 2021-02-02 19:57:24 +01:00
2d561eab7d Implement 2019 day 16 part 1 2021-02-02 19:12:10 +01:00
165159cba9 Implement 2019 day 15 2021-02-01 22:39:09 +01:00
eff22abf8a Fix mypy issues 2021-01-30 10:09:19 +01:00
43c063cef9 Implement 2019 day 14 2021-01-30 10:08:16 +01:00
c3671ec177 Implement 2019 day 13 part 2 2021-01-29 20:42:54 +01:00
c8c616dffc Implement 2019 day 13 part 1 2021-01-29 20:26:12 +01:00
f94661d7a8 Implement 2019 day 12 2021-01-25 20:55:39 +01:00
2c767d5996 Implement 2019 day 11 2021-01-24 17:35:42 +01:00
dd039e9276 Super messy 2019 day 10
I accidentally inverted my y-axis and I'm not fixing it.
2021-01-24 13:54:48 +01:00
2907726363 Don't build on Python Nightly
Python nightly doesn't support Numpy, or the other way around, but
either way it doesn't work and causes build failures.
2021-01-24 10:23:20 +01:00
091c125f42 Fix day 8 to paint front-to-back 2021-01-24 10:22:37 +01:00
b65c805891 Implement 2019 day 9 2021-01-24 10:16:14 +01:00
4418292dc4 Implement 2019 day 8 2021-01-23 22:34:58 +01:00
18351f93eb Implement 2019 day 7 2021-01-23 21:33:10 +01:00
fe639f14b3 Implement 2019 day 6 2021-01-23 21:08:27 +01:00
07098ab691 Implement 2019 day 5 part 2 2021-01-23 20:27:30 +01:00
b18945c44d Implement 2019 day 5 part 1 2021-01-23 19:57:48 +01:00
ad3029759a Implement 2019 day 4 2021-01-23 17:52:59 +01:00
7a292b026d Implement 2019 day 3 2021-01-23 17:30:37 +01:00
930d86404d Implement 2019 day 2
Start of the intcode madness
2021-01-23 17:05:10 +01:00
07e869c497 Set up travis for use with Python 2021-01-23 16:15:06 +01:00
07db73aa3e Implement 2019 day 1 with tests in Python 2021-01-23 15:52:12 +01:00
69de955158 Remove C++ solutions 2021-01-23 15:00:04 +01:00
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
This project contains my implementations for Advent of Code 2019. The
goal is to create reasonably fast C++ implementations in readable and
ergonomic C++. At the end of the contest, I will probably do a write-
up of some sorts.
This is a quick-and-dirty implementation of all 2019 problems implemented in
Python because I got fed up with C++ and really couldn't stand the missing
stars on the [events page](https://adventofcode.com/2020/events).
## How to compile
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.
I'll try to incorporate unit tests and all because it's just nice programming,
but this edition has decidedly less polish than my other attempts.

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