125 Commits

Author SHA1 Message Date
81f244bde9 Implementation day 23 2021-12-31 17:44:31 +01:00
09b590e927 Update southbound comment 2021-12-29 15:39:20 +01:00
9dacb4c1ae Perform updates in-place 2021-12-29 15:29:21 +01:00
07e03c1630 Implement day 25 2021-12-29 15:09:43 +01:00
3accf9845d Better code reuse, almost generic over dimensions 2021-12-29 14:19:58 +01:00
fd26f58e25 Implement day 22 part 2 (and 1 again) 2021-12-29 14:07:52 +01:00
b2f9898714 Reduce code duplication 2021-12-26 12:32:07 +01:00
d757c389f0 Replace inefficient recursion with iteration 2021-12-26 11:24:45 +01:00
fd561a3e9d Clippy suggestions 2021-12-22 21:16:27 +01:00
2fcdc6b8d2 Brute force day 22 part 1 2021-12-22 21:12:15 +01:00
8a3f0f843c Finally discovered that pos != new_pos 2021-12-22 20:36:58 +01:00
23b5c39838 Add inputs day 21 2021-12-22 20:04:29 +01:00
452f6e5f14 Implement fast day 21 part 1 2021-12-21 09:35:50 +01:00
61fb240622 Avoid 2/3 hashmap lookups 2021-12-20 18:24:37 +01:00
aee25057d6 Avoid allocations 2021-12-20 18:14:36 +01:00
a98332894f Brute force implementation day 20 2021-12-20 18:08:48 +01:00
09e012c082 Add input files day 18 2021-12-19 22:55:11 +01:00
944d3e644a Filter out the worst repetition 2021-12-19 18:38:30 +01:00
d56f4ae8f8 Implement part 2 with the same brute force 2021-12-19 18:33:24 +01:00
53ca8d0043 Implement day 19 part 1
By brute force and lots of it
2021-12-19 18:16:56 +01:00
6506af879a Simplify y-hit iterator 2021-12-18 18:15:11 +01:00
101ebee505 Use dedicated iterator instead of range 2021-12-18 18:15:11 +01:00
cc81a7012b Use math instead of binary search 2021-12-18 17:21:43 +01:00
9c299f140c Tighter bounds for the range of y 2021-12-18 15:01:47 +01:00
ba1b7b693e Use reusable parser wrapper more 2021-12-18 14:54:05 +01:00
7d331f9131 Implement day 18 2021 2021-12-18 14:46:27 +01:00
dfd8b2b985 Partially smart, partially brute-force day 17 2021-12-17 10:04:36 +01:00
0f6167e90f Merge common parts of parsers 2021-12-16 19:26:10 +01:00
3e2a3f5206 Avoid allocating and tracking bits manually 2021-12-16 19:12:34 +01:00
8cc2245492 Optimize hex parser 2021-12-16 18:46:19 +01:00
fb167ef899 Implementation day 16
Not clean but it works.
2021-12-16 09:55:53 +01:00
4240f8fc8c Avoid queueing worse routes 2021-12-15 18:40:20 +01:00
9a4ac427e0 Use Dijkstra instead of A*
The available distance heuristic doesn't save enough steps to offset its
overhead.
2021-12-15 18:29:08 +01:00
c3929a56d8 Slow implementation for day 15 2021-12-15 07:54:14 +01:00
9e37026f30 Remove unnecessary indexing 2021-12-14 19:54:45 +01:00
a926243f0d Improve "winning" logic
You don't need to check every possible win condition, just the one you
touched.
2021-12-14 19:51:57 +01:00
a35ae82548 Replace unnecessary HashMap and allocations 2021-12-14 19:39:00 +01:00
8a0b72f111 Implementation day 14 2021 2021-12-14 09:25:38 +01:00
b5866b2d8a Modify points in-place
Thanks to skius from libera##rust for the idea
2021-12-13 22:55:14 +01:00
554683bc64 Actually apply nom 2021-12-13 22:20:02 +01:00
1031a8cdbb Avoid allocating the final buffer twice 2021-12-13 21:40:00 +01:00
5c764f6627 Don't use silly hashsets 2021-12-13 21:34:48 +01:00
ffe8d27469 Clean up day 13 a little. 2021-12-13 08:52:57 +01:00
d471f170b3 Very dirty solution day 13 2021-12-13 08:50:00 +01:00
07cbf6cf53 Fix upcoming clippy warning 2021-12-12 15:56:05 +01:00
6e3252ce5a Add formal benchmarking code 2021-12-12 15:33:52 +01:00
0897e2e907 Implement day 12 part 2 2021-12-12 11:31:54 +01:00
4d1fdd9cc0 Implement day 12 part 1 2021-12-12 11:09:14 +01:00
84c160cf54 Simplify part 2 2021-12-11 17:20:22 +01:00
440d454911 Implement day 11 2021-12-11 13:56:10 +01:00
50cd6d8171 Implementation day 10 2021 2021-12-10 10:46:02 +01:00
dde9c0adbf Update to RC version of clap
Deriving a parser has become an opt-in feature so we need to enable
that.
2021-12-09 12:14:15 +01:00
e0e1bc26e8 Simplify implementation day 8
The second part doesn't actually need to start the search at the low
points; just iterating everything and keeping track of visited spaces is
enough.

Now that the iterator is only used in part 1, we inline the iterator to
remove some overhead from the code.
2021-12-09 12:02:07 +01:00
77ce31980b Implement day 9 2021-12-09 11:56:29 +01:00
8c78106846 Bit-optimize day 8
The state of a seven segment display can be stored in a byte after all.
Using NonZeroU8 makes the Options smaller too.
2021-12-08 13:52:08 +01:00
22f767e8df Implement day 08 2021 2021-12-08 13:31:33 +01:00
3434966ac2 Just use the median, obviously 2021-12-07 13:22:43 +01:00
c5f66fcc09 Avoid more allocations in day 7 part 1 2021-12-07 11:41:19 +01:00
c02f1e11c5 Convert recursion to iteration 2021-12-07 11:00:26 +01:00
d099614217 Improve part 2 with binary search 2021-12-07 10:40:34 +01:00
766ee91719 Brute-force day 7 part 2 2021-12-07 10:12:01 +01:00
2f3eb50a5b Implement day 7 part 1 2021-12-07 09:45:58 +01:00
b369f9d36a Implement day 6 2021 2021-12-06 09:21:00 +01:00
1433b0cdbe Implement day 5
Nom is really nice and fast, why did I write parsers manually before.
2021-12-05 11:31:14 +01:00
5e52da6e6b Use iterator instead of range 2021-12-04 11:57:01 +01:00
e50b812aed Day 4: more efficiently ignore completed cards 2021-12-04 11:40:40 +01:00
fdef10a78e Less awkward line length hack 2021-12-04 11:28:32 +01:00
392aefb32d Less allocations in day 3 part 2
By working with binary numbers as integers rather than byte strings, we
don't need to allocate a Vec for each of them, reducing us to just the
allocations in the outer Vec.
2021-12-04 11:13:54 +01:00
fb358be8f0 Implementation day 4 2021 2021-12-04 10:43:02 +01:00
612d3ecb6b Tricky solution day 03, could possibly be improved 2021-12-03 09:21:13 +01:00
d08a4e0e4e Merge day 1 solutions 2021-12-02 18:50:26 +01:00
ed844a997c Create reusable line reader 2021-12-02 18:27:48 +01:00
c9468ba139 Implementation 2021 day 2 2021-12-02 08:21:49 +01:00
938eda0d22 Rework day 1
Simplify part 2 a lot, by not actually computing the sums because they
do not matter, only the changes do. Also eliminate the allocation
overhead while parsing line-by-line input.

Fixes the existing clippy error because the offending line no longer
exists.
2021-12-01 20:35:27 +01:00
f413b08da6 Very quick implementation for day 1 2021-12-01 09:30:03 +01:00
10531e3422 Merge pull request #3 from bertptrs/prepare/2021 2021-12-01 09:08:43 +01:00
2e0a7ea81d Update READMEs for 2021. 2021-11-29 20:31:29 +01:00
2c64028978 Enable debug information for release 2021-11-28 17:13:27 +01:00
89159137fe Add other days 2021-11-28 17:12:26 +01:00
186d91d1b7 Use function pointers over dyn traits 2021-11-28 16:49:37 +01:00
c985ba8a1a Add CI for 2021 2021-11-22 18:51:59 +01:00
cece8439a7 Initial 2021 runner 2021-11-20 11:57:32 +01:00
5f98b62f21 2019 day 24 part 2 2021-10-29 08:30:21 +02:00
affaf6e96f 2019 day 24 part 1 2021-10-28 17:55:17 +02:00
a6996e5234 2019 day 19 part 2 2021-10-21 08:25:20 +02:00
a64eff96a4 More efficiently scan lines 2021-10-21 08:16:08 +02:00
7718fc59c6 2019 day 19 part 1 2021-10-21 08:05:22 +02:00
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
250 changed files with 15105 additions and 3692 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

46
.github/workflows/2021.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
on:
- push
name: Advent of Code 2021
jobs:
ci:
strategy:
matrix:
toolchain:
- stable
- beta
experimental: [false]
include:
- toolchain: nightly
experimental: true
name: Continuous Integration
runs-on: ubuntu-latest
continue-on-error: ${{ matrix.experimental }}
steps:
- uses: actions/checkout@v2
- name: Install toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: ${{ matrix.toolchain }}
override: true
components: rustfmt, clippy
- name: Build binaries
working-directory: 2021
run: >
cargo build --all-targets
- name: Run tests
working-directory: 2021
run: >
cargo test
- name: Run clippy
working-directory: 2021
run: >
cargo clippy -- --deny warnings

8
.gitignore vendored
View File

@@ -56,3 +56,11 @@ docs/_build/
# PyBuilder
target/
*.swp
# Rust lock
*.lock
# Performance data
perf.data
perf.data.old
flamegraph.svg

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

66
2019/aoc2019/day19.py Normal file
View File

@@ -0,0 +1,66 @@
import copy
import itertools
from collections import deque
from typing import TextIO, Tuple
from aoc2019.intcode import Computer, read_program
def query_position(x: int, y: int, computer: Computer) -> bool:
computer = copy.deepcopy(computer)
computer.send_input(x)
computer.send_input(y)
computer.run()
return computer.get_output() == 1
def find_line(y: int, x_min: int, x_max: int, computer: Computer) -> Tuple[int, int]:
# First find start of the line:
offset = 0
while not query_position(x_min, y, computer):
offset += 1
x_min += 1
x_max += offset
while query_position(x_max, y, computer):
x_max += 1
x_max -= 1
return x_min, x_max
def part1(data: TextIO) -> int:
computer = Computer(read_program(data))
x_min, x_max = (0, 0)
total = 0
for y in range(50):
x_min, x_max = find_line(y, x_min, x_max, computer)
total += min(x_max, 49) - min(x_min, 50) + 1
return total
def part2(data: TextIO) -> int:
computer = Computer(read_program(data))
x_min, x_max = (0, 0)
lines = deque()
for y in itertools.count():
x_min, x_max = find_line(y, x_min, x_max, computer)
lines.append((x_min, x_max))
if len(lines) == 100:
x_top_min, x_top_max = lines.popleft()
if x_top_max - x_min + 1 < 100:
continue
return x_min * 10000 + y - 99

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

138
2019/aoc2019/day24.py Normal file
View File

@@ -0,0 +1,138 @@
from typing import TextIO, Iterable, Tuple, List
def read_board(data: TextIO) -> Tuple[Tuple[bool]]:
return tuple(
tuple(c == '#' for c in line.strip())
for line in data
)
def flatten(it: Iterable[Iterable]) -> Iterable:
for item in it:
yield from item
def neighbours(board: Tuple[Tuple[bool]], x: int, y: int) -> int:
n = 0
if x > 0 and board[y][x - 1]:
n += 1
if x + 1 < len(board[0]) and board[y][x + 1]:
n += 1
if y > 0 and board[y - 1][x]:
n += 1
if y + 1 < len(board) and board[y + 1][x]:
n += 1
return n
def advance_board(board: Tuple[Tuple[bool]]) -> Tuple[Tuple[bool]]:
def create_row(y: int, row: Tuple[bool]):
new_row = []
for x, live in enumerate(row):
if live:
new_row.append(neighbours(board, x, y) == 1)
else:
new_row.append(neighbours(board, x, y) in [1, 2])
return tuple(new_row)
return tuple(create_row(y, row) for y, row in enumerate(board))
def neighbours2(board: List[Tuple[Tuple[bool]]], x: int, y: int, z: int) -> int:
existing = range(len(board))
if z in existing:
# Normal board count, minus the middle tile if applicable
n = neighbours(board[z], x, y) - board[z][2][2]
else:
n = 0
if z - 1 in existing:
if y == 2:
if x == 1:
n += sum(board[z - 1][iy][0] for iy in range(5))
elif x == 3:
n += sum(board[z - 1][iy][4] for iy in range(5))
elif x == 2:
if y == 1:
n += sum(board[z - 1][0])
elif y == 3:
n += sum(board[z - 1][4])
if z + 1 in existing:
if y == 0:
n += board[z + 1][1][2]
elif y == 4:
n += board[z + 1][3][2]
if x == 0:
n += board[z + 1][2][1]
elif x == 4:
n += board[z + 1][2][3]
return n
def advance_board2(board: List[Tuple[Tuple[bool]]]) -> List[Tuple[Tuple[bool]]]:
layers = []
for z in range(-1, len(board) + 1):
layer = []
for y in range(5):
row = []
for x in range(5):
if y == 2 and x == 2:
row.append(False)
continue
if z in range(len(board)):
live = board[z][y][x]
else:
live = False
if live:
row.append(neighbours2(board, x, y, z) == 1)
else:
row.append(neighbours2(board, x, y, z) in [1, 2])
layer.append(tuple(row))
layers.append(tuple(layer))
if sum(flatten(layers[0])) == 0:
layers = layers[1:]
if sum(flatten(layers[-1])) == 0:
layers = layers[:-1]
return layers
def part1(data: TextIO) -> int:
board = read_board(data)
seen = set(board)
while True:
board = advance_board(board)
if board in seen:
return sum(2 ** i for i, b in enumerate(flatten(board)) if b)
seen.add(board)
def part2(data: TextIO, rounds: int = 200) -> int:
board = [read_board(data)]
for _ in range(rounds):
board = advance_board2(board)
return sum(flatten(flatten(board)))

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 @@
.#..#
.....
#####
....#
...##

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