mirror of
https://github.com/bertptrs/adventofcode.git
synced 2025-12-26 13:20:32 +01:00
Compare commits
1 Commits
master
...
adb09169f4
| Author | SHA1 | Date | |
|---|---|---|---|
| adb09169f4 |
43
.github/workflows/2024.yml
vendored
Normal file
43
.github/workflows/2024.yml
vendored
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
on:
|
||||||
|
- push
|
||||||
|
|
||||||
|
name: Advent of Code 2024
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version:
|
||||||
|
- "3.12"
|
||||||
|
- "3.13"
|
||||||
|
|
||||||
|
name: Continuous Integration
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
|
- name: Install uv
|
||||||
|
uses: astral-sh/setup-uv@v3
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
cache-dependency-glob: "2024/uv.lock"
|
||||||
|
|
||||||
|
- name: Check formatting
|
||||||
|
working-directory: "2024"
|
||||||
|
run: >
|
||||||
|
uv run ruff format --check
|
||||||
|
|
||||||
|
- name: Run lints
|
||||||
|
working-directory: "2024"
|
||||||
|
run: >
|
||||||
|
uv run ruff check
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
working-directory: "2024"
|
||||||
|
run: >
|
||||||
|
uv run pytest tests
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -67,6 +67,3 @@ flamegraph.svg
|
|||||||
|
|
||||||
# Ignore saved inputs
|
# Ignore saved inputs
|
||||||
inputs/
|
inputs/
|
||||||
|
|
||||||
# Terraform dir
|
|
||||||
.terraform
|
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import fileinput
|
|
||||||
|
|
||||||
print("digraph day24 {")
|
|
||||||
|
|
||||||
for line in fileinput.input():
|
|
||||||
parts = line.split(" ")
|
|
||||||
if len(parts) != 5:
|
|
||||||
continue
|
|
||||||
|
|
||||||
first, op, second, _, result = parts
|
|
||||||
print(f'{first}{second}{op} [label="{op}"];')
|
|
||||||
print(f"{first} -> {first}{second}{op} -> {result};")
|
|
||||||
print(f"{second} -> {first}{second}{op};")
|
|
||||||
|
|
||||||
print("}")
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# Bonus Challenge
|
|
||||||
|
|
||||||
This part of the repo holds the bonus challenge for 2024: implement as much as possible in pure
|
|
||||||
Terraform.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
The infrastructure definitions expect the input files to be located at `../inputs/[\d]{2}.txt`.
|
|
||||||
After storing the input files there, the code should be runnable as follows:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ terraform init
|
|
||||||
< a lot of modules being installed >
|
|
||||||
$ terraform plan
|
|
||||||
|
|
||||||
Changes to Outputs:
|
|
||||||
+ day01_1 = 42
|
|
||||||
+ day01_2 = 12
|
|
||||||
+ day02_1 = …
|
|
||||||
|
|
||||||
You can apply this plan to save these new output values to the Terraform state, without changing any real infrastructure.
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that Terraform may freeze for tens of seconds while it's running the computations. This is
|
|
||||||
normal, and all Terraform code ought to think a little before doing anything. It would save people
|
|
||||||
some bad rollbacks.
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
I use [Terraform tests](https://developer.hashicorp.com/terraform/language/tests) to automatically
|
|
||||||
run my terraform code on the sample inputs. It's almost a normal development workflow.
|
|
||||||
|
|
||||||
The only rule is that the code should be all terraform, no cheating by shelling out to external
|
|
||||||
programs. Using providers is allowed, as long as the providers don't actually interact with external
|
|
||||||
systems and are reasonably self-contained. I will try to limit my use of those regardless.
|
|
||||||
|
|
||||||
## Why
|
|
||||||
|
|
||||||
DevOps will continue until morale improves. But really, a friend remarked that my Python solutions
|
|
||||||
were strangely normal for me, so I opted to use a language that I do use professionally.
|
|
||||||
|
|
||||||
Terraform is a unique beast. It can do a lot, but it is also very limited, and intentionally so.
|
|
||||||
There's a standard library of functions you might want to use, but all of them work in strange ways
|
|
||||||
and there isn't that much to begin with. You can never mutate any variables, you can only declare
|
|
||||||
new ones. You don't have recursion and your only source of loops are list- and map comprehensions,
|
|
||||||
or multiple instantiations or a module.
|
|
||||||
|
|
||||||
These make for a very constrained programming environment, and constrained programming is fun. It
|
|
||||||
makes you think outside the box.
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
variable "input" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
cleaned_input = replace(var.input, "/ +/", " ")
|
|
||||||
lines = split("\n", trim(local.cleaned_input, "\n"))
|
|
||||||
lines_split = [for line in local.lines : split(" ", line)]
|
|
||||||
left = [for line in local.lines_split : tonumber(line[0])]
|
|
||||||
right = [for line in local.lines_split : tonumber(line[1])]
|
|
||||||
|
|
||||||
left_sorted = sort(local.left)
|
|
||||||
right_sorted = sort(local.right)
|
|
||||||
|
|
||||||
diffs = [for i in range(length(local.left_sorted)) : abs(local.left_sorted[i] - local.right_sorted[i])]
|
|
||||||
|
|
||||||
counts = { for num in local.right : num => num... }
|
|
||||||
|
|
||||||
matching = [for left in local.left : left * try(length(local.counts[left]), 0)]
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part1" {
|
|
||||||
value = sum(local.diffs)
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part2" {
|
|
||||||
value = sum(local.matching)
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
variable "report" {
|
|
||||||
type = list(number)
|
|
||||||
}
|
|
||||||
|
|
||||||
module "is_valid" {
|
|
||||||
source = "../is_valid"
|
|
||||||
count = length(var.report)
|
|
||||||
|
|
||||||
report = concat(
|
|
||||||
count.index > 0 ? slice(var.report, 0, count.index) : [],
|
|
||||||
try(slice(var.report, count.index + 1, length(var.report)), [])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
output "valid" {
|
|
||||||
value = anytrue(module.is_valid[*].valid)
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
variable "report" {
|
|
||||||
type = list(number)
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
delta = [for i in range(1, length(var.report)) : var.report[i] - var.report[i - 1]]
|
|
||||||
|
|
||||||
all_negative = alltrue([for d in local.delta : d <= -1 && d >= -3])
|
|
||||||
all_positive = alltrue([for d in local.delta : d >= 1 && d <= 3])
|
|
||||||
}
|
|
||||||
|
|
||||||
output "valid" {
|
|
||||||
value = local.all_negative || local.all_positive
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
variable "input" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
reports = [for line in split("\n", trim(var.input, "\n")) : split(" ", line)]
|
|
||||||
}
|
|
||||||
|
|
||||||
module "part1_valid" {
|
|
||||||
source = "./is_valid"
|
|
||||||
|
|
||||||
count = length(local.reports)
|
|
||||||
report = local.reports[count.index]
|
|
||||||
}
|
|
||||||
|
|
||||||
module "part2_valid" {
|
|
||||||
source = "./is_savable"
|
|
||||||
count = length(local.reports)
|
|
||||||
report = local.reports[count.index]
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part1" {
|
|
||||||
value = length([for i in range(length(local.reports)) : true if module.part1_valid[i].valid])
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part2" {
|
|
||||||
value = length([for i in range(length(local.reports)) : true if module.part2_valid[i].valid])
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
variable "input" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
filtered = replace(var.input, "/(?s)don't\\(\\).*?do\\(\\)/", "")
|
|
||||||
filtered2 = replace(local.filtered, "/(?s)don't\\(\\).*/", "")
|
|
||||||
|
|
||||||
muls = regexall("mul\\((\\d+),(\\d+)\\)", var.input)
|
|
||||||
filtered_muls = regexall("mul\\((\\d+),(\\d+)\\)", local.filtered2)
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part1" {
|
|
||||||
value = sum([for mul in local.muls : tonumber(mul[1]) * tonumber(mul[0])])
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part2" {
|
|
||||||
value = sum([for mul in local.filtered_muls : tonumber(mul[1]) * tonumber(mul[0])])
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
variable "grid" {
|
|
||||||
type = list(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "x" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "y" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
found_a = substr(var.grid[var.y], var.x, 1) == "A"
|
|
||||||
|
|
||||||
c1 = substr(var.grid[var.y - 1], var.x - 1, 1)
|
|
||||||
c2 = substr(var.grid[var.y - 1], var.x + 1, 1)
|
|
||||||
c3 = substr(var.grid[var.y + 1], var.x + 1, 1)
|
|
||||||
c4 = substr(var.grid[var.y + 1], var.x - 1, 1)
|
|
||||||
|
|
||||||
d1 = "${local.c1}${local.c3}"
|
|
||||||
d2 = "${local.c2}${local.c4}"
|
|
||||||
|
|
||||||
found_d1 = contains(["SM", "MS"], local.d1)
|
|
||||||
found_d2 = contains(["SM", "MS"], local.d2)
|
|
||||||
}
|
|
||||||
|
|
||||||
output "found" {
|
|
||||||
value = local.found_a && local.found_d1 && local.found_d2
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
variable "grid" {
|
|
||||||
type = list(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "x" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "y" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "dx" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "dy" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
word = join("", [for i in range(4) : var.x + i * var.dx >= 0 ? try(substr(var.grid[var.y + i * var.dy], var.x + i * var.dx, 1), "F") : "F"])
|
|
||||||
}
|
|
||||||
|
|
||||||
output "found" {
|
|
||||||
value = contains(["SAMX", "XMAS"], local.word) ? 1 : 0
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
variable "grid" {
|
|
||||||
type = list(string)
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "index" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "width" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "height" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
x = var.index % var.width
|
|
||||||
y = floor(var.index / var.width)
|
|
||||||
|
|
||||||
directions = {
|
|
||||||
"DL" = [-1, 1]
|
|
||||||
"D" = [0, 1]
|
|
||||||
"DR" = [1, 1]
|
|
||||||
}
|
|
||||||
|
|
||||||
should_check_x_mas = local.x >= 1 && local.y >= 1 && local.x < var.width - 1 && local.y < var.height - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
module "check_xmas" {
|
|
||||||
source = "./check_xmas"
|
|
||||||
for_each = local.directions
|
|
||||||
|
|
||||||
grid = var.grid
|
|
||||||
|
|
||||||
x = local.x
|
|
||||||
y = local.y
|
|
||||||
|
|
||||||
dx = each.value[0]
|
|
||||||
dy = each.value[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
module "check_x_mas" {
|
|
||||||
source = "./check_x_mas"
|
|
||||||
count = local.should_check_x_mas ? 1 : 0
|
|
||||||
|
|
||||||
grid = var.grid
|
|
||||||
|
|
||||||
y = local.y
|
|
||||||
x = local.x
|
|
||||||
}
|
|
||||||
|
|
||||||
output "xmas" {
|
|
||||||
value = sum([for _, v in module.check_xmas : v.found])
|
|
||||||
}
|
|
||||||
|
|
||||||
output "x_mas" {
|
|
||||||
value = try(module.check_x_mas[0].found, false) ? 1 : 0
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
variable "input" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
grid = split("\n", chomp(var.input))
|
|
||||||
height = length(local.grid)
|
|
||||||
width = length(local.grid[0])
|
|
||||||
|
|
||||||
lr = length(regexall("XMAS", var.input))
|
|
||||||
rl = length(regexall("SAMX", var.input))
|
|
||||||
}
|
|
||||||
|
|
||||||
module "check_point" {
|
|
||||||
source = "./check_point"
|
|
||||||
|
|
||||||
count = local.width * local.height
|
|
||||||
|
|
||||||
width = local.width
|
|
||||||
height = local.height
|
|
||||||
grid = local.grid
|
|
||||||
index = count.index
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part1" {
|
|
||||||
value = sum(module.check_point[*].xmas) + local.lr + local.rl
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part2" {
|
|
||||||
value = sum(module.check_point[*].x_mas)
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
variable "input" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
parts = split("\n\n", chomp(var.input))
|
|
||||||
rules = [for rule_line in split("\n", local.parts[0]) : [for v in split("|", rule_line) : tonumber(v)]]
|
|
||||||
disallow_rules = { for rule in local.rules : rule[1] => rule[0]... }
|
|
||||||
|
|
||||||
updates = [for update_line in split("\n", local.parts[1]) : [for v in split(",", update_line) : tonumber(v)]]
|
|
||||||
|
|
||||||
scores = [
|
|
||||||
for update in local.updates :
|
|
||||||
alltrue([
|
|
||||||
for i in range(1, length(update)) :
|
|
||||||
!contains(
|
|
||||||
flatten([for j in range(i) : lookup(local.disallow_rules, update[j], [])]),
|
|
||||||
update[i]
|
|
||||||
)
|
|
||||||
]) ? update[floor(length(update) / 2)] : 0]
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part1" {
|
|
||||||
value = sum(local.scores[*])
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
variable "width" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "height" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "antennae" {
|
|
||||||
type = list(tuple([number, number]))
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
pairs = concat([
|
|
||||||
for i in range(length(var.antennae)) :
|
|
||||||
[
|
|
||||||
for j in range(i + 1, length(var.antennae)) : [var.antennae[i], var.antennae[j]]
|
|
||||||
]
|
|
||||||
]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
module "pair" {
|
|
||||||
source = "./pair"
|
|
||||||
count = length(local.pairs)
|
|
||||||
|
|
||||||
first = local.pairs[count.index][0]
|
|
||||||
second = local.pairs[count.index][1]
|
|
||||||
width = var.width
|
|
||||||
height = var.height
|
|
||||||
}
|
|
||||||
|
|
||||||
output "nodes1" {
|
|
||||||
value = setunion([
|
|
||||||
for i in range(length(local.pairs)) :
|
|
||||||
[
|
|
||||||
for v in module.pair[i].nodes1 :
|
|
||||||
v
|
|
||||||
if v[0] >= 0 && v[0] < var.width && v[1] >= 0 && v[1] < var.height
|
|
||||||
]
|
|
||||||
]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
output "nodes2" {
|
|
||||||
value = setunion([
|
|
||||||
for i in range(length(local.pairs)) :
|
|
||||||
[
|
|
||||||
for v in module.pair[i].nodes2 :
|
|
||||||
v
|
|
||||||
if v[0] >= 0 && v[0] < var.width && v[1] >= 0 && v[1] < var.height
|
|
||||||
]
|
|
||||||
]...)
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
variable "first" {
|
|
||||||
type = tuple([number, number])
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "second" {
|
|
||||||
type = tuple([number, number])
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "width" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "height" {
|
|
||||||
type = number
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
dx = var.second[0] - var.first[0]
|
|
||||||
dy = var.second[1] - var.first[1]
|
|
||||||
|
|
||||||
# sort() doesn't work as it turns the numbers into strings
|
|
||||||
gcd0 = abs(local.dx) < abs(local.dy) ? [abs(local.dx), abs(local.dy)] : [abs(local.dy), abs(local.dx)]
|
|
||||||
|
|
||||||
# Do as many iterations as necessary.
|
|
||||||
gcd1 = local.gcd0[0] == 0 ? local.gcd0 : [local.gcd0[1] % local.gcd0[0], local.gcd0[0]]
|
|
||||||
gcd2 = local.gcd1[0] == 0 ? local.gcd1 : [local.gcd1[1] % local.gcd1[0], local.gcd1[0]]
|
|
||||||
gcd3 = local.gcd2[0] == 0 ? local.gcd2 : [local.gcd2[1] % local.gcd2[0], local.gcd2[0]]
|
|
||||||
gcd4 = local.gcd3[0] == 0 ? local.gcd3 : [local.gcd3[1] % local.gcd3[0], local.gcd3[0]]
|
|
||||||
gcd5 = local.gcd4[0] == 0 ? local.gcd4 : [local.gcd4[1] % local.gcd4[0], local.gcd4[0]]
|
|
||||||
gcd6 = local.gcd5[0] == 0 ? local.gcd5 : [local.gcd5[1] % local.gcd5[0], local.gcd5[0]]
|
|
||||||
gcd7 = local.gcd6[0] == 0 ? local.gcd6 : [local.gcd6[1] % local.gcd6[0], local.gcd6[0]]
|
|
||||||
gcd8 = local.gcd7[0] == 0 ? local.gcd7 : [local.gcd7[1] % local.gcd7[0], local.gcd7[0]]
|
|
||||||
gcd9 = local.gcd8[0] == 0 ? local.gcd8 : [local.gcd8[1] % local.gcd8[0], local.gcd8[0]]
|
|
||||||
gcd10 = local.gcd9[0] == 0 ? local.gcd9 : [local.gcd9[1] % local.gcd9[0], local.gcd9[0]]
|
|
||||||
|
|
||||||
# 10 iterations should cover numbers up to 55, which is more than the width/height
|
|
||||||
gcd = local.gcd10[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
output "nodes1" {
|
|
||||||
value = [
|
|
||||||
[var.first[0] - local.dx, var.first[1] - local.dy],
|
|
||||||
[var.second[0] + local.dx, var.second[1] + local.dy],
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
output "nodes2" {
|
|
||||||
value = concat(
|
|
||||||
[for i in range(max(var.width, var.height)) : [var.first[0] - i * local.dx / local.gcd, var.first[1] - i * local.dy / local.gcd]],
|
|
||||||
[for i in range(max(var.width, var.height)) : [var.second[0] + i * local.dx / local.gcd, var.second[1] + i * local.dy / local.gcd]]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
variable "input" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
lines = split("\n", chomp(var.input))
|
|
||||||
height = length(local.lines)
|
|
||||||
width = length(local.lines[0])
|
|
||||||
|
|
||||||
antennae = concat([
|
|
||||||
for y in range(local.height) :
|
|
||||||
[
|
|
||||||
for x in range(local.width) :
|
|
||||||
[substr(local.lines[y], x, 1), x, y]
|
|
||||||
if substr(local.lines[y], x, 1) != "."
|
|
||||||
]
|
|
||||||
]...)
|
|
||||||
|
|
||||||
by_freq = {
|
|
||||||
for antenna in local.antennae :
|
|
||||||
antenna[0] => [antenna[1], antenna[2]]...
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module "freq" {
|
|
||||||
source = "./freq"
|
|
||||||
for_each = local.by_freq
|
|
||||||
width = local.width
|
|
||||||
height = local.height
|
|
||||||
antennae = each.value
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part1" {
|
|
||||||
value = length(setunion([for _, v in module.freq : v.nodes1]...))
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part2" {
|
|
||||||
value = length(setunion([for _, v in module.freq : v.nodes2]...))
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
variable "input" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
nums = [for s in split(" ", chomp(var.input)) : tonumber(s)]
|
|
||||||
|
|
||||||
grouped = { for num in local.nums : num => 1... }
|
|
||||||
total = { for k, v in local.grouped : k => sum(v) }
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step1" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = local.total
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part1" {
|
|
||||||
value = sum(values(module.step25.next))
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part2" {
|
|
||||||
value = sum(values(module.step75.next))
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
variable "prev" {
|
|
||||||
type = map(number)
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
by_value = flatten([
|
|
||||||
for num, count in var.prev : (
|
|
||||||
tonumber(num) == 0
|
|
||||||
? [{ number = 1, amount = count }]
|
|
||||||
: (
|
|
||||||
length(tostring(num)) % 2 == 0
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
number = tonumber(substr(tostring(num), 0, length(tostring(num)) / 2)),
|
|
||||||
amount = count
|
|
||||||
},
|
|
||||||
{
|
|
||||||
number = tonumber(substr(tostring(num), length(tostring(num)) / 2, length(tostring(num)) / 2)),
|
|
||||||
amount = count,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [{ number = 2024 * num, amount = count }]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
])
|
|
||||||
|
|
||||||
grouped = { for kv in local.by_value : kv.number => kv.amount... }
|
|
||||||
}
|
|
||||||
|
|
||||||
output "next" {
|
|
||||||
value = { for num, groups in local.grouped : num => sum(groups) }
|
|
||||||
}
|
|
||||||
@@ -1,446 +0,0 @@
|
|||||||
# This file was generated using a nasty bash for loop. Edit at your own peril.
|
|
||||||
|
|
||||||
module "step2" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step1.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step3" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step2.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step4" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step3.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step5" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step4.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step6" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step5.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step7" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step6.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step8" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step7.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step9" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step8.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step10" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step9.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step11" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step10.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step12" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step11.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step13" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step12.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step14" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step13.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step15" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step14.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step16" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step15.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step17" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step16.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step18" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step17.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step19" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step18.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step20" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step19.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step21" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step20.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step22" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step21.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step23" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step22.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step24" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step23.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step25" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step24.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step26" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step25.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step27" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step26.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step28" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step27.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step29" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step28.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step30" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step29.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step31" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step30.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step32" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step31.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step33" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step32.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step34" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step33.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step35" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step34.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step36" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step35.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step37" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step36.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step38" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step37.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step39" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step38.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step40" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step39.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step41" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step40.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step42" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step41.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step43" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step42.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step44" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step43.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step45" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step44.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step46" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step45.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step47" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step46.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step48" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step47.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step49" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step48.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step50" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step49.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step51" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step50.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step52" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step51.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step53" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step52.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step54" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step53.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step55" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step54.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step56" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step55.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step57" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step56.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step58" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step57.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step59" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step58.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step60" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step59.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step61" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step60.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step62" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step61.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step63" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step62.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step64" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step63.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step65" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step64.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step66" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step65.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step67" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step66.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step68" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step67.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step69" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step68.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step70" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step69.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step71" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step70.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step72" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step71.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step73" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step72.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step74" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step73.next
|
|
||||||
}
|
|
||||||
|
|
||||||
module "step75" {
|
|
||||||
source = "./step"
|
|
||||||
|
|
||||||
prev = module.step74.next
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
variable "input" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
machines = regexall(
|
|
||||||
"Button A: X\\+(\\d+), Y\\+(\\d+)\nButton B: X\\+(\\d+), Y\\+(\\d+)\nPrize: X=(\\d+), Y=(\\d+)",
|
|
||||||
var.input
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
module "solve1" {
|
|
||||||
source = "./solve"
|
|
||||||
machines = local.machines
|
|
||||||
}
|
|
||||||
|
|
||||||
module "solve2" {
|
|
||||||
source = "./solve"
|
|
||||||
machines = [
|
|
||||||
for machine in local.machines :
|
|
||||||
[machine[0], machine[1], machine[2], machine[3], 10000000000000 + machine[4], 10000000000000 + machine[5]]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part1" {
|
|
||||||
value = module.solve1.solutions
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part2" {
|
|
||||||
value = module.solve2.solutions
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
variable "machines" {
|
|
||||||
type = list(list(number))
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
a_substitutions = [
|
|
||||||
for machine in var.machines :
|
|
||||||
[-machine[2] / machine[0], machine[4] / machine[0]]
|
|
||||||
]
|
|
||||||
|
|
||||||
b_equations = [
|
|
||||||
for i in range(length(var.machines)) :
|
|
||||||
[
|
|
||||||
var.machines[i][3] + local.a_substitutions[i][0] * var.machines[i][1],
|
|
||||||
var.machines[i][5] - local.a_substitutions[i][1] * var.machines[i][1]
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
b = [for eq in local.b_equations : floor(eq[1] / eq[0] + 0.5)]
|
|
||||||
|
|
||||||
a = [
|
|
||||||
for i in range(length(var.machines)) :
|
|
||||||
floor((var.machines[i][4] - local.b[i] * var.machines[i][2]) / var.machines[i][0] + 0.5)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
output "solutions" {
|
|
||||||
value = sum([
|
|
||||||
for i in range(length(var.machines)) :
|
|
||||||
3 * local.a[i] + local.b[i]
|
|
||||||
if var.machines[i][0] * local.a[i] + var.machines[i][2] * local.b[i] == var.machines[i][4]
|
|
||||||
&& var.machines[i][1] * local.a[i] + var.machines[i][3] * local.b[i] == var.machines[i][5]
|
|
||||||
])
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
variable "input" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "width" {
|
|
||||||
type = number
|
|
||||||
default = 101
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "height" {
|
|
||||||
type = number
|
|
||||||
default = 103
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
lines = regexall("p=(-?\\d+),(-?\\d+) v=(-?\\d+),(-?\\d+)", var.input)
|
|
||||||
positions = [
|
|
||||||
for line in local.lines :
|
|
||||||
[
|
|
||||||
((line[0] + 100 * line[2]) % var.width + var.width) % var.width,
|
|
||||||
((line[1] + 100 * line[3]) % var.height + var.height) % var.height,
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
q1 = length([for pos in local.positions : pos if pos[0] < floor(var.width / 2) && pos[1] < floor(var.height / 2)])
|
|
||||||
q2 = length([for pos in local.positions : pos if pos[0] > floor(var.width / 2) && pos[1] < floor(var.height / 2)])
|
|
||||||
q3 = length([for pos in local.positions : pos if pos[0] < floor(var.width / 2) && pos[1] > floor(var.height / 2)])
|
|
||||||
q4 = length([for pos in local.positions : pos if pos[0] > floor(var.width / 2) && pos[1] > floor(var.height / 2)])
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part1" {
|
|
||||||
value = local.q1 * local.q2 * local.q3 * local.q4
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
variable "input" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
parts = split("\n\n", chomp((var.input)))
|
|
||||||
patterns = replace(local.parts[0], ", ", "|")
|
|
||||||
valid = [for line in split("\n", local.parts[1]) : line if length(regexall("^(${local.patterns})+$", line)) > 0]
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part1" {
|
|
||||||
value = length(local.valid)
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
variable "input" {
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
blocks = split("\n\n", chomp(var.input))
|
|
||||||
heights = [
|
|
||||||
for block in local.blocks : [
|
|
||||||
for i in range(5) : length([
|
|
||||||
for line in split("\n", block) : line if substr(line, i, 1) == "#"
|
|
||||||
])
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
locks = [for i in range(length(local.blocks)) : local.heights[i] if startswith(local.blocks[i], "#####")]
|
|
||||||
keys = [for i in range(length(local.blocks)) : local.heights[i] if !startswith(local.blocks[i], "#####")]
|
|
||||||
|
|
||||||
combined = concat([for lock in local.locks : [for key in local.keys : [for i in range(5) : lock[i] + key[i] <= 7]]]...)
|
|
||||||
}
|
|
||||||
|
|
||||||
output "part1" {
|
|
||||||
value = length([for combination in local.combined : combination if alltrue(combination)])
|
|
||||||
}
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
terraform {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module "day01" {
|
|
||||||
source = "./day01"
|
|
||||||
input = file("../inputs/01.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day01_1" {
|
|
||||||
value = module.day01.part1
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day01_2" {
|
|
||||||
value = module.day01.part2
|
|
||||||
}
|
|
||||||
|
|
||||||
module "day02" {
|
|
||||||
source = "./day02"
|
|
||||||
input = file("../inputs/02.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day02_1" {
|
|
||||||
value = module.day02.part1
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day02_2" {
|
|
||||||
value = module.day02.part2
|
|
||||||
}
|
|
||||||
|
|
||||||
module "day03" {
|
|
||||||
source = "./day03"
|
|
||||||
input = file("../inputs/03.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day03_1" {
|
|
||||||
value = module.day03.part1
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day03_2" {
|
|
||||||
value = module.day03.part2
|
|
||||||
}
|
|
||||||
|
|
||||||
# Don't run this, it runs forever (6 minutes) and requires a lot of memory (~5.5GB) to execute to
|
|
||||||
# boot. Trust me, it works.
|
|
||||||
|
|
||||||
# module "day04" {
|
|
||||||
# source = "./day04"
|
|
||||||
# input = file("../inputs/04.txt")
|
|
||||||
# }
|
|
||||||
|
|
||||||
# output "day04_1" {
|
|
||||||
# value = module.day04.part1
|
|
||||||
# }
|
|
||||||
|
|
||||||
# output "day04_2" {
|
|
||||||
# value = module.day04.part2
|
|
||||||
# }
|
|
||||||
|
|
||||||
module "day05" {
|
|
||||||
source = "./day05"
|
|
||||||
input = file("../inputs/05.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day05_1" {
|
|
||||||
value = module.day05.part1
|
|
||||||
}
|
|
||||||
|
|
||||||
module "day08" {
|
|
||||||
source = "./day08"
|
|
||||||
input = file("../inputs/08.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day08_1" {
|
|
||||||
value = module.day08.part1
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day08_2" {
|
|
||||||
value = module.day08.part2
|
|
||||||
}
|
|
||||||
|
|
||||||
module "day11" {
|
|
||||||
source = "./day11"
|
|
||||||
input = file("../inputs/11.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day11_1" {
|
|
||||||
value = module.day11.part1
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day11_2" {
|
|
||||||
value = module.day11.part2
|
|
||||||
}
|
|
||||||
|
|
||||||
module "day13" {
|
|
||||||
source = "./day13"
|
|
||||||
input = file("../inputs/13.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day13_1" {
|
|
||||||
value = module.day13.part1
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day13_2" {
|
|
||||||
value = module.day13.part2
|
|
||||||
}
|
|
||||||
|
|
||||||
module "day14" {
|
|
||||||
source = "./day14"
|
|
||||||
input = file("../inputs/14.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day14_1" {
|
|
||||||
value = module.day14.part1
|
|
||||||
}
|
|
||||||
|
|
||||||
module "day19" {
|
|
||||||
source = "./day19"
|
|
||||||
input = file("../inputs/19.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day19_1" {
|
|
||||||
value = module.day19.part1
|
|
||||||
}
|
|
||||||
|
|
||||||
module "day25" {
|
|
||||||
source = "./day25"
|
|
||||||
input = file("../inputs/25.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
output "day25_1" {
|
|
||||||
value = module.day25.part1
|
|
||||||
}
|
|
||||||
@@ -1,242 +0,0 @@
|
|||||||
run "day1" {
|
|
||||||
command = plan
|
|
||||||
module {
|
|
||||||
source = "./day01"
|
|
||||||
}
|
|
||||||
|
|
||||||
variables {
|
|
||||||
input = file("../tests/samples/01.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part1 == 11
|
|
||||||
error_message = "Part1 output is wrong"
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part2 == 31
|
|
||||||
error_message = "Part2 output is wrong"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run "day2" {
|
|
||||||
command = plan
|
|
||||||
|
|
||||||
module {
|
|
||||||
source = "./day02"
|
|
||||||
}
|
|
||||||
|
|
||||||
variables {
|
|
||||||
input = file("../tests/samples/02.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part1 == 2
|
|
||||||
error_message = "Part1 output is wrong"
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part2 == 4
|
|
||||||
error_message = "Part2 output is wrong"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run "day3_1" {
|
|
||||||
command = plan
|
|
||||||
|
|
||||||
module {
|
|
||||||
source = "./day03"
|
|
||||||
}
|
|
||||||
|
|
||||||
variables {
|
|
||||||
input = "xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))"
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part1 == 161
|
|
||||||
error_message = "Part1 output is wrong"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run "day3_2" {
|
|
||||||
command = plan
|
|
||||||
|
|
||||||
module {
|
|
||||||
source = "./day03"
|
|
||||||
}
|
|
||||||
|
|
||||||
variables {
|
|
||||||
input = "xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))"
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part2 == 48
|
|
||||||
error_message = "Part2 output is wrong"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run "day4_small" {
|
|
||||||
command = plan
|
|
||||||
|
|
||||||
module {
|
|
||||||
source = "./day04"
|
|
||||||
}
|
|
||||||
|
|
||||||
variables {
|
|
||||||
input = file("../tests/samples/04.1.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part1 == 4
|
|
||||||
error_message = "Part1 output is wrong"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run "day4" {
|
|
||||||
command = plan
|
|
||||||
|
|
||||||
module {
|
|
||||||
source = "./day04"
|
|
||||||
}
|
|
||||||
|
|
||||||
variables {
|
|
||||||
input = file("../tests/samples/04.2.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part1 == 18
|
|
||||||
error_message = "Part1 output is wrong"
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part2 == 9
|
|
||||||
error_message = "Part2 output is wrong"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run "day5_1" {
|
|
||||||
command = plan
|
|
||||||
|
|
||||||
module {
|
|
||||||
source = "./day05"
|
|
||||||
}
|
|
||||||
|
|
||||||
variables {
|
|
||||||
input = file("../tests/samples/05.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part1 == 143
|
|
||||||
error_message = "Part1 output is wrong"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run "day08" {
|
|
||||||
command = plan
|
|
||||||
|
|
||||||
module {
|
|
||||||
source = "./day08"
|
|
||||||
}
|
|
||||||
|
|
||||||
variables {
|
|
||||||
input = file("../tests/samples/08.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part1 == 14
|
|
||||||
error_message = "Part1 output is wrong"
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part2 == 34
|
|
||||||
error_message = "Part1 output is wrong"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run "day11" {
|
|
||||||
command = plan
|
|
||||||
|
|
||||||
module {
|
|
||||||
source = "./day11"
|
|
||||||
}
|
|
||||||
|
|
||||||
variables {
|
|
||||||
input = "125 17"
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part1 == 55312
|
|
||||||
error_message = "Part1 output is wrong"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run "day13" {
|
|
||||||
command = plan
|
|
||||||
|
|
||||||
module {
|
|
||||||
source = "./day13"
|
|
||||||
}
|
|
||||||
|
|
||||||
variables {
|
|
||||||
input = file("../tests/samples/13.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part1 == 480
|
|
||||||
error_message = "Part1 output is wrong"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run "day14" {
|
|
||||||
command = plan
|
|
||||||
|
|
||||||
module {
|
|
||||||
source = "./day14"
|
|
||||||
}
|
|
||||||
|
|
||||||
variables {
|
|
||||||
input = file("../tests/samples/14.txt")
|
|
||||||
height = 7
|
|
||||||
width = 11
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part1 == 12
|
|
||||||
error_message = "Part1 output is wrong"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run "day19" {
|
|
||||||
command = plan
|
|
||||||
|
|
||||||
module {
|
|
||||||
source = "./day19"
|
|
||||||
}
|
|
||||||
|
|
||||||
variables {
|
|
||||||
input = file("../tests/samples/19.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part1 == 6
|
|
||||||
error_message = "Part1 output is wrong"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run "day25" {
|
|
||||||
command = plan
|
|
||||||
|
|
||||||
module {
|
|
||||||
source = "./day25"
|
|
||||||
}
|
|
||||||
|
|
||||||
variables {
|
|
||||||
input = file("../tests/samples/25.txt")
|
|
||||||
}
|
|
||||||
|
|
||||||
assert {
|
|
||||||
condition = output.part1 == 3
|
|
||||||
error_message = "Part1 output is wrong"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ readme = "README.md"
|
|||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"click>=8.1.7",
|
"click>=8.1.7",
|
||||||
"networkx>=3.4.2",
|
|
||||||
"numpy>=2.1.2",
|
"numpy>=2.1.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
from typing import IO
|
|
||||||
|
|
||||||
import click
|
import click
|
||||||
|
|
||||||
@@ -20,13 +19,12 @@ from aoc import days
|
|||||||
"-t", "--time", "timing", is_flag=True, help="Print elapsed time afterwards"
|
"-t", "--time", "timing", is_flag=True, help="Print elapsed time afterwards"
|
||||||
)
|
)
|
||||||
@click.argument("day", required=True)
|
@click.argument("day", required=True)
|
||||||
def main(day: int, timing: bool, data: IO[str]) -> None:
|
def main(day: int, timing: bool, data: str) -> None:
|
||||||
runner_class = days.get_runner(day)
|
runner_class = days.get_runner(day)
|
||||||
contents = data.read()
|
|
||||||
|
|
||||||
start = time.perf_counter_ns()
|
start = time.perf_counter_ns()
|
||||||
|
|
||||||
part1, part2 = runner_class.run_both(contents)
|
part1, part2 = runner_class.run_both(data)
|
||||||
|
|
||||||
if timing:
|
if timing:
|
||||||
elapsed = time.perf_counter_ns() - start
|
elapsed = time.perf_counter_ns() - start
|
||||||
|
|||||||
@@ -1,27 +1,13 @@
|
|||||||
from collections import defaultdict
|
|
||||||
from io import StringIO
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
import numpy
|
from . import SeparateRunner
|
||||||
|
|
||||||
from . import CombinedRunner
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(CombinedRunner):
|
class DayRunner(SeparateRunner):
|
||||||
@classmethod
|
@classmethod
|
||||||
def run_both(cls, data: str) -> tuple[Any, Any]:
|
def part1(cls, _data: str) -> Any:
|
||||||
nums = numpy.loadtxt(StringIO(data), dtype=numpy.int32)
|
return "Hello"
|
||||||
|
|
||||||
left = nums[..., 0]
|
@classmethod
|
||||||
right = nums[..., 1]
|
def part2(cls, _data: str) -> Any:
|
||||||
|
return "world!"
|
||||||
left.sort()
|
|
||||||
right.sort()
|
|
||||||
|
|
||||||
diff = numpy.abs(left - right).sum()
|
|
||||||
|
|
||||||
counts: defaultdict[int, int] = defaultdict(int)
|
|
||||||
for val in right:
|
|
||||||
counts[val] += 1
|
|
||||||
|
|
||||||
return diff, sum(counts[v] * v for v in left)
|
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
from collections import deque
|
|
||||||
|
|
||||||
import numpy
|
|
||||||
|
|
||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(cls, input: str) -> int:
|
|
||||||
grid = numpy.array(
|
|
||||||
[[int(v) for v in line] for line in input.strip().split("\n")]
|
|
||||||
)
|
|
||||||
|
|
||||||
width, height = grid.shape
|
|
||||||
|
|
||||||
start_x, start_y = numpy.nonzero(grid == 0)
|
|
||||||
|
|
||||||
todo = []
|
|
||||||
|
|
||||||
reachable = 0
|
|
||||||
|
|
||||||
for sx, sy in zip(start_x, start_y):
|
|
||||||
todo.append((sx, sy))
|
|
||||||
ways = numpy.zeros_like(grid, dtype=bool)
|
|
||||||
|
|
||||||
def enqueue(x: int, y: int, val: int) -> None:
|
|
||||||
if grid[x, y] == val + 1:
|
|
||||||
if not ways[x, y]:
|
|
||||||
todo.append((x, y))
|
|
||||||
ways[x, y] += True
|
|
||||||
|
|
||||||
while todo:
|
|
||||||
x, y = todo.pop()
|
|
||||||
val = grid[x, y]
|
|
||||||
if val == 9:
|
|
||||||
reachable += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
if x > 0:
|
|
||||||
enqueue(x - 1, y, val)
|
|
||||||
if y > 0:
|
|
||||||
enqueue(x, y - 1, val)
|
|
||||||
if x < width - 1:
|
|
||||||
enqueue(x + 1, y, val)
|
|
||||||
if y < height - 1:
|
|
||||||
enqueue(x, y + 1, val)
|
|
||||||
|
|
||||||
return reachable
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, input: str) -> int:
|
|
||||||
grid = numpy.array(
|
|
||||||
[[int(v) for v in line] for line in input.strip().split("\n")]
|
|
||||||
)
|
|
||||||
ways = numpy.zeros_like(grid)
|
|
||||||
|
|
||||||
width, height = grid.shape
|
|
||||||
|
|
||||||
start_x, start_y = numpy.nonzero(grid == 9)
|
|
||||||
ways[grid == 9] = 1
|
|
||||||
|
|
||||||
todo = deque((x, y) for x, y in zip(start_x, start_y))
|
|
||||||
|
|
||||||
def enqueue(x: int, y: int, val: int, cur: int) -> None:
|
|
||||||
if grid[x, y] == val - 1:
|
|
||||||
if ways[x, y] == 0:
|
|
||||||
todo.append((x, y))
|
|
||||||
ways[x, y] += cur
|
|
||||||
|
|
||||||
while todo:
|
|
||||||
x, y = todo.popleft()
|
|
||||||
val = grid[x, y]
|
|
||||||
cur = ways[x, y]
|
|
||||||
|
|
||||||
if x > 0:
|
|
||||||
enqueue(x - 1, y, val, cur)
|
|
||||||
if y > 0:
|
|
||||||
enqueue(x, y - 1, val, cur)
|
|
||||||
if x < width - 1:
|
|
||||||
enqueue(x + 1, y, val, cur)
|
|
||||||
if y < height - 1:
|
|
||||||
enqueue(x, y + 1, val, cur)
|
|
||||||
|
|
||||||
return ways[grid == 0].sum()
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import functools
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from . import CombinedRunner
|
|
||||||
|
|
||||||
|
|
||||||
@functools.cache
|
|
||||||
def blink_num(num: int) -> tuple[int, ...]:
|
|
||||||
if num == 0:
|
|
||||||
return (1,)
|
|
||||||
|
|
||||||
num_str = str(num)
|
|
||||||
num_len = len(num_str)
|
|
||||||
|
|
||||||
if num_len % 2 == 0:
|
|
||||||
half = num_len // 2
|
|
||||||
return (int(num_str[:half]), int(num_str[half:]))
|
|
||||||
|
|
||||||
return (num * 2024,)
|
|
||||||
|
|
||||||
|
|
||||||
def step(nums: dict[int, int]) -> dict[int, int]:
|
|
||||||
result = defaultdict(int)
|
|
||||||
|
|
||||||
for num, count in nums.items():
|
|
||||||
for transformed in blink_num(num):
|
|
||||||
result[transformed] += count
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(CombinedRunner):
|
|
||||||
@classmethod
|
|
||||||
def run_both(cls, input: str) -> tuple[int, int]:
|
|
||||||
nums = [int(val) for val in input.strip().split(" ")]
|
|
||||||
|
|
||||||
counts = defaultdict(int)
|
|
||||||
|
|
||||||
for num in nums:
|
|
||||||
counts[num] += 1
|
|
||||||
|
|
||||||
for _ in range(25):
|
|
||||||
counts = step(counts)
|
|
||||||
|
|
||||||
part1 = sum(counts.values())
|
|
||||||
|
|
||||||
for _ in range(50):
|
|
||||||
counts = step(counts)
|
|
||||||
|
|
||||||
return part1, sum(counts.values())
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
import numpy
|
|
||||||
|
|
||||||
from . import CombinedRunner
|
|
||||||
|
|
||||||
DIRECTIONS = [
|
|
||||||
(-1, 0),
|
|
||||||
(1, 0),
|
|
||||||
(0, -1),
|
|
||||||
(0, 1),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(CombinedRunner):
|
|
||||||
@classmethod
|
|
||||||
def run_both(cls, input: str) -> tuple[int, int]:
|
|
||||||
grid = numpy.array(list(map(list, input.strip().split("\n"))))
|
|
||||||
|
|
||||||
score = 0
|
|
||||||
score2 = 0
|
|
||||||
|
|
||||||
for y in range(grid.shape[0]):
|
|
||||||
for x in range(grid.shape[1]):
|
|
||||||
if grid[y, x] == ".":
|
|
||||||
continue
|
|
||||||
|
|
||||||
search = grid[y, x]
|
|
||||||
grid[y, x] = "."
|
|
||||||
|
|
||||||
todo = [(y, x)]
|
|
||||||
cluster = {(y, x)}
|
|
||||||
|
|
||||||
def enqueue(y, x):
|
|
||||||
if grid[y, x] == search:
|
|
||||||
grid[y, x] = "."
|
|
||||||
todo.append((y, x))
|
|
||||||
cluster.add((y, x))
|
|
||||||
|
|
||||||
while todo:
|
|
||||||
cy, cx = todo.pop()
|
|
||||||
|
|
||||||
if cx > 0:
|
|
||||||
enqueue(cy, cx - 1)
|
|
||||||
if cy > 0:
|
|
||||||
enqueue(cy - 1, cx)
|
|
||||||
|
|
||||||
if cx < grid.shape[1] - 1:
|
|
||||||
enqueue(cy, cx + 1)
|
|
||||||
if cy < grid.shape[0] - 1:
|
|
||||||
enqueue(cy + 1, cx)
|
|
||||||
|
|
||||||
side_length = sum(
|
|
||||||
sum((cy + dy, cx + dx) not in cluster for dy, dx in DIRECTIONS)
|
|
||||||
for cy, cx in cluster
|
|
||||||
)
|
|
||||||
|
|
||||||
corners = 0
|
|
||||||
|
|
||||||
for cy, cx in cluster:
|
|
||||||
# Outer corners
|
|
||||||
corners += (cy, cx - 1) not in cluster and (
|
|
||||||
cy - 1,
|
|
||||||
cx,
|
|
||||||
) not in cluster
|
|
||||||
corners += (cy, cx + 1) not in cluster and (
|
|
||||||
cy - 1,
|
|
||||||
cx,
|
|
||||||
) not in cluster
|
|
||||||
corners += (cy, cx - 1) not in cluster and (
|
|
||||||
cy + 1,
|
|
||||||
cx,
|
|
||||||
) not in cluster
|
|
||||||
corners += (cy, cx + 1) not in cluster and (
|
|
||||||
cy + 1,
|
|
||||||
cx,
|
|
||||||
) not in cluster
|
|
||||||
# Inner corners
|
|
||||||
corners += (
|
|
||||||
(cy, cx - 1) in cluster
|
|
||||||
and (cy - 1, cx) in cluster
|
|
||||||
and (cy - 1, cx - 1) not in cluster
|
|
||||||
)
|
|
||||||
corners += (
|
|
||||||
(cy, cx + 1) in cluster
|
|
||||||
and (cy - 1, cx) in cluster
|
|
||||||
and (cy - 1, cx + 1) not in cluster
|
|
||||||
)
|
|
||||||
corners += (
|
|
||||||
(cy, cx - 1) in cluster
|
|
||||||
and (cy + 1, cx) in cluster
|
|
||||||
and (cy + 1, cx - 1) not in cluster
|
|
||||||
)
|
|
||||||
corners += (
|
|
||||||
(cy, cx + 1) in cluster
|
|
||||||
and (cy + 1, cx) in cluster
|
|
||||||
and (cy + 1, cx + 1) not in cluster
|
|
||||||
)
|
|
||||||
|
|
||||||
score += side_length * len(cluster)
|
|
||||||
score2 += corners * len(cluster)
|
|
||||||
|
|
||||||
return (score, score2)
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import re
|
|
||||||
|
|
||||||
import numpy
|
|
||||||
|
|
||||||
from . import CombinedRunner
|
|
||||||
|
|
||||||
NASTY_REGEX = r"""Button A: X\+(\d+), Y\+(\d+)
|
|
||||||
Button B: X\+(\d+), Y\+(\d+)
|
|
||||||
Prize: X=(\d+), Y=(\d+)"""
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(CombinedRunner):
|
|
||||||
@classmethod
|
|
||||||
def run_both(cls, input: str) -> tuple[int, int]:
|
|
||||||
machines = re.findall(NASTY_REGEX, input)
|
|
||||||
|
|
||||||
cost_to_win = 0
|
|
||||||
cost_to_win2 = 0
|
|
||||||
scale = 10000000000000
|
|
||||||
|
|
||||||
for machine in machines:
|
|
||||||
ax, ay, bx, by, px, py = map(int, machine)
|
|
||||||
|
|
||||||
X = numpy.array([[ax, bx], [ay, by]])
|
|
||||||
B = numpy.array([px, py])
|
|
||||||
B2 = numpy.array([px + scale, py + scale])
|
|
||||||
|
|
||||||
A = numpy.linalg.solve(X, B)
|
|
||||||
A2 = numpy.linalg.solve(X, B2)
|
|
||||||
|
|
||||||
a_press, b_press = map(round, A)
|
|
||||||
a_press2, b_press2 = map(round, A2)
|
|
||||||
|
|
||||||
if a_press * ax + b_press * bx == px and a_press * ay + b_press * by == py:
|
|
||||||
cost_to_win += 3 * a_press + b_press
|
|
||||||
|
|
||||||
if (
|
|
||||||
a_press2 * ax + b_press2 * bx == px + scale
|
|
||||||
and a_press2 * ay + b_press2 * by == py + scale
|
|
||||||
):
|
|
||||||
cost_to_win2 += 3 * a_press2 + b_press2
|
|
||||||
|
|
||||||
return cost_to_win, cost_to_win2
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
import itertools
|
|
||||||
import math
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import numpy
|
|
||||||
|
|
||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
NASTY_REGEX = re.compile(r"p=(-?\d+),(-?\d+) v=(-?\d+),(-?\d+)")
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(cls, input: str, width: int = 101, height: int = 103) -> int:
|
|
||||||
points = NASTY_REGEX.findall(input)
|
|
||||||
|
|
||||||
quadrants = [0] * 4
|
|
||||||
|
|
||||||
x_middle = width // 2
|
|
||||||
y_middle = height // 2
|
|
||||||
|
|
||||||
for point in points:
|
|
||||||
x, y, dx, dy = map(int, point)
|
|
||||||
|
|
||||||
rx = ((x + dx * 100) % width + width) % width
|
|
||||||
ry = ((y + dy * 100) % height + height) % height
|
|
||||||
|
|
||||||
match rx:
|
|
||||||
case _ if rx < x_middle:
|
|
||||||
xq = 0
|
|
||||||
case _ if rx > x_middle:
|
|
||||||
xq = 1
|
|
||||||
case _:
|
|
||||||
continue
|
|
||||||
|
|
||||||
match ry:
|
|
||||||
case _ if ry < y_middle:
|
|
||||||
yq = 0
|
|
||||||
case _ if ry > y_middle:
|
|
||||||
yq = 1
|
|
||||||
case _:
|
|
||||||
continue
|
|
||||||
|
|
||||||
quadrants[2 * yq + xq] += 1
|
|
||||||
|
|
||||||
return math.prod(quadrants)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, input: str) -> int:
|
|
||||||
width = 101
|
|
||||||
height = 103
|
|
||||||
|
|
||||||
points = NASTY_REGEX.findall(input)
|
|
||||||
points_fast = numpy.array([list(map(int, point)) for point in points])
|
|
||||||
|
|
||||||
positions = points_fast[:, 0:2]
|
|
||||||
velocities = points_fast[:, 2:]
|
|
||||||
|
|
||||||
target = len(velocities)
|
|
||||||
|
|
||||||
# Assumption: when the easter egg happens, no robots overlap, and this is the
|
|
||||||
# only time this happens. There is no reason this should work but it does.
|
|
||||||
mod_base = numpy.array([width, height])
|
|
||||||
for i in itertools.count(1):
|
|
||||||
positions += velocities
|
|
||||||
|
|
||||||
positions %= mod_base
|
|
||||||
positions += mod_base
|
|
||||||
positions %= mod_base
|
|
||||||
|
|
||||||
if len(numpy.unique(positions, axis=0)) == target:
|
|
||||||
grid = [[" "] * width for _ in range(height)]
|
|
||||||
|
|
||||||
for x, y in positions:
|
|
||||||
grid[y][x] = "#"
|
|
||||||
|
|
||||||
tree = "\n".join(map((lambda x: "".join(x)), grid))
|
|
||||||
print(tree, file=sys.stderr)
|
|
||||||
return i
|
|
||||||
@@ -1,161 +0,0 @@
|
|||||||
import itertools
|
|
||||||
|
|
||||||
import numpy
|
|
||||||
|
|
||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
|
|
||||||
def parse_input(data: str) -> tuple[numpy.array, str]:
|
|
||||||
grid, steps = data.split("\n\n")
|
|
||||||
|
|
||||||
grid_split = numpy.array([list(line) for line in grid.split("\n")])
|
|
||||||
|
|
||||||
steps = "".join(steps.split("\n"))
|
|
||||||
|
|
||||||
return grid_split, steps
|
|
||||||
|
|
||||||
|
|
||||||
def convert_dir(c: str) -> tuple[int, int]:
|
|
||||||
match c:
|
|
||||||
case "^":
|
|
||||||
return 0, -1
|
|
||||||
case ">":
|
|
||||||
return 1, 0
|
|
||||||
case "<":
|
|
||||||
return -1, 0
|
|
||||||
case "v":
|
|
||||||
return 0, 1
|
|
||||||
case other:
|
|
||||||
raise ValueError(f"Invalid movement: {other}")
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(cls, input: str) -> None:
|
|
||||||
grid, steps = parse_input(input)
|
|
||||||
|
|
||||||
y, x = numpy.where(grid == "@")
|
|
||||||
x, y = x[0], y[0]
|
|
||||||
|
|
||||||
for c in steps:
|
|
||||||
dx, dy = convert_dir(c)
|
|
||||||
|
|
||||||
match grid[y + dy, x + dx]:
|
|
||||||
case "#":
|
|
||||||
continue
|
|
||||||
case "O":
|
|
||||||
crashed = False
|
|
||||||
for dist in itertools.count(2):
|
|
||||||
match grid[y + dist * dy, x + dist * dx]:
|
|
||||||
case "O":
|
|
||||||
continue
|
|
||||||
case "#":
|
|
||||||
crashed = True
|
|
||||||
break
|
|
||||||
case _:
|
|
||||||
crashed = False
|
|
||||||
break
|
|
||||||
|
|
||||||
if crashed:
|
|
||||||
continue
|
|
||||||
|
|
||||||
grid[y + dist * dy, x + dist * dx] = "O"
|
|
||||||
case _:
|
|
||||||
pass
|
|
||||||
|
|
||||||
grid[y, x] = "."
|
|
||||||
x += dx
|
|
||||||
y += dy
|
|
||||||
grid[y, x] = "@"
|
|
||||||
|
|
||||||
stones = numpy.where(grid == "O")
|
|
||||||
|
|
||||||
return sum(100 * y + x for y, x in zip(*stones))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, input: str) -> None:
|
|
||||||
input = input.replace(".", "..")
|
|
||||||
input = input.replace("#", "##")
|
|
||||||
input = input.replace("O", "[]")
|
|
||||||
input = input.replace("@", "@.")
|
|
||||||
|
|
||||||
grid, steps = parse_input(input)
|
|
||||||
|
|
||||||
y, x = numpy.where(grid == "@")
|
|
||||||
x, y = x[0], y[0]
|
|
||||||
|
|
||||||
for c in steps:
|
|
||||||
dx, dy = convert_dir(c)
|
|
||||||
|
|
||||||
match grid[y + dy, x + dx]:
|
|
||||||
case "#":
|
|
||||||
continue
|
|
||||||
case "]" | "[":
|
|
||||||
crashed = False
|
|
||||||
if dy == 0:
|
|
||||||
# easy case: just move linearly
|
|
||||||
for dist in itertools.count(2):
|
|
||||||
match grid[y, x + dist * dx]:
|
|
||||||
case "[" | "]":
|
|
||||||
continue
|
|
||||||
case "#":
|
|
||||||
crashed = True
|
|
||||||
break
|
|
||||||
case _:
|
|
||||||
break
|
|
||||||
|
|
||||||
if crashed:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# shuffle all grid points one over
|
|
||||||
for steps in range(dist, 1, -1):
|
|
||||||
grid[y, x + dx * steps] = grid[y, x + dx * (steps - 1)]
|
|
||||||
else:
|
|
||||||
if grid[y + dy, x] == "[":
|
|
||||||
to_check = {x, x + 1}
|
|
||||||
else:
|
|
||||||
to_check = {x, x - 1}
|
|
||||||
|
|
||||||
moving_stones = [to_check]
|
|
||||||
|
|
||||||
for dist in itertools.count(2):
|
|
||||||
to_check_next = set()
|
|
||||||
|
|
||||||
for cx in to_check:
|
|
||||||
match grid[y + dist * dy, cx]:
|
|
||||||
case "#":
|
|
||||||
crashed = True
|
|
||||||
break
|
|
||||||
case "[":
|
|
||||||
to_check_next.add(cx)
|
|
||||||
to_check_next.add(cx + 1)
|
|
||||||
case "]":
|
|
||||||
to_check_next.add(cx)
|
|
||||||
to_check_next.add(cx - 1)
|
|
||||||
case _:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if crashed or not to_check_next:
|
|
||||||
break
|
|
||||||
moving_stones.append(to_check_next)
|
|
||||||
to_check = to_check_next
|
|
||||||
|
|
||||||
if crashed:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for steps in range(len(moving_stones), 0, -1):
|
|
||||||
dist = steps + 1
|
|
||||||
for cx in moving_stones[steps - 1]:
|
|
||||||
grid[y + dy * dist, cx] = grid[y + dy * (dist - 1), cx]
|
|
||||||
grid[y + dy * (dist - 1), cx] = "."
|
|
||||||
case _:
|
|
||||||
pass
|
|
||||||
|
|
||||||
grid[y, x] = "."
|
|
||||||
x += dx
|
|
||||||
y += dy
|
|
||||||
grid[y, x] = "@"
|
|
||||||
|
|
||||||
stones = numpy.where(grid == "[")
|
|
||||||
|
|
||||||
return sum(100 * y + x for y, x in zip(*stones))
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import heapq
|
|
||||||
|
|
||||||
import numpy
|
|
||||||
|
|
||||||
from . import CombinedRunner
|
|
||||||
|
|
||||||
TURNS = (
|
|
||||||
(-1, 1),
|
|
||||||
(1, -1),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(CombinedRunner):
|
|
||||||
@classmethod
|
|
||||||
def run_both(cls, input: str) -> tuple[int, int]:
|
|
||||||
grid = numpy.array([list(line) for line in input.strip().split("\n")])
|
|
||||||
|
|
||||||
y, x = numpy.where(grid == "S")
|
|
||||||
x, y = x[0], y[0]
|
|
||||||
|
|
||||||
todo = [(0, x, y, 1, 0)]
|
|
||||||
best = {
|
|
||||||
(x, y, 1, 0): (0, []),
|
|
||||||
}
|
|
||||||
|
|
||||||
def enqueue(dist, x, y, dx, dy, cx, cy, cdx, cdy):
|
|
||||||
if grid[y, x] == "#":
|
|
||||||
return
|
|
||||||
|
|
||||||
if (x, y, dx, dy) not in best or best[x, y, dx, dy][0] > dist:
|
|
||||||
best[x, y, dx, dy] = (dist, [(cx, cy, cdx, cdy)])
|
|
||||||
heapq.heappush(todo, (dist, x, y, dx, dy))
|
|
||||||
elif best[x, y, dx, dy][0] == dist:
|
|
||||||
best[x, y, dx, dy][1].append((cx, cy, cdx, cdy))
|
|
||||||
|
|
||||||
shortest_dist = None
|
|
||||||
finishes = set()
|
|
||||||
|
|
||||||
while todo:
|
|
||||||
dist, x, y, dx, dy = heapq.heappop(todo)
|
|
||||||
|
|
||||||
if best[x, y, dx, dy][0] < dist:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if shortest_dist is not None and shortest_dist < dist:
|
|
||||||
break
|
|
||||||
|
|
||||||
if grid[y, x] == "E":
|
|
||||||
shortest_dist = dist
|
|
||||||
finishes.add((x, y, dx, dy))
|
|
||||||
|
|
||||||
enqueue(dist + 1, x + dx, y + dy, dx, dy, x, y, dx, dy)
|
|
||||||
enqueue(dist + 2001, x - dx, y - dy, dx, dy, x, y, dx, dy)
|
|
||||||
|
|
||||||
for tx, ty in TURNS:
|
|
||||||
ndx = dy * ty
|
|
||||||
ndy = dx * ty
|
|
||||||
|
|
||||||
enqueue(dist + 1001, x + ndx, y + ndy, ndx, ndy, x, y, dx, dy)
|
|
||||||
|
|
||||||
assert shortest_dist is not None, "Should find a path to the exit"
|
|
||||||
|
|
||||||
visited_tiles = {(x, y) for x, y, _, _ in finishes}
|
|
||||||
todo2 = [f for f in finishes]
|
|
||||||
visited_states = set(todo2)
|
|
||||||
|
|
||||||
while todo2:
|
|
||||||
state = todo2.pop()
|
|
||||||
|
|
||||||
for prev in best[state][1]:
|
|
||||||
if prev not in visited_states:
|
|
||||||
visited_states.add(prev)
|
|
||||||
visited_tiles.add((prev[0], prev[1]))
|
|
||||||
todo2.append(prev)
|
|
||||||
|
|
||||||
return shortest_dist, len(visited_tiles)
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
import re
|
|
||||||
|
|
||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
|
|
||||||
def run_program(
|
|
||||||
register_a: int, register_b: int, register_c: int, program: list[int]
|
|
||||||
) -> list[int]:
|
|
||||||
ip = 0
|
|
||||||
out = []
|
|
||||||
|
|
||||||
def combo(index: int) -> int:
|
|
||||||
match program[index]:
|
|
||||||
case 0:
|
|
||||||
return 0
|
|
||||||
case 1:
|
|
||||||
return 1
|
|
||||||
case 2:
|
|
||||||
return 2
|
|
||||||
case 3:
|
|
||||||
return 3
|
|
||||||
case 4:
|
|
||||||
return register_a
|
|
||||||
case 5:
|
|
||||||
return register_b
|
|
||||||
case 6:
|
|
||||||
return register_c
|
|
||||||
|
|
||||||
while ip < len(program):
|
|
||||||
match program[ip]:
|
|
||||||
case 0: # adv
|
|
||||||
register_a = register_a // 2 ** combo(ip + 1)
|
|
||||||
case 1: # bxl
|
|
||||||
register_b ^= program[ip + 1]
|
|
||||||
case 2: # bst
|
|
||||||
register_b = combo(ip + 1) & 0x7
|
|
||||||
case 3: # jnz
|
|
||||||
if register_a != 0:
|
|
||||||
ip = program[ip + 1]
|
|
||||||
continue
|
|
||||||
case 4: # bxc
|
|
||||||
register_b ^= register_c
|
|
||||||
case 5: # out
|
|
||||||
out.append(combo(ip + 1) & 7)
|
|
||||||
case 6: # bdv
|
|
||||||
register_b = register_a // 2 ** combo(ip + 1)
|
|
||||||
case 7: # cdv
|
|
||||||
register_c = register_a // 2 ** combo(ip + 1)
|
|
||||||
ip += 2
|
|
||||||
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(cls, input: str) -> str:
|
|
||||||
numbers = re.findall(r"\d+", input)
|
|
||||||
|
|
||||||
register_a, register_b, register_c = map(int, numbers[:3])
|
|
||||||
program = list(map(int, numbers[3:]))
|
|
||||||
|
|
||||||
out = run_program(register_a, register_b, register_c, program)
|
|
||||||
|
|
||||||
return ",".join(map(str, out))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, input: str) -> int:
|
|
||||||
numbers = re.findall(r"\d+", input)
|
|
||||||
|
|
||||||
_, register_b, register_c = map(int, numbers[:3])
|
|
||||||
program = list(map(int, numbers[3:]))
|
|
||||||
|
|
||||||
cur = [0]
|
|
||||||
|
|
||||||
# It came to me in a dream
|
|
||||||
for entry in reversed(program):
|
|
||||||
next_gen = []
|
|
||||||
|
|
||||||
for num in cur:
|
|
||||||
num *= 8
|
|
||||||
for n in range(8):
|
|
||||||
output = run_program(num + n, register_b, register_c, program)
|
|
||||||
result = output[0]
|
|
||||||
if result == entry:
|
|
||||||
next_gen.append(num + n)
|
|
||||||
|
|
||||||
cur = next_gen
|
|
||||||
|
|
||||||
return cur[0]
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
from collections import deque
|
|
||||||
|
|
||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
|
|
||||||
def parse_input(data: str) -> list[tuple[int, int]]:
|
|
||||||
return [tuple(map(int, line.split(","))) for line in data.strip().split("\n")]
|
|
||||||
|
|
||||||
|
|
||||||
def find_exit(fallen: set[tuple[int, int]], width: int, height: int) -> int | None:
|
|
||||||
todo = deque([(0, 0, 0)])
|
|
||||||
|
|
||||||
best = {(0, 0): 0}
|
|
||||||
|
|
||||||
def enqueue(dist: int, x: int, y: int):
|
|
||||||
# print(f"trying {x},{y}")
|
|
||||||
if (x, y) in fallen:
|
|
||||||
return
|
|
||||||
|
|
||||||
if (x, y) not in best or best[x, y] > dist:
|
|
||||||
best[x, y] = dist
|
|
||||||
todo.append((dist, x, y))
|
|
||||||
|
|
||||||
while todo:
|
|
||||||
dist, x, y = todo.popleft()
|
|
||||||
# print(x, y)
|
|
||||||
|
|
||||||
if x == width - 1 and y == height - 1:
|
|
||||||
return dist
|
|
||||||
|
|
||||||
if x > 0:
|
|
||||||
enqueue(dist + 1, x - 1, y)
|
|
||||||
|
|
||||||
if x + 1 < width:
|
|
||||||
enqueue(dist + 1, x + 1, y)
|
|
||||||
|
|
||||||
if y > 0:
|
|
||||||
enqueue(dist + 1, x, y - 1)
|
|
||||||
|
|
||||||
if y + 1 < height:
|
|
||||||
enqueue(dist + 1, x, y + 1)
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(
|
|
||||||
cls, input: str, width: int = 71, height: int = 71, limit: int = 1024
|
|
||||||
) -> int:
|
|
||||||
falling = parse_input(input)
|
|
||||||
|
|
||||||
return find_exit(set(falling[:limit]), width, height)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, input: str, width: int = 71, height: int = 71) -> str:
|
|
||||||
falling = parse_input(input)
|
|
||||||
|
|
||||||
lower = 0
|
|
||||||
upper = len(falling)
|
|
||||||
|
|
||||||
while lower < upper:
|
|
||||||
mid = lower + (upper - lower) // 2
|
|
||||||
|
|
||||||
if find_exit(set(falling[:mid]), width, height) is not None:
|
|
||||||
lower = mid + 1
|
|
||||||
else:
|
|
||||||
upper = mid
|
|
||||||
|
|
||||||
first_blocker = falling[lower - 1]
|
|
||||||
|
|
||||||
return f"{first_blocker[0]},{first_blocker[1]}"
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import collections
|
|
||||||
import functools
|
|
||||||
|
|
||||||
from . import CombinedRunner
|
|
||||||
|
|
||||||
|
|
||||||
def parse_input(data: str) -> tuple[list[str], list[str]]:
|
|
||||||
patterns, designs = data.strip().split("\n\n")
|
|
||||||
|
|
||||||
return patterns.split(", "), designs.split("\n")
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(CombinedRunner):
|
|
||||||
@classmethod
|
|
||||||
def run_both(cls, input: str) -> int:
|
|
||||||
patterns, designs = parse_input(input)
|
|
||||||
|
|
||||||
by_prefix = collections.defaultdict(list)
|
|
||||||
for prefix in patterns:
|
|
||||||
by_prefix[prefix[0]].append(prefix)
|
|
||||||
|
|
||||||
possible = 0
|
|
||||||
ways = 0
|
|
||||||
|
|
||||||
@functools.cache
|
|
||||||
def is_possible(design: str) -> bool:
|
|
||||||
if not design:
|
|
||||||
return 1
|
|
||||||
else:
|
|
||||||
return sum(
|
|
||||||
is_possible(design[len(prefix) :])
|
|
||||||
for prefix in by_prefix[design[0]]
|
|
||||||
if design.startswith(prefix)
|
|
||||||
)
|
|
||||||
|
|
||||||
for design in designs:
|
|
||||||
if (solve := is_possible(design)) > 0:
|
|
||||||
possible += 1
|
|
||||||
ways += solve
|
|
||||||
|
|
||||||
return possible, ways
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import numpy
|
|
||||||
|
|
||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
|
|
||||||
def _safe(nums: numpy.ndarray) -> bool:
|
|
||||||
steps = nums[1:] - nums[:-1]
|
|
||||||
|
|
||||||
if numpy.all(steps > 0):
|
|
||||||
return numpy.all((steps >= 1) & (steps <= 3))
|
|
||||||
elif numpy.all(steps < 0):
|
|
||||||
return numpy.all((steps <= -1) & (steps >= -3))
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def is_safe(line: str) -> bool:
|
|
||||||
nums = numpy.fromstring(line, dtype=numpy.int32, sep=" ")
|
|
||||||
|
|
||||||
return _safe(nums)
|
|
||||||
|
|
||||||
|
|
||||||
def is_savable(line: str) -> bool:
|
|
||||||
nums = numpy.fromstring(line, dtype=numpy.int32, sep=" ")
|
|
||||||
|
|
||||||
return any(
|
|
||||||
_safe(numpy.concatenate((nums[:i], nums[i + 1 :]), axis=None))
|
|
||||||
for i in range(len(nums))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(cls, data: str) -> int:
|
|
||||||
lines = data.strip().split("\n")
|
|
||||||
|
|
||||||
safe = sum(1 for line in lines if is_safe(line))
|
|
||||||
|
|
||||||
return safe
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, data: str) -> int:
|
|
||||||
lines = data.strip().split("\n")
|
|
||||||
|
|
||||||
safe = sum(1 for line in lines if is_savable(line))
|
|
||||||
|
|
||||||
return safe
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
import itertools
|
|
||||||
|
|
||||||
import numpy
|
|
||||||
|
|
||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
DIRECTIONS = [
|
|
||||||
(-1, 0),
|
|
||||||
(1, 0),
|
|
||||||
(0, -1),
|
|
||||||
(0, 1),
|
|
||||||
]
|
|
||||||
|
|
||||||
CHEATS = [
|
|
||||||
(-2, 0),
|
|
||||||
(2, 0),
|
|
||||||
(0, -2),
|
|
||||||
(0, 2),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def parse_path(input: str) -> dict[tuple[int, int], int]:
|
|
||||||
grid = numpy.array(list(map(list, input.strip().split("\n"))))
|
|
||||||
|
|
||||||
ys, xs = numpy.nonzero(grid == "S")
|
|
||||||
sx, sy = int(xs[0]), int(ys[0])
|
|
||||||
|
|
||||||
nx, ny = sx, sy
|
|
||||||
|
|
||||||
path = {
|
|
||||||
(sx, sy): 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
while grid[ny, nx] != "E":
|
|
||||||
x, y = nx, ny
|
|
||||||
|
|
||||||
for dx, dy in DIRECTIONS:
|
|
||||||
if grid[y + dy, x + dx] == "#" or (x + dx, y + dy) in path:
|
|
||||||
continue
|
|
||||||
nx = x + dx
|
|
||||||
ny = y + dy
|
|
||||||
break
|
|
||||||
|
|
||||||
path[nx, ny] = len(path)
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
|
||||||
def get_savings(a: tuple[tuple[int, int], int], b: tuple[tuple[int, int], int]) -> int:
|
|
||||||
(ax, ay), ad = a
|
|
||||||
(bx, by), bd = b
|
|
||||||
|
|
||||||
dist = abs(bx - ax) + abs(by - ay)
|
|
||||||
if dist <= 20:
|
|
||||||
return bd - ad - dist
|
|
||||||
else:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(cls, input: str, limit: int = 100) -> int:
|
|
||||||
path = parse_path(input)
|
|
||||||
|
|
||||||
total = 0
|
|
||||||
|
|
||||||
for (px, py), dist in path.items():
|
|
||||||
for dx, dy in CHEATS:
|
|
||||||
if (other := path.get((px + dx, py + dy))) is not None:
|
|
||||||
savings = dist - other - 2
|
|
||||||
if savings >= limit:
|
|
||||||
total += 1
|
|
||||||
|
|
||||||
return total
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, input: str, limit: int = 100) -> int:
|
|
||||||
path = parse_path(input)
|
|
||||||
|
|
||||||
return sum(
|
|
||||||
get_savings(a, b) >= limit
|
|
||||||
for a, b in itertools.combinations(path.items(), 2)
|
|
||||||
)
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
import functools
|
|
||||||
from collections import Counter, defaultdict
|
|
||||||
|
|
||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
NUMPAD = {
|
|
||||||
"A": (3, 2),
|
|
||||||
"0": (3, 1),
|
|
||||||
"1": (2, 0),
|
|
||||||
"2": (2, 1),
|
|
||||||
"3": (2, 2),
|
|
||||||
"4": (1, 0),
|
|
||||||
"5": (1, 1),
|
|
||||||
"6": (1, 2),
|
|
||||||
"7": (0, 0),
|
|
||||||
"8": (0, 1),
|
|
||||||
"9": (0, 2),
|
|
||||||
}
|
|
||||||
|
|
||||||
DIRPAD = {
|
|
||||||
"A": (0, 2),
|
|
||||||
"^": (0, 1),
|
|
||||||
"<": (1, 0),
|
|
||||||
"v": (1, 1),
|
|
||||||
">": (1, 2),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@functools.cache
|
|
||||||
def shortest_numpad(from_: str, to: str) -> list[str]:
|
|
||||||
inverse = set(NUMPAD.values())
|
|
||||||
ay, ax = NUMPAD[from_]
|
|
||||||
by, bx = NUMPAD[to]
|
|
||||||
|
|
||||||
dx, dy = bx - ax, by - ay
|
|
||||||
|
|
||||||
sx = "<" if dx < 0 else ">"
|
|
||||||
sy = "^" if dy < 0 else "v"
|
|
||||||
|
|
||||||
if dx > 0 and (by, ax) in inverse or (ay, bx) not in inverse:
|
|
||||||
return abs(dy) * sy + abs(dx) * sx + "A"
|
|
||||||
else:
|
|
||||||
return abs(dx) * sx + abs(dy) * sy + "A"
|
|
||||||
|
|
||||||
|
|
||||||
@functools.cache
|
|
||||||
def shortest_dirpad(from_: str, to: str) -> str:
|
|
||||||
inverse = set(DIRPAD.values())
|
|
||||||
ay, ax = DIRPAD[from_]
|
|
||||||
by, bx = DIRPAD[to]
|
|
||||||
|
|
||||||
dx, dy = bx - ax, by - ay
|
|
||||||
sx = "<" if dx < 0 else ">"
|
|
||||||
sy = "^" if dy < 0 else "v"
|
|
||||||
|
|
||||||
if dx > 0 and (by, ax) in inverse or (ay, bx) not in inverse:
|
|
||||||
return abs(dy) * sy + abs(dx) * sx + "A"
|
|
||||||
else:
|
|
||||||
return abs(dx) * sx + abs(dy) * sy + "A"
|
|
||||||
|
|
||||||
|
|
||||||
def encode_shortest_numpad(code: str) -> str:
|
|
||||||
pos = "A"
|
|
||||||
|
|
||||||
res = ""
|
|
||||||
|
|
||||||
for c in code:
|
|
||||||
res += shortest_numpad(pos, c)
|
|
||||||
# print(c, res)
|
|
||||||
pos = c
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def encode_shortest_dirpad(code: str) -> str:
|
|
||||||
pos = "A"
|
|
||||||
|
|
||||||
res = ""
|
|
||||||
|
|
||||||
for c in code:
|
|
||||||
if pos != c:
|
|
||||||
res += shortest_dirpad(pos, c)
|
|
||||||
else:
|
|
||||||
res += "A"
|
|
||||||
pos = c
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
def decode(code: str, pad: dict[str, tuple[int, int]]) -> str:
|
|
||||||
result = ""
|
|
||||||
inverse = {v: k for k, v in pad.items()}
|
|
||||||
|
|
||||||
y, x = pad["A"]
|
|
||||||
|
|
||||||
for i, c in enumerate(code):
|
|
||||||
match c:
|
|
||||||
case "A":
|
|
||||||
result += inverse[y, x]
|
|
||||||
case "^":
|
|
||||||
y -= 1
|
|
||||||
case "v":
|
|
||||||
y += 1
|
|
||||||
case "<":
|
|
||||||
x -= 1
|
|
||||||
case ">":
|
|
||||||
x += 1
|
|
||||||
|
|
||||||
if (y, x) not in inverse:
|
|
||||||
raise ValueError(
|
|
||||||
f"""Moved off the board {x, y}, after processing {c}.
|
|
||||||
Path so far: {result} (from {code[:i]})"""
|
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def count_steps(path: str, count: int) -> dict[str, int]:
|
|
||||||
cur = "A"
|
|
||||||
counts = defaultdict(int)
|
|
||||||
|
|
||||||
for c in path:
|
|
||||||
step = shortest_dirpad(cur, c)
|
|
||||||
cur = c
|
|
||||||
counts[step] += count
|
|
||||||
|
|
||||||
return counts
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(cls, input: str) -> int:
|
|
||||||
result = 0
|
|
||||||
for code in input.strip().split("\n"):
|
|
||||||
numpad = encode_shortest_numpad(code)
|
|
||||||
robot1 = encode_shortest_dirpad(numpad)
|
|
||||||
robot2 = encode_shortest_dirpad(robot1)
|
|
||||||
|
|
||||||
result += int(code[:-1]) * len(robot2)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, input: str, robots=25) -> int:
|
|
||||||
result = 0
|
|
||||||
for code in input.strip().split("\n"):
|
|
||||||
numpad = encode_shortest_numpad(code)
|
|
||||||
keypresses = Counter([numpad])
|
|
||||||
|
|
||||||
for _ in range(robots + 1):
|
|
||||||
new_presses = Counter()
|
|
||||||
for subroute, count in keypresses.items():
|
|
||||||
new_presses.update(count_steps(subroute, count))
|
|
||||||
|
|
||||||
keypresses = new_presses
|
|
||||||
|
|
||||||
result += int(code[:-1]) * keypresses.total()
|
|
||||||
|
|
||||||
return result
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
import numpy
|
|
||||||
from numpy.lib.stride_tricks import sliding_window_view
|
|
||||||
|
|
||||||
from . import CombinedRunner
|
|
||||||
|
|
||||||
|
|
||||||
def advance(secrets: numpy.array) -> numpy.array:
|
|
||||||
new_secrets = (secrets ^ (secrets << 6)) & 0xFFFFFF
|
|
||||||
new_secrets ^= new_secrets >> 5
|
|
||||||
new_secrets ^= new_secrets << 11
|
|
||||||
new_secrets &= 0xFFFFFF
|
|
||||||
|
|
||||||
return new_secrets
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(CombinedRunner):
|
|
||||||
@classmethod
|
|
||||||
def run_both(cls, input: str) -> tuple[int, int]:
|
|
||||||
secrets = numpy.fromstring(input, dtype=int, sep="\n")
|
|
||||||
|
|
||||||
progression = [secrets]
|
|
||||||
|
|
||||||
for _ in range(2000):
|
|
||||||
secrets = advance(secrets)
|
|
||||||
progression.append(secrets)
|
|
||||||
|
|
||||||
field = numpy.stack(progression, axis=-1) % 10
|
|
||||||
delta = field[:, 1:] - field[:, :-1]
|
|
||||||
windows = sliding_window_view(delta, 4, axis=1)
|
|
||||||
|
|
||||||
per_signal = defaultdict(int)
|
|
||||||
|
|
||||||
for row_scores, row_deltas in zip(field, windows):
|
|
||||||
unique, positions = numpy.unique(row_deltas, return_index=True, axis=0)
|
|
||||||
|
|
||||||
for key, bananas in zip(unique, row_scores[positions + 4]):
|
|
||||||
per_signal[tuple(key)] += bananas
|
|
||||||
|
|
||||||
return secrets.sum(), max(per_signal.values())
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
import networkx
|
|
||||||
|
|
||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(cls, input: str) -> int:
|
|
||||||
edges = defaultdict(set)
|
|
||||||
|
|
||||||
for line in input.strip().split("\n"):
|
|
||||||
a, b = line.split("-")
|
|
||||||
edges[a].add(b)
|
|
||||||
edges[b].add(a)
|
|
||||||
|
|
||||||
found = set()
|
|
||||||
|
|
||||||
for a, out in edges.items():
|
|
||||||
if a[0] != "t":
|
|
||||||
continue
|
|
||||||
|
|
||||||
for b in out:
|
|
||||||
for c in edges[b]:
|
|
||||||
if c in out:
|
|
||||||
found.add(tuple(sorted([a, b, c])))
|
|
||||||
|
|
||||||
return len(found)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, input: str) -> str:
|
|
||||||
graph = networkx.Graph()
|
|
||||||
|
|
||||||
for line in input.strip().split("\n"):
|
|
||||||
a, b = line.split("-")
|
|
||||||
graph.add_edge(a, b)
|
|
||||||
|
|
||||||
cliques = networkx.find_cliques(graph)
|
|
||||||
max_clique = max(cliques, key=len)
|
|
||||||
|
|
||||||
return ",".join(sorted(max_clique))
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
import functools
|
|
||||||
import re
|
|
||||||
|
|
||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
|
|
||||||
def parse_input(input: str) -> tuple[dict[str, int], dict[str, tuple[str, str, str]]]:
|
|
||||||
variable_part, rules_part = input.strip().split("\n\n")
|
|
||||||
|
|
||||||
variables = {}
|
|
||||||
|
|
||||||
for line in variable_part.splitlines():
|
|
||||||
variable, value = line.split(": ")
|
|
||||||
variables[variable] = int(value)
|
|
||||||
|
|
||||||
rules = {}
|
|
||||||
|
|
||||||
for first, op, second, result in re.findall(
|
|
||||||
r"(\w+) (XOR|OR|AND) (\w+) -> (\w+)", rules_part
|
|
||||||
):
|
|
||||||
rules[result] = (first, op, second)
|
|
||||||
|
|
||||||
return variables, rules
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(cls, input: str) -> int:
|
|
||||||
variables, rules = parse_input(input)
|
|
||||||
|
|
||||||
@functools.cache
|
|
||||||
def get_value(variable: str) -> int:
|
|
||||||
if variable in variables:
|
|
||||||
return variables[variable]
|
|
||||||
|
|
||||||
first, op, second = rules[variable]
|
|
||||||
first_v = get_value(first)
|
|
||||||
second_v = get_value(second)
|
|
||||||
|
|
||||||
match op:
|
|
||||||
case "AND":
|
|
||||||
return first_v & second_v
|
|
||||||
case "OR":
|
|
||||||
return first_v | second_v
|
|
||||||
case "XOR":
|
|
||||||
return first_v ^ second_v
|
|
||||||
|
|
||||||
result = 0
|
|
||||||
for variable in reversed(sorted(rules)):
|
|
||||||
if not variable.startswith("z"):
|
|
||||||
continue
|
|
||||||
result = result * 2 + get_value(variable)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, input: str) -> str:
|
|
||||||
variables, rules = parse_input(input)
|
|
||||||
|
|
||||||
max_bit = int(
|
|
||||||
max(variable for variable in rules if variable.startswith("z"))[1:]
|
|
||||||
)
|
|
||||||
|
|
||||||
def find_invalid(output: str, pattern) -> set[str]:
|
|
||||||
if pattern is None:
|
|
||||||
return set()
|
|
||||||
|
|
||||||
if output in rules:
|
|
||||||
left, op, right = rules[output]
|
|
||||||
elif output == pattern:
|
|
||||||
return set()
|
|
||||||
else:
|
|
||||||
return {output}
|
|
||||||
|
|
||||||
pop, pleft, pright = pattern
|
|
||||||
|
|
||||||
if op != pop:
|
|
||||||
return {output}
|
|
||||||
|
|
||||||
wrong_normal = find_invalid(left, pleft) | find_invalid(right, pright)
|
|
||||||
wrong_mirror = find_invalid(left, pright) | find_invalid(right, pleft)
|
|
||||||
|
|
||||||
least_wrong = min(wrong_mirror, wrong_normal, key=len)
|
|
||||||
|
|
||||||
return least_wrong
|
|
||||||
|
|
||||||
assert max_bit >= 3
|
|
||||||
|
|
||||||
# First one is a half adder, that's a simple pattern
|
|
||||||
invalid = find_invalid("z00", ["XOR", "x00", "y00"])
|
|
||||||
# Second one is missing a reference to the before-previous adder, so it's a
|
|
||||||
# slightly different patterns
|
|
||||||
invalid |= find_invalid(
|
|
||||||
"z01", ["XOR", ["AND", "x00", "y00"], ["XOR", "x01", "y01"]]
|
|
||||||
)
|
|
||||||
# Needed a second lookback to determine that `ktp` is valid
|
|
||||||
invalid |= find_invalid(
|
|
||||||
"z02",
|
|
||||||
[
|
|
||||||
"XOR",
|
|
||||||
["XOR", "x02", "y02"],
|
|
||||||
[
|
|
||||||
"OR",
|
|
||||||
["AND", "x01", "y01"],
|
|
||||||
["AND", ["XOR", "x01", "y01"], ["AND", "x00", "y00"]],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
for n in range(3, max_bit):
|
|
||||||
xcurr = f"x{n:02}"
|
|
||||||
ycurr = f"y{n:02}"
|
|
||||||
zcurr = f"z{n:02}"
|
|
||||||
xprev = f"x{n-1:02}"
|
|
||||||
yprev = f"y{n-1:02}"
|
|
||||||
|
|
||||||
invalid |= find_invalid(
|
|
||||||
zcurr,
|
|
||||||
[
|
|
||||||
"XOR",
|
|
||||||
["XOR", xcurr, ycurr],
|
|
||||||
[
|
|
||||||
"OR",
|
|
||||||
["AND", xprev, yprev],
|
|
||||||
["AND", ["XOR", xprev, yprev], ["OR", None, None]],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
# This code somehow believes `ktp` is invalid, but it's fine on closer
|
|
||||||
# inspection. Will figure that out later.
|
|
||||||
|
|
||||||
return ",".join(sorted(invalid))
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import numpy
|
|
||||||
|
|
||||||
from . import CombinedRunner
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(CombinedRunner):
|
|
||||||
@classmethod
|
|
||||||
def run_both(cls, input: str) -> tuple[int, None]:
|
|
||||||
blocks = input.strip().split("\n\n")
|
|
||||||
|
|
||||||
keys = []
|
|
||||||
locks = []
|
|
||||||
|
|
||||||
for block in blocks:
|
|
||||||
grid = numpy.array(list(map(list, block.splitlines())))
|
|
||||||
heights = numpy.count_nonzero(grid == "#", axis=0)
|
|
||||||
|
|
||||||
if block.startswith("#####"):
|
|
||||||
locks.append(heights)
|
|
||||||
else:
|
|
||||||
keys.append(heights)
|
|
||||||
|
|
||||||
locks = numpy.stack(locks, axis=0)
|
|
||||||
|
|
||||||
fitting = sum(
|
|
||||||
numpy.count_nonzero(numpy.all((key + locks) <= 7, axis=1)) for key in keys
|
|
||||||
)
|
|
||||||
|
|
||||||
return fitting, None
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import re
|
|
||||||
|
|
||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(cls, data: str) -> int:
|
|
||||||
return sum(int(a) * int(b) for a, b in re.findall(r"mul\((\d+),(\d+)\)", data))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, data: str) -> int:
|
|
||||||
do = True
|
|
||||||
total = 0
|
|
||||||
for op, a, b in re.findall(r"(don't\(\)|do\(\)|mul\((\d+),(\d+)\))", data):
|
|
||||||
match op:
|
|
||||||
case "do()":
|
|
||||||
do = True
|
|
||||||
case "don't()":
|
|
||||||
do = False
|
|
||||||
case _:
|
|
||||||
if do:
|
|
||||||
total += int(a) * int(b)
|
|
||||||
|
|
||||||
return total
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import numpy
|
|
||||||
|
|
||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(cls, input: str) -> int:
|
|
||||||
grid = numpy.array(list(map(list, input.strip().split("\n"))))
|
|
||||||
|
|
||||||
found = 0
|
|
||||||
|
|
||||||
directions = [
|
|
||||||
(-1, -1),
|
|
||||||
(-1, 0),
|
|
||||||
(-1, 1),
|
|
||||||
(0, -1),
|
|
||||||
(0, 1),
|
|
||||||
(1, -1),
|
|
||||||
(1, 0),
|
|
||||||
(1, 1),
|
|
||||||
]
|
|
||||||
|
|
||||||
word = "XMAS"
|
|
||||||
|
|
||||||
for y in range(grid.shape[0]):
|
|
||||||
for x in range(grid.shape[1]):
|
|
||||||
if grid[y, x] != "X":
|
|
||||||
continue
|
|
||||||
|
|
||||||
for dx, dy in directions:
|
|
||||||
end_x = x + 3 * dx
|
|
||||||
end_y = y + 3 * dy
|
|
||||||
|
|
||||||
if (
|
|
||||||
end_x < 0
|
|
||||||
or end_x >= grid.shape[1]
|
|
||||||
or end_y < 0
|
|
||||||
or end_y >= grid.shape[0]
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if all(
|
|
||||||
grid[y + i * dy, x + i * dx] == c for i, c in enumerate(word)
|
|
||||||
):
|
|
||||||
found += 1
|
|
||||||
|
|
||||||
return found
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, input: str) -> int:
|
|
||||||
grid = numpy.array(list(map(list, input.strip().split("\n"))))
|
|
||||||
|
|
||||||
found = 0
|
|
||||||
|
|
||||||
magic = ord("M") ^ ord("S")
|
|
||||||
|
|
||||||
for y in range(1, grid.shape[0] - 1):
|
|
||||||
for x in range(1, grid.shape[1] - 1):
|
|
||||||
if grid[y, x] != "A":
|
|
||||||
continue
|
|
||||||
|
|
||||||
first_diag = ord(grid[y - 1, x - 1]) ^ ord(grid[y + 1, x + 1])
|
|
||||||
secnd_diag = ord(grid[y - 1, x + 1]) ^ ord(grid[y + 1, x - 1])
|
|
||||||
|
|
||||||
if first_diag == magic and secnd_diag == magic:
|
|
||||||
found += 1
|
|
||||||
|
|
||||||
return found
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
import functools
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
from . import CombinedRunner
|
|
||||||
|
|
||||||
|
|
||||||
def parse_input(input: str) -> tuple[set[tuple[int, int]], list[list[int]]]:
|
|
||||||
first, second = input.strip().split("\n\n")
|
|
||||||
|
|
||||||
rules = {tuple(int(x) for x in line.split("|")) for line in first.split("\n")}
|
|
||||||
updates = [[int(x) for x in line.split(",")] for line in second.split("\n")]
|
|
||||||
|
|
||||||
return rules, updates
|
|
||||||
|
|
||||||
|
|
||||||
def is_correct(update: list[int], must_after: dict[int, set[int]]) -> bool:
|
|
||||||
forbidden = set()
|
|
||||||
|
|
||||||
for entry in update:
|
|
||||||
if entry in forbidden:
|
|
||||||
return False
|
|
||||||
|
|
||||||
forbidden |= must_after.get(entry, set())
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(CombinedRunner):
|
|
||||||
@classmethod
|
|
||||||
def run_both(cls, input: str) -> int:
|
|
||||||
rules, updates = parse_input(input)
|
|
||||||
|
|
||||||
must_after = defaultdict(set)
|
|
||||||
|
|
||||||
for before, after in rules:
|
|
||||||
must_after[after].add(before)
|
|
||||||
|
|
||||||
correct = 0
|
|
||||||
corrected = 0
|
|
||||||
|
|
||||||
key = functools.cmp_to_key(lambda a, b: -1 if (a, b) in rules else 1)
|
|
||||||
|
|
||||||
for update in updates:
|
|
||||||
if is_correct(update, must_after):
|
|
||||||
correct += update[len(update) // 2]
|
|
||||||
else:
|
|
||||||
update.sort(key=key)
|
|
||||||
corrected += update[len(update) // 2]
|
|
||||||
|
|
||||||
return correct, corrected
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import numpy
|
|
||||||
|
|
||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
|
|
||||||
def does_loop(
|
|
||||||
grid: numpy.array,
|
|
||||||
x: int,
|
|
||||||
y: int,
|
|
||||||
dx: int,
|
|
||||||
dy: int,
|
|
||||||
visited: set[tuple[int, int, int, int]],
|
|
||||||
) -> bool:
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
while y + dy >= 0 and x + dx >= 0 and grid[y + dy, x + dx] == "#":
|
|
||||||
dx, dy = -dy, dx
|
|
||||||
|
|
||||||
x += dx
|
|
||||||
y += dy
|
|
||||||
|
|
||||||
if x < 0 or y < 0:
|
|
||||||
return False
|
|
||||||
|
|
||||||
pos = (x, y, dx, dy)
|
|
||||||
|
|
||||||
if pos in visited:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
visited.add(pos)
|
|
||||||
except IndexError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(cls, input: str) -> int:
|
|
||||||
grid = input.strip().split("\n")
|
|
||||||
|
|
||||||
for y, line in enumerate(grid):
|
|
||||||
if (x := line.find("^")) != -1:
|
|
||||||
break
|
|
||||||
|
|
||||||
dx = 0
|
|
||||||
dy = -1
|
|
||||||
|
|
||||||
visited = {(x, y)}
|
|
||||||
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
nx = x + dx
|
|
||||||
ny = y + dy
|
|
||||||
|
|
||||||
if grid[ny][nx] == "#":
|
|
||||||
dx, dy = -dy, dx
|
|
||||||
else:
|
|
||||||
x, y = nx, ny
|
|
||||||
visited.add((x, y))
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return len(visited)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, input: str) -> int:
|
|
||||||
grid = numpy.array(list(map(list, input.strip().split("\n"))))
|
|
||||||
y, x = numpy.where(grid == "^")
|
|
||||||
|
|
||||||
y = y[0]
|
|
||||||
x = x[0]
|
|
||||||
|
|
||||||
dx = 0
|
|
||||||
dy = -1
|
|
||||||
loops = 0
|
|
||||||
|
|
||||||
visited = {(x, y, dx, dy)}
|
|
||||||
tiles_visited = {(x, y)}
|
|
||||||
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
while y + dy >= 0 and x + dx >= 0 and grid[y + dy, x + dx] == "#":
|
|
||||||
dx, dy = -dy, dx
|
|
||||||
|
|
||||||
nx = x + dx
|
|
||||||
ny = y + dy
|
|
||||||
|
|
||||||
if nx < 0 or ny < 0:
|
|
||||||
break
|
|
||||||
|
|
||||||
if (nx, ny) not in tiles_visited:
|
|
||||||
# check for a loop
|
|
||||||
grid[ny, nx] = "#"
|
|
||||||
if does_loop(grid, x, y, dx, dy, visited.copy()):
|
|
||||||
loops += 1
|
|
||||||
grid[ny, nx] = "L"
|
|
||||||
else:
|
|
||||||
grid[ny, nx] = "."
|
|
||||||
|
|
||||||
x, y = nx, ny
|
|
||||||
tiles_visited.add((x, y))
|
|
||||||
visited.add((x, y, dx, dy))
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return loops
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
|
|
||||||
def parse_input(input: str) -> tuple[int, list[int]]:
|
|
||||||
result = []
|
|
||||||
|
|
||||||
for line in input.strip().split("\n"):
|
|
||||||
test, nums = line.split(": ")
|
|
||||||
result.append((int(test), list(map(int, nums.split(" ")))))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def is_possible(target: int, nums: list[int]) -> bool:
|
|
||||||
if target == 0:
|
|
||||||
return not nums
|
|
||||||
if not nums or target < 0:
|
|
||||||
return False
|
|
||||||
|
|
||||||
tail = nums[-1]
|
|
||||||
remainder = nums[:-1]
|
|
||||||
|
|
||||||
return is_possible(target - tail, remainder) or (
|
|
||||||
target % tail == 0 and is_possible(target // tail, remainder)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def is_possible2(target: int, nums: list[int]) -> bool:
|
|
||||||
if target == 0:
|
|
||||||
return not nums
|
|
||||||
if not nums or target < 0:
|
|
||||||
return False
|
|
||||||
|
|
||||||
tail = nums[-1]
|
|
||||||
remainder = nums[:-1]
|
|
||||||
|
|
||||||
target_str = str(target)
|
|
||||||
tail_str = str(tail)
|
|
||||||
|
|
||||||
return (
|
|
||||||
is_possible2(target - tail, remainder)
|
|
||||||
or (target % tail == 0 and is_possible2(target // tail, remainder))
|
|
||||||
or (
|
|
||||||
target_str.endswith(tail_str)
|
|
||||||
and is_possible2(int(target_str[: -len(str(tail_str))]), remainder)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(cls, input: str) -> int:
|
|
||||||
lines = parse_input(input)
|
|
||||||
|
|
||||||
return sum(target for target, nums in lines if is_possible(target, nums))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, input: str) -> int:
|
|
||||||
lines = parse_input(input)
|
|
||||||
|
|
||||||
return sum(target for target, nums in lines if is_possible2(target, nums))
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import itertools
|
|
||||||
import math
|
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
import numpy
|
|
||||||
|
|
||||||
from . import CombinedRunner
|
|
||||||
|
|
||||||
|
|
||||||
def simplify(vec: numpy.array) -> numpy.array:
|
|
||||||
if vec[0] == 0:
|
|
||||||
return numpy.array([0, 1])
|
|
||||||
elif vec[1] == 0:
|
|
||||||
return numpy.array([0, 1])
|
|
||||||
else:
|
|
||||||
div = math.gcd(*vec)
|
|
||||||
return vec // div
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(CombinedRunner):
|
|
||||||
@classmethod
|
|
||||||
def run_both(cls, input: str) -> int:
|
|
||||||
grid = input.strip().split("\n")
|
|
||||||
height = len(grid)
|
|
||||||
width = len(grid[0])
|
|
||||||
|
|
||||||
antennae = defaultdict(list)
|
|
||||||
|
|
||||||
for y, line in enumerate(grid):
|
|
||||||
for x, c in enumerate(line):
|
|
||||||
if c != ".":
|
|
||||||
antennae[c].append(numpy.array([x, y]))
|
|
||||||
|
|
||||||
antinodes = set()
|
|
||||||
antinodes2 = set()
|
|
||||||
|
|
||||||
def in_bounds(node: numpy.array) -> bool:
|
|
||||||
return 0 <= node[0] < width and 0 <= node[1] < height
|
|
||||||
|
|
||||||
def add(node: numpy.array):
|
|
||||||
if in_bounds(node):
|
|
||||||
antinodes.add(tuple(node))
|
|
||||||
|
|
||||||
def walk(start: numpy.array, step: numpy.array):
|
|
||||||
for pos in itertools.count(start, step):
|
|
||||||
if in_bounds(pos):
|
|
||||||
antinodes2.add(tuple(pos))
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
for values in antennae.values():
|
|
||||||
for a, b in itertools.combinations(values, 2):
|
|
||||||
add(2 * a - b)
|
|
||||||
add(2 * b - a)
|
|
||||||
|
|
||||||
step = simplify(b - a)
|
|
||||||
walk(b, step)
|
|
||||||
walk(a, -step)
|
|
||||||
|
|
||||||
return len(antinodes), len(antinodes2)
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
import heapq
|
|
||||||
|
|
||||||
from . import SeparateRunner
|
|
||||||
|
|
||||||
|
|
||||||
def file_checksum(file_id: int, start: int, length: int) -> int:
|
|
||||||
return file_id * length * (2 * start + length - 1) // 2
|
|
||||||
|
|
||||||
|
|
||||||
class DayRunner(SeparateRunner):
|
|
||||||
@classmethod
|
|
||||||
def part1(cls, input: str) -> int:
|
|
||||||
files = []
|
|
||||||
empty = []
|
|
||||||
|
|
||||||
pos = 0
|
|
||||||
|
|
||||||
for c in input.strip():
|
|
||||||
val = int(c)
|
|
||||||
|
|
||||||
if len(files) == len(empty):
|
|
||||||
files.append((pos, val))
|
|
||||||
else:
|
|
||||||
empty.append((pos, val))
|
|
||||||
|
|
||||||
pos += val
|
|
||||||
|
|
||||||
checksum = 0
|
|
||||||
|
|
||||||
for start, length in empty:
|
|
||||||
while files and length > 0:
|
|
||||||
file_start, file_len = files.pop()
|
|
||||||
if file_start < start:
|
|
||||||
files.append((file_start, file_len))
|
|
||||||
break
|
|
||||||
|
|
||||||
file_id = len(files)
|
|
||||||
|
|
||||||
infill = min(file_len, length)
|
|
||||||
|
|
||||||
checksum += file_checksum(file_id, start, infill)
|
|
||||||
start += infill
|
|
||||||
|
|
||||||
if infill != file_len:
|
|
||||||
files.append((file_start, file_len - infill))
|
|
||||||
|
|
||||||
length -= infill
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
|
|
||||||
for file_id, (file_start, file_len) in enumerate(files):
|
|
||||||
checksum += file_checksum(file_id, file_start, file_len)
|
|
||||||
|
|
||||||
return checksum
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def part2(cls, input: str) -> int:
|
|
||||||
files = []
|
|
||||||
empty = [[] for _ in range(10)]
|
|
||||||
|
|
||||||
pos = 0
|
|
||||||
|
|
||||||
is_file = True
|
|
||||||
|
|
||||||
for c in input.strip():
|
|
||||||
val = int(c)
|
|
||||||
|
|
||||||
if is_file:
|
|
||||||
files.append((pos, val))
|
|
||||||
is_file = False
|
|
||||||
else:
|
|
||||||
# No need for heappush, as we're appending values in order
|
|
||||||
empty[val].append(pos)
|
|
||||||
is_file = True
|
|
||||||
|
|
||||||
pos += val
|
|
||||||
|
|
||||||
checksum = 0
|
|
||||||
|
|
||||||
while files:
|
|
||||||
start, length = files.pop()
|
|
||||||
file_id = len(files)
|
|
||||||
|
|
||||||
best = None
|
|
||||||
best_heap = None
|
|
||||||
|
|
||||||
for i, heap in enumerate(empty[length:]):
|
|
||||||
if not heap or heap[0] > start:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if best is None or best > heap[0]:
|
|
||||||
best = heap[0]
|
|
||||||
best_heap = i + length
|
|
||||||
|
|
||||||
if best is None:
|
|
||||||
# No room to move left, count score at current position
|
|
||||||
checksum += file_checksum(file_id, start, length)
|
|
||||||
else:
|
|
||||||
checksum += file_checksum(file_id, best, length)
|
|
||||||
heapq.heappop(empty[best_heap])
|
|
||||||
|
|
||||||
if length < best_heap:
|
|
||||||
remainder = best_heap - length
|
|
||||||
heapq.heappush(empty[remainder], best + length)
|
|
||||||
|
|
||||||
return checksum
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
def get_data(day: int, sub: int | None = None) -> str:
|
|
||||||
basename = f"{day:02d}" if sub is None else f"{day:02d}.{sub}"
|
|
||||||
sample = os.path.dirname(__file__) + f"/samples/{basename}.txt"
|
|
||||||
with open(sample, mode="rt", encoding="utf-8") as f:
|
|
||||||
return f.read()
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
3 4
|
|
||||||
4 3
|
|
||||||
2 5
|
|
||||||
1 3
|
|
||||||
3 9
|
|
||||||
3 3
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
7 6 4 2 1
|
|
||||||
1 2 7 8 9
|
|
||||||
9 7 6 2 1
|
|
||||||
1 3 2 4 5
|
|
||||||
8 6 4 4 1
|
|
||||||
1 3 6 7 9
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
..X...
|
|
||||||
.SAMX.
|
|
||||||
.A..A.
|
|
||||||
XMAS.S
|
|
||||||
.X....
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
MMMSXXMASM
|
|
||||||
MSAMXMSMSA
|
|
||||||
AMXSXMAAMM
|
|
||||||
MSAMASMSMX
|
|
||||||
XMASAMXAMM
|
|
||||||
XXAMMXXAMA
|
|
||||||
SMSMSASXSS
|
|
||||||
SAXAMASAAA
|
|
||||||
MAMMMXMMMM
|
|
||||||
MXMXAXMASX
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
47|53
|
|
||||||
97|13
|
|
||||||
97|61
|
|
||||||
97|47
|
|
||||||
75|29
|
|
||||||
61|13
|
|
||||||
75|53
|
|
||||||
29|13
|
|
||||||
97|29
|
|
||||||
53|29
|
|
||||||
61|53
|
|
||||||
97|53
|
|
||||||
61|29
|
|
||||||
47|13
|
|
||||||
75|47
|
|
||||||
97|75
|
|
||||||
47|61
|
|
||||||
75|61
|
|
||||||
47|29
|
|
||||||
75|13
|
|
||||||
53|13
|
|
||||||
|
|
||||||
75,47,61,53,29
|
|
||||||
97,61,53,29,13
|
|
||||||
75,29,13
|
|
||||||
75,97,47,61,53
|
|
||||||
61,13,29
|
|
||||||
97,13,75,29,47
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
....#.....
|
|
||||||
.........#
|
|
||||||
..........
|
|
||||||
..#.......
|
|
||||||
.......#..
|
|
||||||
..........
|
|
||||||
.#..^.....
|
|
||||||
........#.
|
|
||||||
#.........
|
|
||||||
......#...
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
190: 10 19
|
|
||||||
3267: 81 40 27
|
|
||||||
83: 17 5
|
|
||||||
156: 15 6
|
|
||||||
7290: 6 8 6 15
|
|
||||||
161011: 16 10 13
|
|
||||||
192: 17 8 14
|
|
||||||
21037: 9 7 18 13
|
|
||||||
292: 11 6 16 20
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
............
|
|
||||||
........0...
|
|
||||||
.....0......
|
|
||||||
.......0....
|
|
||||||
....0.......
|
|
||||||
......A.....
|
|
||||||
............
|
|
||||||
............
|
|
||||||
........A...
|
|
||||||
.........A..
|
|
||||||
............
|
|
||||||
............
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
89010123
|
|
||||||
78121874
|
|
||||||
87430965
|
|
||||||
96549874
|
|
||||||
45678903
|
|
||||||
32019012
|
|
||||||
01329801
|
|
||||||
10456732
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
AAAA
|
|
||||||
BBCD
|
|
||||||
BBCC
|
|
||||||
EEEC
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
OOOOO
|
|
||||||
OXOXO
|
|
||||||
OOOOO
|
|
||||||
OXOXO
|
|
||||||
OOOOO
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
RRRRIICCFF
|
|
||||||
RRRRIICCCF
|
|
||||||
VVRRRCCFFF
|
|
||||||
VVRCCCJFFF
|
|
||||||
VVVVCJJCFE
|
|
||||||
VVIVCCJJEE
|
|
||||||
VVIIICJJEE
|
|
||||||
MIIIIIJJEE
|
|
||||||
MIIISIJEEE
|
|
||||||
MMMISSJEEE
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
Button A: X+94, Y+34
|
|
||||||
Button B: X+22, Y+67
|
|
||||||
Prize: X=8400, Y=5400
|
|
||||||
|
|
||||||
Button A: X+26, Y+66
|
|
||||||
Button B: X+67, Y+21
|
|
||||||
Prize: X=12748, Y=12176
|
|
||||||
|
|
||||||
Button A: X+17, Y+86
|
|
||||||
Button B: X+84, Y+37
|
|
||||||
Prize: X=7870, Y=6450
|
|
||||||
|
|
||||||
Button A: X+69, Y+23
|
|
||||||
Button B: X+27, Y+71
|
|
||||||
Prize: X=18641, Y=10279
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
p=0,4 v=3,-3
|
|
||||||
p=6,3 v=-1,-3
|
|
||||||
p=10,3 v=-1,2
|
|
||||||
p=2,0 v=2,-1
|
|
||||||
p=0,0 v=1,3
|
|
||||||
p=3,0 v=-2,-2
|
|
||||||
p=7,6 v=-1,-3
|
|
||||||
p=3,0 v=-1,-2
|
|
||||||
p=9,3 v=2,3
|
|
||||||
p=7,3 v=-1,2
|
|
||||||
p=2,4 v=2,-3
|
|
||||||
p=9,5 v=-3,-3
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
##########
|
|
||||||
#..O..O.O#
|
|
||||||
#......O.#
|
|
||||||
#.OO..O.O#
|
|
||||||
#..O@..O.#
|
|
||||||
#O#..O...#
|
|
||||||
#O..O..O.#
|
|
||||||
#.OO.O.OO#
|
|
||||||
#....O...#
|
|
||||||
##########
|
|
||||||
|
|
||||||
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
|
|
||||||
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
|
|
||||||
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
|
|
||||||
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
|
|
||||||
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
|
|
||||||
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
|
|
||||||
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
|
|
||||||
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
|
|
||||||
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
|
|
||||||
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
########
|
|
||||||
#..O.O.#
|
|
||||||
##@.O..#
|
|
||||||
#...O..#
|
|
||||||
#.#.O..#
|
|
||||||
#...O..#
|
|
||||||
#......#
|
|
||||||
########
|
|
||||||
|
|
||||||
<^^>>>vv<v>>v<<
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
#######
|
|
||||||
#...#.#
|
|
||||||
#.....#
|
|
||||||
#..OO@#
|
|
||||||
#..O..#
|
|
||||||
#.....#
|
|
||||||
#######
|
|
||||||
|
|
||||||
<vv<<^^<<^^
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
###############
|
|
||||||
#.......#....E#
|
|
||||||
#.#.###.#.###.#
|
|
||||||
#.....#.#...#.#
|
|
||||||
#.###.#####.#.#
|
|
||||||
#.#.#.......#.#
|
|
||||||
#.#.#####.###.#
|
|
||||||
#...........#.#
|
|
||||||
###.#.#####.#.#
|
|
||||||
#...#.....#.#.#
|
|
||||||
#.#.#.###.#.#.#
|
|
||||||
#.....#...#.#.#
|
|
||||||
#.###.#.#.#.#.#
|
|
||||||
#S..#.....#...#
|
|
||||||
###############
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#################
|
|
||||||
#...#...#...#..E#
|
|
||||||
#.#.#.#.#.#.#.#.#
|
|
||||||
#.#.#.#...#...#.#
|
|
||||||
#.#.#.#.###.#.#.#
|
|
||||||
#...#.#.#.....#.#
|
|
||||||
#.#.#.#.#.#####.#
|
|
||||||
#.#...#.#.#.....#
|
|
||||||
#.#.#####.#.###.#
|
|
||||||
#.#.#.......#...#
|
|
||||||
#.#.###.#####.###
|
|
||||||
#.#.#...#.....#.#
|
|
||||||
#.#.#.#####.###.#
|
|
||||||
#.#.#.........#.#
|
|
||||||
#.#.#.#########.#
|
|
||||||
#S#.............#
|
|
||||||
#################
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Register A: 729
|
|
||||||
Register B: 0
|
|
||||||
Register C: 0
|
|
||||||
|
|
||||||
Program: 0,1,5,4,3,0
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
Register A: 2024
|
|
||||||
Register B: 0
|
|
||||||
Register C: 0
|
|
||||||
|
|
||||||
Program: 0,3,5,4,3,0
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
5,4
|
|
||||||
4,2
|
|
||||||
4,5
|
|
||||||
3,0
|
|
||||||
2,1
|
|
||||||
6,3
|
|
||||||
2,4
|
|
||||||
1,5
|
|
||||||
0,6
|
|
||||||
3,3
|
|
||||||
2,6
|
|
||||||
5,1
|
|
||||||
1,2
|
|
||||||
5,5
|
|
||||||
2,5
|
|
||||||
6,5
|
|
||||||
1,4
|
|
||||||
0,4
|
|
||||||
6,4
|
|
||||||
1,1
|
|
||||||
6,1
|
|
||||||
1,0
|
|
||||||
0,5
|
|
||||||
1,6
|
|
||||||
2,0
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
r, wr, b, g, bwu, rb, gb, br
|
|
||||||
|
|
||||||
brwrr
|
|
||||||
bggr
|
|
||||||
gbbr
|
|
||||||
rrbgbr
|
|
||||||
ubwu
|
|
||||||
bwurrg
|
|
||||||
brgr
|
|
||||||
bbrgwb
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
###############
|
|
||||||
#...#...#.....#
|
|
||||||
#.#.#.#.#.###.#
|
|
||||||
#S#...#.#.#...#
|
|
||||||
#######.#.#.###
|
|
||||||
#######.#.#...#
|
|
||||||
#######.#.###.#
|
|
||||||
###..E#...#...#
|
|
||||||
###.#######.###
|
|
||||||
#...###...#...#
|
|
||||||
#.#####.#.###.#
|
|
||||||
#.#...#.#.#...#
|
|
||||||
#.#.#.#.#.#.###
|
|
||||||
#...#...#...###
|
|
||||||
###############
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
029A
|
|
||||||
980A
|
|
||||||
179A
|
|
||||||
456A
|
|
||||||
379A
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
1
|
|
||||||
10
|
|
||||||
100
|
|
||||||
2024
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
1
|
|
||||||
2
|
|
||||||
3
|
|
||||||
2024
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
kh-tc
|
|
||||||
qp-kh
|
|
||||||
de-cg
|
|
||||||
ka-co
|
|
||||||
yn-aq
|
|
||||||
qp-ub
|
|
||||||
cg-tb
|
|
||||||
vc-aq
|
|
||||||
tb-ka
|
|
||||||
wh-tc
|
|
||||||
yn-cg
|
|
||||||
kh-ub
|
|
||||||
ta-co
|
|
||||||
de-co
|
|
||||||
tc-td
|
|
||||||
tb-wq
|
|
||||||
wh-td
|
|
||||||
ta-ka
|
|
||||||
td-qp
|
|
||||||
aq-cg
|
|
||||||
wq-ub
|
|
||||||
ub-vc
|
|
||||||
de-ta
|
|
||||||
wq-aq
|
|
||||||
wq-vc
|
|
||||||
wh-yn
|
|
||||||
ka-de
|
|
||||||
kh-ta
|
|
||||||
co-tc
|
|
||||||
wh-qp
|
|
||||||
tb-vc
|
|
||||||
td-yn
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
x00: 1
|
|
||||||
x01: 0
|
|
||||||
x02: 1
|
|
||||||
x03: 1
|
|
||||||
x04: 0
|
|
||||||
y00: 1
|
|
||||||
y01: 1
|
|
||||||
y02: 1
|
|
||||||
y03: 1
|
|
||||||
y04: 1
|
|
||||||
|
|
||||||
ntg XOR fgs -> mjb
|
|
||||||
y02 OR x01 -> tnw
|
|
||||||
kwq OR kpj -> z05
|
|
||||||
x00 OR x03 -> fst
|
|
||||||
tgd XOR rvg -> z01
|
|
||||||
vdt OR tnw -> bfw
|
|
||||||
bfw AND frj -> z10
|
|
||||||
ffh OR nrd -> bqk
|
|
||||||
y00 AND y03 -> djm
|
|
||||||
y03 OR y00 -> psh
|
|
||||||
bqk OR frj -> z08
|
|
||||||
tnw OR fst -> frj
|
|
||||||
gnj AND tgd -> z11
|
|
||||||
bfw XOR mjb -> z00
|
|
||||||
x03 OR x00 -> vdt
|
|
||||||
gnj AND wpb -> z02
|
|
||||||
x04 AND y00 -> kjc
|
|
||||||
djm OR pbm -> qhw
|
|
||||||
nrd AND vdt -> hwm
|
|
||||||
kjc AND fst -> rvg
|
|
||||||
y04 OR y02 -> fgs
|
|
||||||
y01 AND x02 -> pbm
|
|
||||||
ntg OR kjc -> kwq
|
|
||||||
psh XOR fgs -> tgd
|
|
||||||
qhw XOR tgd -> z09
|
|
||||||
pbm OR djm -> kpj
|
|
||||||
x03 XOR y03 -> ffh
|
|
||||||
x00 XOR y04 -> ntg
|
|
||||||
bfw OR bqk -> z06
|
|
||||||
nrd XOR fgs -> wpb
|
|
||||||
frj XOR qhw -> z04
|
|
||||||
bqk OR frj -> z07
|
|
||||||
y03 OR x01 -> nrd
|
|
||||||
hwm AND bqk -> z03
|
|
||||||
tgd XOR rvg -> z12
|
|
||||||
tnw OR pbm -> gnj
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#####
|
|
||||||
.####
|
|
||||||
.####
|
|
||||||
.####
|
|
||||||
.#.#.
|
|
||||||
.#...
|
|
||||||
.....
|
|
||||||
|
|
||||||
#####
|
|
||||||
##.##
|
|
||||||
.#.##
|
|
||||||
...##
|
|
||||||
...#.
|
|
||||||
...#.
|
|
||||||
.....
|
|
||||||
|
|
||||||
.....
|
|
||||||
#....
|
|
||||||
#....
|
|
||||||
#...#
|
|
||||||
#.#.#
|
|
||||||
#.###
|
|
||||||
#####
|
|
||||||
|
|
||||||
.....
|
|
||||||
.....
|
|
||||||
#.#..
|
|
||||||
###..
|
|
||||||
###.#
|
|
||||||
###.#
|
|
||||||
#####
|
|
||||||
|
|
||||||
.....
|
|
||||||
.....
|
|
||||||
.....
|
|
||||||
#....
|
|
||||||
#.#..
|
|
||||||
#.#.#
|
|
||||||
#####
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
from aoc.days.day1 import DayRunner
|
|
||||||
|
|
||||||
from . import get_data
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part1() -> None:
|
|
||||||
assert DayRunner.part1(get_data(1)) == 11
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part2() -> None:
|
|
||||||
assert DayRunner.part2(get_data(1)) == 31
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
from aoc.days.day2 import DayRunner, is_savable
|
|
||||||
|
|
||||||
from . import get_data
|
|
||||||
|
|
||||||
|
|
||||||
def test_individual_samples() -> None:
|
|
||||||
assert is_savable("7 6 4 2 1")
|
|
||||||
assert not is_savable("1 2 7 8 9")
|
|
||||||
assert not is_savable("9 7 6 2 1")
|
|
||||||
assert is_savable("1 3 2 4 5")
|
|
||||||
assert is_savable("8 6 4 4 1")
|
|
||||||
assert is_savable("1 3 6 7 9")
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part1() -> None:
|
|
||||||
assert DayRunner.part1(get_data(2)) == 2
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part2() -> None:
|
|
||||||
assert DayRunner.part2(get_data(2)) == 4
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
from aoc.days.day3 import DayRunner
|
|
||||||
|
|
||||||
SAMPLE_1 = "xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))"
|
|
||||||
SAMPLE_2 = "xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))"
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part1() -> None:
|
|
||||||
assert DayRunner.part1(SAMPLE_1) == 161
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part2() -> None:
|
|
||||||
assert DayRunner.part2(SAMPLE_2) == 48
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from aoc.days.day4 import DayRunner
|
|
||||||
|
|
||||||
from . import get_data
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"input,answer",
|
|
||||||
[
|
|
||||||
(get_data(4, 1), 4),
|
|
||||||
(get_data(4, 2), 18),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_sample_part1(input: str, answer: int) -> None:
|
|
||||||
assert DayRunner.part1(input) == answer
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part2() -> None:
|
|
||||||
assert DayRunner.part2(get_data(4, 2)) == 9
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
from aoc.days.day5 import DayRunner
|
|
||||||
|
|
||||||
from . import get_data
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part1() -> None:
|
|
||||||
data = get_data(5)
|
|
||||||
|
|
||||||
assert DayRunner.part1(data) == 143
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part2() -> None:
|
|
||||||
data = get_data(5)
|
|
||||||
|
|
||||||
assert DayRunner.part2(data) == 123
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from aoc.days.day6 import DayRunner
|
|
||||||
|
|
||||||
|
|
||||||
def get_data() -> str:
|
|
||||||
sample = os.path.dirname(__file__) + "/samples/06.txt"
|
|
||||||
with open(sample, mode="rt", encoding="utf-8") as f:
|
|
||||||
return f.read()
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part1() -> None:
|
|
||||||
assert DayRunner.part1(get_data()) == 41
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part2() -> None:
|
|
||||||
assert DayRunner.part2(get_data()) == 6
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
from aoc.days.day7 import DayRunner
|
|
||||||
|
|
||||||
from . import get_data
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part1() -> None:
|
|
||||||
assert DayRunner.part1(get_data(7)) == 3749
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part2() -> None:
|
|
||||||
assert DayRunner.part2(get_data(7)) == 11387
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
from aoc.days.day8 import DayRunner
|
|
||||||
|
|
||||||
from . import get_data
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part1() -> None:
|
|
||||||
assert DayRunner.part1(get_data(8)) == 14
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part2() -> None:
|
|
||||||
assert DayRunner.part2(get_data(8)) == 34
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
from aoc.days.day9 import DayRunner
|
|
||||||
|
|
||||||
SAMPLE = "2333133121414131402"
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part1() -> None:
|
|
||||||
assert DayRunner.part1(SAMPLE) == 1928
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part2() -> None:
|
|
||||||
assert DayRunner.part2(SAMPLE) == 2858
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
from aoc.days.day10 import DayRunner
|
|
||||||
|
|
||||||
from . import get_data
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part1() -> None:
|
|
||||||
assert DayRunner.part1(get_data(10)) == 36
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
from aoc.days.day11 import DayRunner
|
|
||||||
|
|
||||||
SAMPLE = "125 17"
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part1() -> None:
|
|
||||||
assert DayRunner.part1(SAMPLE) == 55312
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from aoc.days.day12 import DayRunner
|
|
||||||
|
|
||||||
from . import get_data
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"data,result",
|
|
||||||
[(get_data(12, 1), 140), (get_data(12, 2), 772), (get_data(12, 3), 1930)],
|
|
||||||
)
|
|
||||||
def test_sample_part1(data: str, result: int) -> None:
|
|
||||||
assert DayRunner.part1(data) == result
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
"data,result",
|
|
||||||
[(get_data(12, 1), 80), (get_data(12, 2), 436), (get_data(12, 3), 1206)],
|
|
||||||
)
|
|
||||||
def test_sample_part2(data: str, result: int) -> None:
|
|
||||||
assert DayRunner.part2(data) == result
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
from aoc.days.day13 import DayRunner
|
|
||||||
|
|
||||||
from . import get_data
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part1() -> None:
|
|
||||||
assert DayRunner.part1(get_data(13)) == 480
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
from aoc.days.day14 import DayRunner
|
|
||||||
|
|
||||||
from . import get_data
|
|
||||||
|
|
||||||
|
|
||||||
def test_sample_part1() -> None:
|
|
||||||
assert DayRunner.part1(get_data(14), width=11, height=7) == 12
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user