mirror of
https://github.com/bertptrs/adventofcode.git
synced 2025-12-25 21:00:31 +01:00
Compare commits
125 Commits
8a137deae4
...
2021-day23
| Author | SHA1 | Date | |
|---|---|---|---|
| 81f244bde9 | |||
| 09b590e927 | |||
| 9dacb4c1ae | |||
| 07e03c1630 | |||
| 3accf9845d | |||
| fd26f58e25 | |||
| b2f9898714 | |||
| d757c389f0 | |||
| fd561a3e9d | |||
| 2fcdc6b8d2 | |||
| 8a3f0f843c | |||
| 23b5c39838 | |||
| 452f6e5f14 | |||
| 61fb240622 | |||
| aee25057d6 | |||
| a98332894f | |||
| 09e012c082 | |||
| 944d3e644a | |||
| d56f4ae8f8 | |||
| 53ca8d0043 | |||
| 6506af879a | |||
| 101ebee505 | |||
| cc81a7012b | |||
| 9c299f140c | |||
| ba1b7b693e | |||
| 7d331f9131 | |||
| dfd8b2b985 | |||
| 0f6167e90f | |||
| 3e2a3f5206 | |||
| 8cc2245492 | |||
| fb167ef899 | |||
| 4240f8fc8c | |||
| 9a4ac427e0 | |||
| c3929a56d8 | |||
| 9e37026f30 | |||
| a926243f0d | |||
| a35ae82548 | |||
| 8a0b72f111 | |||
| b5866b2d8a | |||
| 554683bc64 | |||
| 1031a8cdbb | |||
| 5c764f6627 | |||
| ffe8d27469 | |||
| d471f170b3 | |||
| 07cbf6cf53 | |||
| 6e3252ce5a | |||
| 0897e2e907 | |||
| 4d1fdd9cc0 | |||
| 84c160cf54 | |||
| 440d454911 | |||
| 50cd6d8171 | |||
| dde9c0adbf | |||
| e0e1bc26e8 | |||
| 77ce31980b | |||
| 8c78106846 | |||
| 22f767e8df | |||
| 3434966ac2 | |||
| c5f66fcc09 | |||
| c02f1e11c5 | |||
| d099614217 | |||
| 766ee91719 | |||
| 2f3eb50a5b | |||
| b369f9d36a | |||
| 1433b0cdbe | |||
| 5e52da6e6b | |||
| e50b812aed | |||
| fdef10a78e | |||
| 392aefb32d | |||
| fb358be8f0 | |||
| 612d3ecb6b | |||
| d08a4e0e4e | |||
| ed844a997c | |||
| c9468ba139 | |||
| 938eda0d22 | |||
| f413b08da6 | |||
| 10531e3422 | |||
| 2e0a7ea81d | |||
| 2c64028978 | |||
| 89159137fe | |||
| 186d91d1b7 | |||
| c985ba8a1a | |||
| cece8439a7 | |||
| 5f98b62f21 | |||
| affaf6e96f | |||
| a6996e5234 | |||
| a64eff96a4 | |||
| 7718fc59c6 | |||
| 4e3c9c75c5 | |||
| 583b590a6c | |||
| ef302bbeb7 | |||
| 5791760412 | |||
| e66f3df949 | |||
| 71f54bac6b | |||
| dbf6b2eb17 | |||
| 05e2a4f3a7 | |||
| ca43475a44 | |||
| 3f8c9505c3 | |||
| befa2eefaa | |||
| 22091fc8c3 | |||
| abc742e2ad | |||
| a43f260e1b | |||
| beae045f55 | |||
| 2d561eab7d | |||
| 165159cba9 | |||
| eff22abf8a | |||
| 43c063cef9 | |||
| c3671ec177 | |||
| c8c616dffc | |||
| f94661d7a8 | |||
| 2c767d5996 | |||
| dd039e9276 | |||
| 2907726363 | |||
| 091c125f42 | |||
| b65c805891 | |||
| 4418292dc4 | |||
| 18351f93eb | |||
| fe639f14b3 | |||
| 07098ab691 | |||
| b18945c44d | |||
| ad3029759a | |||
| 7a292b026d | |||
| 930d86404d | |||
| 07e869c497 | |||
| 07db73aa3e | |||
| 69de955158 |
18
.github/workflows/2019.yml
vendored
Normal file
18
.github/workflows/2019.yml
vendored
Normal 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
46
.github/workflows/2021.yml
vendored
Normal 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
8
.gitignore
vendored
@@ -56,3 +56,11 @@ docs/_build/
|
||||
# PyBuilder
|
||||
target/
|
||||
*.swp
|
||||
|
||||
# Rust lock
|
||||
*.lock
|
||||
|
||||
# Performance data
|
||||
perf.data
|
||||
perf.data.old
|
||||
flamegraph.svg
|
||||
|
||||
19
.travis.yml
19
.travis.yml
@@ -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
142
2019/.gitignore
vendored
@@ -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/
|
||||
|
||||
@@ -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)
|
||||
@@ -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
0
2019/aoc2019/__init__.py
Normal file
28
2019/aoc2019/__main__.py
Normal file
28
2019/aoc2019/__main__.py
Normal 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
23
2019/aoc2019/day01.py
Normal 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
33
2019/aoc2019/day02.py
Normal 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
44
2019/aoc2019/day03.py
Normal 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
38
2019/aoc2019/day04.py
Normal 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
29
2019/aoc2019/day05.py
Normal 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
27
2019/aoc2019/day06.py
Normal 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
69
2019/aoc2019/day07.py
Normal 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
34
2019/aoc2019/day08.py
Normal 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
25
2019/aoc2019/day09.py
Normal 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
93
2019/aoc2019/day10.py
Normal 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
60
2019/aoc2019/day11.py
Normal 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
69
2019/aoc2019/day12.py
Normal 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
57
2019/aoc2019/day13.py
Normal 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
71
2019/aoc2019/day14.py
Normal 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
102
2019/aoc2019/day15.py
Normal 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
57
2019/aoc2019/day16.py
Normal 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
26
2019/aoc2019/day17.py
Normal 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
66
2019/aoc2019/day19.py
Normal 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
44
2019/aoc2019/day21.py
Normal 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
90
2019/aoc2019/day22.py
Normal 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
78
2019/aoc2019/day23.py
Normal 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
138
2019/aoc2019/day24.py
Normal 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
123
2019/aoc2019/day25.py
Normal 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
150
2019/aoc2019/intcode.py
Normal 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
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
1
2019/inputs/25.txt
Normal file
File diff suppressed because one or more lines are too long
3
2019/requirements.txt
Normal file
3
2019/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
pytest
|
||||
networkx
|
||||
numpy
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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));
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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> ¤tInputs() 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
0
2019/tests/__init__.py
Normal file
@@ -1 +0,0 @@
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
100756
|
||||
@@ -1 +0,0 @@
|
||||
33583
|
||||
@@ -1,2 +0,0 @@
|
||||
R75,D30,R83,U83,L12,D49,R71,U7,L72
|
||||
U62,R66,U55,R34,D71,R55,D58,R83
|
||||
@@ -1 +0,0 @@
|
||||
159
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
135
|
||||
@@ -1,2 +0,0 @@
|
||||
R8,U5,L5,D3
|
||||
U7,R6,D4,L4
|
||||
@@ -1 +0,0 @@
|
||||
6
|
||||
@@ -1 +0,0 @@
|
||||
03-1-1.in
|
||||
@@ -1 +0,0 @@
|
||||
610
|
||||
@@ -1 +0,0 @@
|
||||
03-1-2.in
|
||||
@@ -1 +0,0 @@
|
||||
410
|
||||
@@ -1 +0,0 @@
|
||||
03-1-3.in
|
||||
@@ -1 +0,0 @@
|
||||
30
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
42
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
4
|
||||
@@ -1 +0,0 @@
|
||||
3,15,3,16,1002,16,10,16,1,16,15,15,4,15,99,0,0
|
||||
@@ -1 +0,0 @@
|
||||
43210
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
54321
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
65210
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
139629729
|
||||
@@ -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
|
||||
@@ -1 +0,0 @@
|
||||
18216
|
||||
@@ -1,5 +0,0 @@
|
||||
.#..#
|
||||
.....
|
||||
#####
|
||||
....#
|
||||
...##
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user