Compare commits
36 Commits
0dad7a3244
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a8503c3fe | |||
| 2b69cbd391 | |||
| 661bcbb557 | |||
| c6ac448ad2 | |||
| 3d85144065 | |||
| 4e8b636d2f | |||
| ee819d1362 | |||
| 34cb639327 | |||
| b33e678c7e | |||
| 94ddabc590 | |||
| 32895b3e17 | |||
| 77c91f8386 | |||
| 8ed0896adb | |||
| c17cf38795 | |||
| f53c65fb0d | |||
| 6ecae2b5bf | |||
| c3a12cbf59 | |||
| 8ea03a4c51 | |||
| 7757fd2b49 | |||
| f0fbba722c | |||
| 51a679ec63 | |||
| 8459219e60 | |||
| 5f231facc6 | |||
| ae6f86302d | |||
| 479affa4cc | |||
| 17492a8c23 | |||
| d1e15ca2c9 | |||
| 2688950560 | |||
| af8170a3e6 | |||
| eafb73cfc1 | |||
| 61713a5faa | |||
| e79b0344fd | |||
| 479b1d745e | |||
| e9d0701b4c | |||
| 7826194b37 | |||
| 3d45bd3da3 |
55
README.md
Normal file
55
README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Advent of Code 2020 Solutions
|
||||
Here's to my first 50 star year since 2017! The goal
|
||||
was to make it to top 100 at least once this year,
|
||||
and it finally came true on day 22 (and again in day 25
|
||||
part 2, but that hardly counts). Honestly,
|
||||
I am not at all happy with myself, though.
|
||||
|
||||
## Kinds of Solutions
|
||||
I "raced" in [Crystal](https://crystal-lang.org), but also tried my hand at
|
||||
formal verification in [Coq](https://coq.inria.fr/), and tried out
|
||||
an APL dialect called [J](https://jsoftware.com/) for fun.
|
||||
I didn't always clean my race day solutions, particularly the ones to the
|
||||
"hard" days. I will write about my Coq solutions on [my site](https://danilafe.com).
|
||||
|
||||
## Rankings
|
||||
Here's the (rather embarassing) table with my times.
|
||||
```
|
||||
-------Part 1-------- -------Part 2--------
|
||||
Day Time Rank Score Time Rank Score
|
||||
25 00:07:18 105 0 00:07:24 94 7
|
||||
24 00:25:27 1496 0 00:43:46 1165 0
|
||||
23 00:27:30 887 0 01:35:00 1057 0
|
||||
22 00:03:44 42 59 01:16:52 2242 0
|
||||
21 00:21:57 612 0 00:26:05 439 0
|
||||
20 00:27:52 465 0 03:00:50 845 0
|
||||
19 00:14:56 123 0 00:48:50 439 0
|
||||
18 00:26:17 1209 0 00:28:01 574 0
|
||||
17 00:17:23 400 0 00:19:14 281 0
|
||||
16 00:11:50 627 0 01:03:40 2040 0
|
||||
15 00:18:11 1772 0 00:30:38 1924 0
|
||||
14 00:18:05 1220 0 00:39:57 1173 0
|
||||
13 00:04:59 198 0 01:05:11 1606 0
|
||||
12 00:06:19 206 0 00:14:17 245 0
|
||||
11 00:13:23 414 0 00:19:11 261 0
|
||||
10 00:05:04 293 0 00:31:04 1493 0
|
||||
9 00:08:55 1721 0 00:26:50 3540 0
|
||||
8 00:04:23 275 0 00:09:56 256 0
|
||||
7 00:17:46 832 0 00:24:22 615 0
|
||||
6 00:03:31 385 0 00:09:38 999 0
|
||||
5 00:08:21 839 0 00:27:23 3404 0
|
||||
4 00:07:40 786 0 00:20:26 440 0
|
||||
3 00:02:46 119 0 00:07:40 448 0
|
||||
2 00:03:48 341 0 00:06:01 272 0
|
||||
1 00:07:52 1072 0 00:09:00 748 0
|
||||
```
|
||||
|
||||
## Crystal for Competitive Programming
|
||||
I really enjoyed writing Crystal for the Advent of Code, but there were a few reasons
|
||||
why it wasn't perfect for the task.
|
||||
|
||||
* When you're not banking on brute force speed, you lose some time to the compiler.
|
||||
* Numbers are _always_ 32-bit by default, and require constant `_i64` suffixes everywhere
|
||||
in the code when they're involved.
|
||||
* Type annotations (however necessary they are) for hashes and arrays make refactoring
|
||||
a little bit slower.
|
||||
36
console.cr
Normal file
36
console.cr
Normal file
@@ -0,0 +1,36 @@
|
||||
def parse(input)
|
||||
input.lines.map do |s|
|
||||
code, int = s.split(" ")
|
||||
{code, int.to_i32}
|
||||
end
|
||||
end
|
||||
|
||||
def run(prog)
|
||||
acc = 0
|
||||
pc = 0
|
||||
visited = Set(Int32).new
|
||||
input = Channel(Int32).new
|
||||
output = Channel({Symbol, Int32}).new
|
||||
spawn do
|
||||
loop do
|
||||
if pc >= prog.size
|
||||
output.send({:term, acc})
|
||||
break
|
||||
elsif visited.includes? pc
|
||||
output.send({:inf, acc})
|
||||
break
|
||||
end
|
||||
|
||||
visited << pc
|
||||
code, int = prog[pc]
|
||||
case code
|
||||
when "acc"
|
||||
acc += int
|
||||
when "jmp"
|
||||
pc += int - 1
|
||||
end
|
||||
pc += 1
|
||||
end
|
||||
end
|
||||
{input, output}
|
||||
end
|
||||
3
day1.cr
3
day1.cr
@@ -1,4 +1,5 @@
|
||||
INPUT = File.read("day1.txt").lines.map(&.chomp.to_i32)
|
||||
require "advent"
|
||||
INPUT = input(2020, 1).lines.map(&.chomp.to_i32)
|
||||
|
||||
def part1
|
||||
input = INPUT.clone.sort!
|
||||
|
||||
3
day1.ijs
Normal file
3
day1.ijs
Normal file
@@ -0,0 +1,3 @@
|
||||
c =. > 0 ". each cutopen 1!:1 < jpath '~/projects/AoC/2020/year2020day1.txt.cache'
|
||||
>./,(c*/c)*2020=c+/c
|
||||
>./,(c*/c*/c)*2020=c+/c+/c
|
||||
37
day10.cr
Normal file
37
day10.cr
Normal file
@@ -0,0 +1,37 @@
|
||||
require "advent"
|
||||
INPUT = input(2020, 10).lines.map(&.to_i32).sort!
|
||||
|
||||
def part1(input)
|
||||
diff_1 = 0
|
||||
diff_3 = 0
|
||||
curr = 0
|
||||
input << (input.max + 3)
|
||||
input.each do |i|
|
||||
diff_1 += 1 if (i - curr) == 1
|
||||
diff_3 += 1 if (i - curr) == 3
|
||||
curr = i
|
||||
end
|
||||
puts diff_1 * diff_3
|
||||
end
|
||||
|
||||
def count_ways(input, prev, index, mem, indent = 0)
|
||||
if m = mem[{prev, index}]?
|
||||
return m
|
||||
end
|
||||
return 0_i64 if input[index] - prev > 3 || index >= input.size
|
||||
return 1_i64 if index == input.size - 1
|
||||
|
||||
total = count_ways(input, input[index], index+1, mem, indent + 1)
|
||||
total += count_ways(input, prev, index+1, mem, indent + 1)
|
||||
|
||||
mem[{prev, index}] = total
|
||||
return total
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
input << (input.max + 3)
|
||||
count_ways(input, 0, 0, {} of {Int32, Int32} => Int64)
|
||||
end
|
||||
|
||||
puts part1(INPUT.clone)
|
||||
puts part2(INPUT.clone)
|
||||
64
day11.cr
Normal file
64
day11.cr
Normal file
@@ -0,0 +1,64 @@
|
||||
require "advent"
|
||||
INPUT = input(2020, 11).lines.map(&.chars)
|
||||
|
||||
abstract class Search
|
||||
def search(current, x, y, dx, dy)
|
||||
x, y = x + dx, y + dy
|
||||
return 0 if y < 0 || y >= current.size
|
||||
return 0 if x < 0 || x >= current[y].size
|
||||
search_impl(current,x,y,dx,dy)
|
||||
end
|
||||
|
||||
abstract def search_impl(current, x, y, dx, dy)
|
||||
end
|
||||
|
||||
class FirstSearch < Search
|
||||
def search_impl(current, x, y, dx, dy)
|
||||
return current[y][x] == '#' ? 1 : 0
|
||||
end
|
||||
end
|
||||
|
||||
class SecondSearch < Search
|
||||
def search_impl(current, x, y, dx, dy)
|
||||
return 1 if current[y][x] == '#'
|
||||
return 0 if current[y][x] == 'L'
|
||||
return search(current, x, y, dx, dy)
|
||||
end
|
||||
end
|
||||
|
||||
DIRS = [{-1,-1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1,1}]
|
||||
|
||||
def step(current, step, check, n)
|
||||
current.each_with_index do |row, y|
|
||||
row.each_with_index do |seat, x|
|
||||
step[y][x] = current[y][x]
|
||||
count = DIRS.sum do |dx, dy|
|
||||
check.search(current, x, y, dx, dy)
|
||||
end
|
||||
step[y][x] = 'L' if seat == '#' && count >= n
|
||||
step[y][x] = '#' if seat == 'L' && count == 0
|
||||
end
|
||||
end
|
||||
{step, current}
|
||||
end
|
||||
|
||||
def run(input, search, n)
|
||||
current = input
|
||||
step = input.clone
|
||||
loop do
|
||||
current, step = step(current, step, search, n)
|
||||
break if current.zip_with(step) { |l, r| l == r }.all?
|
||||
end
|
||||
current.sum(&.count(&.==('#')))
|
||||
end
|
||||
|
||||
def part1(input)
|
||||
run(input, FirstSearch.new, 4)
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
run(input, SecondSearch.new, 5)
|
||||
end
|
||||
|
||||
puts part1(INPUT.clone)
|
||||
puts part2(INPUT.clone)
|
||||
45
day12.cr
Normal file
45
day12.cr
Normal file
@@ -0,0 +1,45 @@
|
||||
require "advent"
|
||||
DISTS = [{1, 0}, {0, 1}, {-1, 0}, {0, -1}]
|
||||
INPUT = input(2020, 12).lines.map do |l|
|
||||
{l[0], l[1..].to_i32}
|
||||
end
|
||||
|
||||
def part1(input)
|
||||
pos = {0, 0}
|
||||
angle = 0
|
||||
input.each do |c, n|
|
||||
case c
|
||||
when 'N' then pos = pos.add({0, n})
|
||||
when 'S' then pos = pos.add({0, -n})
|
||||
when 'E' then pos = pos.add({n, 0})
|
||||
when 'W' then pos = pos.add({-n, 0})
|
||||
when 'L' then angle = (angle + n) % 360
|
||||
when 'R' then angle = (angle + (360-n)) % 360
|
||||
when 'F'
|
||||
dx, dy = DISTS[angle//90]
|
||||
pos = pos.add({dx * n, dy * n})
|
||||
end
|
||||
end
|
||||
pos.map(&.abs).sum
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
pos = {10, 1}
|
||||
spos = {0, 0}
|
||||
angle = 0
|
||||
input.each do |c, n|
|
||||
case c
|
||||
when 'N' then pos = pos.add({0, n})
|
||||
when 'S' then pos = pos.add({0, -n})
|
||||
when 'E' then pos = pos.add({n, 0})
|
||||
when 'W' then pos = pos.add({-n, 0})
|
||||
when 'L' then (n//90).times { pos = {-pos[1], pos[0]} }
|
||||
when 'R' then (n//90).times { pos = {pos[1], -pos[0]} }
|
||||
when 'F' then spos = spos.add({pos[0] * n, pos[1]*n})
|
||||
end
|
||||
end
|
||||
spos.map(&.abs).sum
|
||||
end
|
||||
|
||||
puts part1(INPUT.clone)
|
||||
puts part2(INPUT.clone)
|
||||
34
day13.cr
Normal file
34
day13.cr
Normal file
@@ -0,0 +1,34 @@
|
||||
require "advent"
|
||||
INPU = input(2020, 13).lines
|
||||
INPUT = {INPU[0].to_i32, INPU[1].split(",")}
|
||||
|
||||
def part1(input)
|
||||
early, busses = input
|
||||
busses.reject! &.==("x")
|
||||
busses = busses.map &.to_i32
|
||||
bbus = busses.min_by do |b|
|
||||
(early / b).ceil * b
|
||||
end
|
||||
diff = bbus * (((early/bbus).ceil * bbus).to_i32 - early)
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
_, busses = input
|
||||
busses = busses.map_with_index do |x, i|
|
||||
x.to_i32?.try { |n| { n, i } }
|
||||
end
|
||||
busses = busses.compact
|
||||
n = 0_i64
|
||||
iter = 1_i64
|
||||
busses.each do |m, i|
|
||||
while (n + i) % m != 0
|
||||
n += iter
|
||||
end
|
||||
iter *= m
|
||||
end
|
||||
puts n
|
||||
puts busses
|
||||
end
|
||||
|
||||
puts part1(INPUT.clone)
|
||||
puts part2(INPUT.clone)
|
||||
60
day14.cr
Normal file
60
day14.cr
Normal file
@@ -0,0 +1,60 @@
|
||||
require "advent"
|
||||
raw = input(2020, 14).lines
|
||||
input = [] of {UInt64, UInt64, Array(Int32), UInt64, UInt64}
|
||||
|
||||
and_mask = (2_u64**63) + (2_u64**63-1)
|
||||
or_mask = 0_u64
|
||||
floats = [] of Int32
|
||||
|
||||
raw.each do |line|
|
||||
if data = line.match(/^mask = (.+)$/)
|
||||
new_mask = data[1]
|
||||
and_mask = (2_u64**63) + (2_u64**63-1)
|
||||
or_mask = 0_u64
|
||||
floats = [] of Int32
|
||||
new_mask.reverse.chars.each_with_index do |c, i|
|
||||
floats << i if c == 'X'
|
||||
or_mask = or_mask | (1_u64 << i) if c == '1'
|
||||
and_mask = and_mask & ~(1_u64 << i) if c == '0'
|
||||
end
|
||||
elsif data = line.match(/^mem\[(\d+)\] = (\d+)$/)
|
||||
input << {and_mask, or_mask, floats, data[1].to_u64, data[2].to_u64}
|
||||
else
|
||||
raise "Invalid line"
|
||||
end
|
||||
end
|
||||
|
||||
def part1(input)
|
||||
memory = {} of UInt64 => UInt64
|
||||
input.each do |i|
|
||||
and_mask, or_mask, floats, addr, v = i
|
||||
memory[addr] = (v & and_mask) | or_mask
|
||||
end
|
||||
memory.values.sum
|
||||
end
|
||||
|
||||
def generate_possible(indices, index, n, &block : UInt64 -> _)
|
||||
if index == indices.size
|
||||
yield n
|
||||
return
|
||||
end
|
||||
float_addr = indices[index]
|
||||
no = n | (1_u64 << float_addr)
|
||||
generate_possible(indices, index+1, no, &block)
|
||||
nz = n & ~(1_u64 << float_addr)
|
||||
generate_possible(indices, index+1, nz, &block)
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
memory = {} of UInt64 => UInt64
|
||||
input.each do |i|
|
||||
_, or_mask, floats, addr, v = i
|
||||
generate_possible(floats, 0, addr | or_mask) do |addr|
|
||||
memory[addr] = v
|
||||
end
|
||||
end
|
||||
memory.values.sum
|
||||
end
|
||||
|
||||
puts part1(input)
|
||||
puts part2(input)
|
||||
24
day15.cr
Normal file
24
day15.cr
Normal file
@@ -0,0 +1,24 @@
|
||||
require "advent"
|
||||
INPUT = input(2020, 15).split(",").map(&.to_i32)
|
||||
|
||||
def run(input, times)
|
||||
ls = {} of Int32 => Int32
|
||||
temp = 0
|
||||
(times-1).times do |i|
|
||||
n = input[i]? || temp
|
||||
temp = i - (ls[n]? || i)
|
||||
ls[n] = i
|
||||
end
|
||||
return temp
|
||||
end
|
||||
|
||||
def part1(input)
|
||||
run(input, 2020)
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
run(input, 30000000)
|
||||
end
|
||||
|
||||
puts part1(INPUT.clone)
|
||||
puts part2(INPUT.clone)
|
||||
65
day16.cr
Normal file
65
day16.cr
Normal file
@@ -0,0 +1,65 @@
|
||||
require "advent"
|
||||
|
||||
def parse_range(str)
|
||||
data = str.match(/([a-z ]+): (\d+)-(\d+) or (\d+)-(\d+)/).not_nil!
|
||||
return {data[1], (data[2].to_i32..data[3].to_i32), (data[4].to_i32.. data[5].to_i32)}
|
||||
end
|
||||
|
||||
fields, your, nearby = input(2020, 16).split("\n\n")
|
||||
fields = fields.lines.map { |l| parse_range(l) }
|
||||
your = your.lines[1].split(",").map(&.to_i32)
|
||||
nearby = nearby.lines[1..].map(&.split(",").map(&.to_i32))
|
||||
|
||||
def part1(input)
|
||||
fields, your, nearby = input
|
||||
all_ranges = [] of Range(Int32, Int32)
|
||||
fields.each do |a|
|
||||
all_ranges << a[1] << a[2]
|
||||
end
|
||||
nearby.select! do |nb|
|
||||
nb.all? do |n|
|
||||
all_ranges.any? &.includes?(n)
|
||||
end
|
||||
end
|
||||
nearby << your
|
||||
field_map = {} of String => Set(Int32)
|
||||
fields.each do |f|
|
||||
field_map[f[0]] = Set(Int32).new
|
||||
nearby[0].size.times do |i|
|
||||
next if field_map.values.includes? i
|
||||
all_match = nearby.all? do |nb|
|
||||
f[1].includes?(nb[i]) || f[2].includes?(nb[i])
|
||||
end
|
||||
if all_match
|
||||
field_map[f[0]] << i
|
||||
end
|
||||
end
|
||||
end
|
||||
sum = 1_u64
|
||||
numbers = (0...nearby[0].size).to_a
|
||||
solved = {} of String => Int32
|
||||
while solved.size != fields.size
|
||||
cleared = [] of {String, Int32}
|
||||
field_map.each do |k, v|
|
||||
next unless v.size == 1
|
||||
cleared << {k, v.to_a[0]}
|
||||
end
|
||||
cleared.each do |f,n|
|
||||
solved[f] = n
|
||||
field_map.each do |k, v|
|
||||
v.delete n
|
||||
end
|
||||
end
|
||||
end
|
||||
fields.each do |f|
|
||||
next unless f[0].starts_with? "departure"
|
||||
sum *= your[solved[f[0]]]
|
||||
end
|
||||
sum
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
end
|
||||
|
||||
puts part1({fields, your, nearby})
|
||||
puts part2({fields, your, nearby})
|
||||
47
day17.cr
Normal file
47
day17.cr
Normal file
@@ -0,0 +1,47 @@
|
||||
require "advent"
|
||||
require "benchmark"
|
||||
|
||||
INPUT = input(2020, 17).lines.map(&.chars)
|
||||
|
||||
def solve(input, dim)
|
||||
step = input.clone
|
||||
cubes = Set(Array(Int32)).new
|
||||
new_cubes = Set(Array(Int32)).new
|
||||
input.each_with_index do |row, y|
|
||||
row.each_with_index do |c, x|
|
||||
cubes << [x,y].concat([0] * (dim-2)) if c == '#'
|
||||
end
|
||||
end
|
||||
|
||||
8.times do |i|
|
||||
print '.'
|
||||
neighbor_count = Hash(Array(Int32), Int32).new(0)
|
||||
Array.product([[-1,0,1]] * dim).each do |diff|
|
||||
next if diff.all? &.==(0)
|
||||
cubes.each do |c|
|
||||
neighbor_count[c.zip_with(diff) { |a,b| a+b }] += 1
|
||||
end
|
||||
end
|
||||
|
||||
new_cubes.clear
|
||||
neighbor_count.each do |n, i|
|
||||
new_cubes << n if i == 3 || (cubes.includes?(n) && i == 2)
|
||||
end
|
||||
new_cubes, cubes = cubes, new_cubes
|
||||
end
|
||||
cubes.size
|
||||
end
|
||||
|
||||
def part1(input)
|
||||
solve(input, 3)
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
solve(input, 4)
|
||||
end
|
||||
|
||||
(3..).each do |i|
|
||||
print "Dim #{i} "
|
||||
bm = Benchmark.measure { puts " #{solve(INPUT, i)}" }
|
||||
puts bm.real * 1000
|
||||
end
|
||||
54
day18.cr
Normal file
54
day18.cr
Normal file
@@ -0,0 +1,54 @@
|
||||
require "advent"
|
||||
INPUT = input(2020, 18).lines
|
||||
|
||||
class Array(T)
|
||||
def push_op(op)
|
||||
r = pop
|
||||
l = pop
|
||||
self << ((op == '*') ? (l*r) : (l+r))
|
||||
end
|
||||
|
||||
def has_op?
|
||||
!empty? && last != '('
|
||||
end
|
||||
end
|
||||
|
||||
def translate(toks, prec)
|
||||
output = [] of Int64
|
||||
stack = [] of Char
|
||||
toks.each do |tok|
|
||||
case tok
|
||||
when .number? then output << tok.to_i64
|
||||
when '(' then stack << '('
|
||||
when ')'
|
||||
while stack.has_op?
|
||||
output.push_op(stack.pop)
|
||||
end
|
||||
stack.pop
|
||||
else
|
||||
while stack.has_op? && prec[stack.last] < prec[tok]
|
||||
output.push_op(stack.pop)
|
||||
end
|
||||
stack << tok
|
||||
end
|
||||
end
|
||||
while stack.has_op?
|
||||
output.push_op(stack.pop)
|
||||
end
|
||||
output.last
|
||||
end
|
||||
|
||||
def part1(input)
|
||||
input.sum do |line|
|
||||
translate(line.chars.reject &.==(' '), {'*' => 0, '+' => 0})
|
||||
end
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
input.sum do |line|
|
||||
translate(line.chars.reject &.==(' '), {'*' => 1, '+' => 0})
|
||||
end
|
||||
end
|
||||
|
||||
puts part1(INPUT.clone)
|
||||
puts part2(INPUT.clone)
|
||||
68
day19.cr
Normal file
68
day19.cr
Normal file
@@ -0,0 +1,68 @@
|
||||
require "advent"
|
||||
|
||||
rlines, _, strings = input(2020, 19).partition("\n\n")
|
||||
strings = strings.lines
|
||||
rules = {} of String => String
|
||||
rlines.lines.map do |l|
|
||||
rule, _, text = l.partition(": ")
|
||||
rules[rule] = text
|
||||
end
|
||||
|
||||
alias Matcher = Proc(String,Int32,Array(Int32))
|
||||
|
||||
def char(c)
|
||||
->(str : String, i : Int32) { str[i]? == c ? [i+1] : [] of Int32 }
|
||||
end
|
||||
|
||||
def any(ps)
|
||||
->(str : String, i : Int32) { ps.flat_map { |p| p.call(str, i) } }
|
||||
end
|
||||
|
||||
def seq(ps)
|
||||
->(str : String, i : Int32) {
|
||||
base = [i]
|
||||
ps.each do |p|
|
||||
base = base.flat_map { |i| p.call(str, i) }
|
||||
end
|
||||
base
|
||||
}
|
||||
end
|
||||
|
||||
def to_regex(rules, rule)
|
||||
end
|
||||
|
||||
def build_rule(rules, built, rule) : Matcher
|
||||
if exists = built[rule]?
|
||||
return exists
|
||||
end
|
||||
|
||||
body = rules[rule]
|
||||
return built[rule] = char body[1] if body.matches? /"."/
|
||||
|
||||
branches = [] of Matcher
|
||||
top = any(branches)
|
||||
built[rule] = top
|
||||
branches.concat(body.split(" | ").map { |b| seq(b.split(" ").map { |subrule| build_rule(rules, built, subrule) }) })
|
||||
top
|
||||
end
|
||||
|
||||
def part1(input)
|
||||
rules, lines = input
|
||||
matcher = build_rule(rules, {} of String => Matcher, "0")
|
||||
lines.count do |l|
|
||||
matcher.call(l, 0).includes? l.size
|
||||
end
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
rules, lines = input
|
||||
rules["8"] = "42 | 42 8"
|
||||
rules["11"] = "42 31 | 42 11 31"
|
||||
matcher = build_rule(rules, {} of String => Matcher, "0")
|
||||
lines.count do |l|
|
||||
matcher.call(l, 0).includes? l.size
|
||||
end
|
||||
end
|
||||
|
||||
puts part1({rules,strings})
|
||||
puts part2({rules,strings})
|
||||
3
day2.cr
3
day2.cr
@@ -1,4 +1,5 @@
|
||||
INPUT = File.read("day2.txt").lines.map(&.chomp)
|
||||
require "advent"
|
||||
INPUT = input(2020, 2).lines.map(&.chomp)
|
||||
|
||||
def parse(line)
|
||||
data = line.match(/([0-9]+)-([0-9]+) ([a-z]): ([a-z]+)/)
|
||||
|
||||
6
day2.ijs
Normal file
6
day2.ijs
Normal file
@@ -0,0 +1,6 @@
|
||||
r =: >"1 cut each cutopen 1!:1 < jpath '~/projects/AoC/2020/year2020day2.txt.cache'
|
||||
rs =: >0".each'-'cut"1>(0}"1 r)
|
||||
cs =: 0{"1>1{"1 r
|
||||
ss =: 2{"1 r
|
||||
+/1>**/|:rs-+/"1 cs=>ss
|
||||
+/1=+/|:(rs-1){"1 cs=>ss
|
||||
166
day20.cr
Normal file
166
day20.cr
Normal file
@@ -0,0 +1,166 @@
|
||||
require "advent"
|
||||
tiles = input(2020, 20).split "\n\n"
|
||||
tiles.pop
|
||||
thash = {} of Int32 => Array(Array(Char))
|
||||
tiles = tiles.map do |t|
|
||||
tl = t.lines
|
||||
tid = tl[0].match(/Tile (\d+):/).not_nil![1].to_i32
|
||||
tls = tl[1..]
|
||||
thash[tid] = tls.map &.chars
|
||||
end
|
||||
|
||||
class Array(T)
|
||||
def matches?(other)
|
||||
zip_with(other) { |l,r| l == r }.all?
|
||||
end
|
||||
|
||||
def rotate
|
||||
reverse.transpose
|
||||
end
|
||||
end
|
||||
|
||||
def check(side_a, side_b)
|
||||
return :normal if side_a.matches?(side_b)
|
||||
return :flip if side_a.matches?(side_b.reverse)
|
||||
return nil
|
||||
end
|
||||
|
||||
def check_all(side_a, other, other_t)
|
||||
check(side_a, other.first) || check(side_a, other.last) || check(side_a, other_t.first) || check(side_a, other_t.last)
|
||||
end
|
||||
|
||||
MONSTER = [ {0, 1}, {1, 0}, {4, 0}, {5, 1}, {6, 1}, {7, 0}, {10, 0}, {11,1}, {12, 1}, {13, 0}, {16, 0},
|
||||
{17, 1}, {18, 1}, {18, 2}, {19, 1} ]
|
||||
|
||||
def stitch(m, thash, corner)
|
||||
image = Array(Array(Char)).new(12*8) do |y|
|
||||
Array(Char).new(12*8) do |x|
|
||||
'.'
|
||||
end
|
||||
end
|
||||
|
||||
tile = thash[corner]
|
||||
tile_id = corner
|
||||
tile.reverse! if m[corner].has_key? :top
|
||||
tile.each &.reverse! if m[corner].has_key? :left
|
||||
|
||||
12.times do |row|
|
||||
tile = tile.not_nil!
|
||||
|
||||
row_tile = tile
|
||||
row_id = tile_id
|
||||
|
||||
12.times do |col|
|
||||
row_tile = row_tile.not_nil!
|
||||
|
||||
(0..7).each do |y|
|
||||
(0..7).each do |x|
|
||||
image[col*8+y][row*8+x] = row_tile[y+1][x+1]
|
||||
end
|
||||
end
|
||||
|
||||
matches = nil
|
||||
thash.each do |other_id, other_tile|
|
||||
next if matches
|
||||
next if other_id == row_id
|
||||
4.times do
|
||||
if row_tile.last.matches? other_tile.first
|
||||
row_id = other_id
|
||||
matches = other_tile
|
||||
elsif row_tile.last.matches? other_tile.first.reverse
|
||||
row_id = other_id
|
||||
matches = other_tile.map &.reverse
|
||||
end
|
||||
other_tile = other_tile.rotate
|
||||
end
|
||||
end
|
||||
|
||||
row_tile = matches
|
||||
end
|
||||
|
||||
rot = tile.rotate
|
||||
matches = nil
|
||||
thash.each do |other_id, other_tile|
|
||||
next if matches
|
||||
next if other_id == tile_id
|
||||
|
||||
4.times do
|
||||
if rot.last.matches? other_tile.first
|
||||
tile_id = other_id
|
||||
matches = other_tile.rotate.rotate.rotate
|
||||
elsif rot.last.matches? other_tile.first.reverse
|
||||
tile_id = other_id
|
||||
matches = other_tile.map(&.reverse).rotate.rotate.rotate
|
||||
end
|
||||
other_tile = other_tile.rotate
|
||||
end
|
||||
end
|
||||
tile = matches
|
||||
end
|
||||
image
|
||||
end
|
||||
|
||||
def find_dragons(stitch)
|
||||
dragons = [] of {Int32,Int32}
|
||||
stitch.each_with_index do |row, y|
|
||||
row.each_with_index do |c, x|
|
||||
is_dragon = MONSTER.all? do |dx, dy|
|
||||
(stitch[y+dy]?.try &.[x+dx]?) == '#'
|
||||
end
|
||||
dragons << {x,y} if is_dragon
|
||||
end
|
||||
end
|
||||
dragons
|
||||
end
|
||||
|
||||
def find_all_dragons(stitch)
|
||||
dragons = [] of {Int32,Int32}
|
||||
4.times do
|
||||
dragons.concat find_dragons(stitch)
|
||||
dragons.concat find_dragons(stitch.reverse)
|
||||
stitch = stitch.rotate
|
||||
end
|
||||
dragons
|
||||
end
|
||||
|
||||
def match(thash, tile)
|
||||
matches = {} of Symbol => {Int32, Symbol}
|
||||
cs = thash[tile]
|
||||
tcs = cs.transpose
|
||||
|
||||
thash.each do |t, ocs|
|
||||
next if t == tile
|
||||
tocs = ocs.transpose
|
||||
top = check_all(cs.first, ocs, tocs)
|
||||
bottom = check_all(cs.last, ocs, tocs)
|
||||
left = check_all(tcs.first, ocs, tocs)
|
||||
right = check_all(tcs.last, ocs, tocs)
|
||||
|
||||
matches[:top] = {t, top} if top
|
||||
matches[:left] = {t, left} if left
|
||||
matches[:bottom] = {t, bottom} if bottom
|
||||
matches[:right] = {t, right} if right
|
||||
end
|
||||
matches
|
||||
end
|
||||
|
||||
def part1(input)
|
||||
corners = input.select do |t, i|
|
||||
match(input, t).size == 2
|
||||
end
|
||||
corners.keys.map(&.to_i64).product
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
matches = {} of Int32 => Hash(Symbol, {Int32, Symbol})
|
||||
corners = input.select do |t, i|
|
||||
matches[t] = match(input, t)
|
||||
match(input, t).size == 2
|
||||
end
|
||||
stitched = stitch(matches, input, corners.first[0])
|
||||
dragons = find_all_dragons(stitched)
|
||||
stitched.sum(&.count(&.==('#'))) - (dragons.size * MONSTER.size)
|
||||
end
|
||||
|
||||
puts part1(thash.clone)
|
||||
puts part2(thash.clone)
|
||||
31
day21.cr
Normal file
31
day21.cr
Normal file
@@ -0,0 +1,31 @@
|
||||
require "advent"
|
||||
input = input(2020, 21).lines.map do |line|
|
||||
data = line.match(/^([a-z ]+) \(contains (.+)\)$/).not_nil!
|
||||
{data[1].split(" ").to_set, data[2].split(", ").to_set}
|
||||
end
|
||||
|
||||
allergens = input.flat_map(&.last.to_a).to_set
|
||||
ingredients = input.flat_map(&.first.to_a).to_set
|
||||
|
||||
allergen_sets = {} of String => Set(String)
|
||||
allergens.each do |a|
|
||||
input.each do |ings, als|
|
||||
next unless als.includes? a
|
||||
allergen_sets[a] ||= ings
|
||||
allergen_sets[a] &= ings
|
||||
end
|
||||
end
|
||||
|
||||
safe = ingredients.reject { |i| allergen_sets.any? &.last.includes?(i) }
|
||||
puts "Part 1: #{input.sum &.first.count { |i| safe.includes? i }}"
|
||||
|
||||
known_allergens = {} of String => String
|
||||
while allergen_sets.size > known_allergens.size
|
||||
allergen_sets.each do |a, s|
|
||||
next unless s.size == 1
|
||||
new_known = s.first
|
||||
known_allergens[a] = new_known
|
||||
allergen_sets.each &.last.delete(new_known)
|
||||
end
|
||||
end
|
||||
puts "Part 2: #{known_allergens.to_a.sort_by(&.first).map(&.last).join(",")}"
|
||||
66
day22.cr
Normal file
66
day22.cr
Normal file
@@ -0,0 +1,66 @@
|
||||
require "advent"
|
||||
first, second = input(2020, 22).split("\n\n")
|
||||
first = first.lines[1..].map &.to_i32
|
||||
second = second.lines[1..].map &.to_i32
|
||||
|
||||
class Array(T)
|
||||
def pop_left
|
||||
delete_at(0)
|
||||
end
|
||||
|
||||
def score
|
||||
total = 0
|
||||
s = size
|
||||
each_with_index do |n, i|
|
||||
total += n * (s - i)
|
||||
end
|
||||
total
|
||||
end
|
||||
end
|
||||
|
||||
def no_rec(deck1, deck2)
|
||||
while !deck1.empty? && !deck2.empty?
|
||||
f, s = deck1.delete_at(0), deck2.delete_at(0)
|
||||
if f > s
|
||||
deck1 << f << s
|
||||
else
|
||||
deck2 << s << f
|
||||
end
|
||||
end
|
||||
return !deck1.empty?
|
||||
end
|
||||
|
||||
def rec(deck1, deck2)
|
||||
seen = Set({Int32, Int32}).new
|
||||
while !deck1.empty? && !deck2.empty?
|
||||
key = {deck1.score, deck2.score}
|
||||
return true if seen.includes?(key)
|
||||
seen << key
|
||||
|
||||
f = deck1.delete_at(0)
|
||||
s = deck2.delete_at(0)
|
||||
|
||||
p1wins = (f <= deck1.size) && (s <= deck2.size) ? rec(deck1[0..f-1], deck2[0..s-1]) : f > s
|
||||
if p1wins
|
||||
deck1 << f << s
|
||||
else
|
||||
deck2 << s << f
|
||||
end
|
||||
end
|
||||
return !deck1.empty?
|
||||
end
|
||||
|
||||
def run(first, second, proc)
|
||||
((proc.call(first, second)) ? first : second).score
|
||||
end
|
||||
|
||||
def part1(input)
|
||||
run(input[0], input[1], ->no_rec(Array(Int32), Array(Int32)))
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
run(input[0], input[1], ->rec(Array(Int32), Array(Int32)))
|
||||
end
|
||||
|
||||
puts part1({first, second}.clone)
|
||||
puts part2({first, second}.clone)
|
||||
117
day23.cr
Normal file
117
day23.cr
Normal file
@@ -0,0 +1,117 @@
|
||||
require "advent"
|
||||
|
||||
input = input(2020, 23).lines[0].chars.map &.to_i32
|
||||
|
||||
class Array(T)
|
||||
def get(i)
|
||||
self[i % size]
|
||||
end
|
||||
|
||||
def del(i)
|
||||
delete_at(i % size)
|
||||
end
|
||||
end
|
||||
|
||||
class Node
|
||||
property next : Node
|
||||
property int : Int32
|
||||
|
||||
def initialize(@int, @next)
|
||||
end
|
||||
|
||||
def insert(other : Node, n = 1)
|
||||
set_next = other
|
||||
(n-1).times { set_next = set_next.@next }
|
||||
set_next.next = @next
|
||||
@next = other
|
||||
end
|
||||
|
||||
def remove(n = 1)
|
||||
curr = self
|
||||
to_return = self.next
|
||||
n.times { curr = curr.@next }
|
||||
@next = curr.@next
|
||||
to_return
|
||||
end
|
||||
|
||||
def find(n)
|
||||
start = self
|
||||
while start.int != n
|
||||
start = start.next
|
||||
end
|
||||
return start
|
||||
end
|
||||
|
||||
def to_s(n)
|
||||
return "" if n < 0
|
||||
return "#{@int} -> #{@next.to_s(n-1)}"
|
||||
end
|
||||
|
||||
def includes?(n, count)
|
||||
return false if count <= 0
|
||||
return @int == n || @next.includes?(n, count-1)
|
||||
end
|
||||
end
|
||||
|
||||
class Cups
|
||||
getter size : Int32
|
||||
getter head : Node
|
||||
|
||||
def initialize(list : Array(Int32))
|
||||
@cache = {} of Int32 => Node
|
||||
h = list.delete_at(0)
|
||||
temp = uninitialized Node
|
||||
@cache[h] = @head = Node.new(h, temp)
|
||||
@size = list.size
|
||||
@local_min = list.min.as(Int32)
|
||||
@local_max = list.max.as(Int32)
|
||||
@head.next = @head
|
||||
curr = @head
|
||||
list.each do |n|
|
||||
@cache[n] = new = Node.new(n, curr.next)
|
||||
curr.insert(new)
|
||||
curr = new
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
@head.to_s(@size)
|
||||
end
|
||||
|
||||
def step
|
||||
first = @head
|
||||
after = first.remove(3)
|
||||
m = first.int - 1
|
||||
while after.includes?(m, 3) || m < @local_min
|
||||
m = (m < @local_min) ? @local_max : (m - 1)
|
||||
end
|
||||
@cache[m].insert(after, 3)
|
||||
@head = @head.next
|
||||
end
|
||||
end
|
||||
|
||||
def play(input, count)
|
||||
cups = Cups.new input
|
||||
count.times { |n| cups.step }
|
||||
cups
|
||||
end
|
||||
|
||||
def part1(input)
|
||||
cups = play(input.clone, 100)
|
||||
cups.head.find(1).next.to_s(cups.size - 1).gsub(" -> ", "")
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
list = input.clone
|
||||
max = input.max
|
||||
(1000000 - input.size).times do
|
||||
max += 1
|
||||
list << max
|
||||
end
|
||||
cups = play(list, 10000000)
|
||||
one = cups.head.find(1)
|
||||
one.next.int.to_i64 * one.next.next.int
|
||||
end
|
||||
|
||||
puts "Part 1: #{part1(input)}"
|
||||
puts "Part 2: #{part2(input)}"
|
||||
78
day24.cr
Normal file
78
day24.cr
Normal file
@@ -0,0 +1,78 @@
|
||||
require "advent"
|
||||
|
||||
INPUT = input(2020, 24).lines.map &.tiles.map { |t| MATCHES[t] }
|
||||
MATCHES = { "se" => {0.5, -1.0}, "sw" => {-0.5, -1.0}, "ne" => {0.5, 1.0}, "nw" => {-0.5, 1.0}, "e" => {1.0, 0.0}, "w" => {-1.0, 0.0} }
|
||||
|
||||
class String
|
||||
def tiles
|
||||
i = 0
|
||||
tiles = [] of String
|
||||
while i < size
|
||||
if self[i] == 's' || self[i] == 'n'
|
||||
tiles << self[i..i+1]
|
||||
i += 2
|
||||
else
|
||||
tiles << self[i..i]
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
tiles
|
||||
end
|
||||
end
|
||||
|
||||
class Array(T)
|
||||
def pos
|
||||
curr = {0.0, 0.0}
|
||||
each do |c|
|
||||
curr = curr.add c
|
||||
end
|
||||
curr
|
||||
end
|
||||
end
|
||||
|
||||
def part1(input)
|
||||
counts = Hash({Float64,Float64}, Int32).new(0)
|
||||
input.each do |i|
|
||||
counts[i.pos] += 1
|
||||
end
|
||||
counts.count &.[1].%(2).==(1)
|
||||
end
|
||||
|
||||
struct Tuple(*T)
|
||||
def neighbors
|
||||
MATCHES.values.map &.add(self)
|
||||
end
|
||||
end
|
||||
|
||||
def part2(input)
|
||||
state = Hash({Float64,Float64}, Bool).new(false)
|
||||
counts = Hash({Float64,Float64}, Int32).new(0)
|
||||
|
||||
input.each do |i|
|
||||
state[i.pos] ^= true
|
||||
end
|
||||
|
||||
100.times do |i|
|
||||
counts.clear
|
||||
state.each do |t, f|
|
||||
next unless f
|
||||
t.neighbors.each do |n|
|
||||
counts[n] += 1
|
||||
end
|
||||
end
|
||||
|
||||
new_state = state.clone.clear
|
||||
state.each do |k, v|
|
||||
next unless v
|
||||
new_state[k] = !(counts[k] == 0 || (counts[k] > 2))
|
||||
end
|
||||
counts.each do |k, v|
|
||||
new_state[k] = true if (!state[k]) && v == 2
|
||||
end
|
||||
state = new_state
|
||||
end
|
||||
state.count &.[1]
|
||||
end
|
||||
|
||||
puts part1(INPUT.clone)
|
||||
puts part2(INPUT.clone)
|
||||
28
day25.cr
Normal file
28
day25.cr
Normal file
@@ -0,0 +1,28 @@
|
||||
require "advent"
|
||||
INPUT = input(2020, 25).lines.map(&.to_i64)
|
||||
|
||||
def transform(s)
|
||||
i = 1_i64
|
||||
c = 0
|
||||
loop do
|
||||
c += 1
|
||||
i = (i * s) % 20201227
|
||||
yield i, c
|
||||
end
|
||||
end
|
||||
|
||||
def find_size(s, goal)
|
||||
transform(s) do |n, c|
|
||||
return c if n == goal
|
||||
end
|
||||
end
|
||||
|
||||
def part1(input)
|
||||
goal = find_size(7, input[0])
|
||||
puts goal
|
||||
transform(input[1]) do |n, c|
|
||||
return n if c == goal
|
||||
end
|
||||
end
|
||||
|
||||
puts part1(INPUT.clone)
|
||||
3
day3.cr
3
day3.cr
@@ -1,4 +1,5 @@
|
||||
INPUT = File.read("day3.txt").lines.map(&.chomp)
|
||||
require "advent"
|
||||
INPUT = input(2020, 3).lines.map(&.chomp)
|
||||
|
||||
def run(input, slopes)
|
||||
prod = 1_i64
|
||||
|
||||
5
day3.ijs
Normal file
5
day3.ijs
Normal file
@@ -0,0 +1,5 @@
|
||||
r =: > cutopen 1!:1 < jpath '~/projects/AoC/2020/year2020day3.txt.cache'
|
||||
rn =: 4 : '+/''#''=((#|:y)|x*i.#y){"0 1 y'
|
||||
rnx =: 3 : '(1{y) rn 0{"2((0{-y)]\r)'
|
||||
rnx 1 3
|
||||
*/ rnx"1 (_2 [\ 1 1 1 3 1 5 1 7 2 1)
|
||||
3
day4.cr
3
day4.cr
@@ -1,5 +1,6 @@
|
||||
require "./passports.cr"
|
||||
INPUT = File.read("day4.txt")
|
||||
require "advent"
|
||||
INPUT = input(2020, 4)
|
||||
|
||||
def part1
|
||||
input = INPUT.clone
|
||||
|
||||
3
day5.cr
3
day5.cr
@@ -1,4 +1,5 @@
|
||||
INPUT = File.read("day5.txt").lines.map(&.chomp)
|
||||
require "advent"
|
||||
INPUT = input(2020, 5).lines.map(&.chomp)
|
||||
|
||||
def partition(choices, list)
|
||||
b = 0
|
||||
|
||||
4
day5.ijs
Normal file
4
day5.ijs
Normal file
@@ -0,0 +1,4 @@
|
||||
bs2n =: 3 : '+/ "1(|.*/\(1,(_1+#y)$2)) *"(1) 0 "."0 y'
|
||||
ns =: bs2n "1 'F0B1L0R1' charsub > cutopen 1!:1 < jpath '~/projects/AoC/2020/year2020day5.txt.cache'
|
||||
>./ns
|
||||
(<./ns)+I.-.((<./ns)+i.#ns)e.ns
|
||||
2
day5g.cr
Normal file
2
day5g.cr
Normal file
@@ -0,0 +1,2 @@
|
||||
i=File.read_lines("day5.txt").map &.tr("FBLR", "0101").to_i32(2);
|
||||
puts({i.max,(i.min..i.max).select{|n|!i.includes? n}})
|
||||
13
day6.cr
Normal file
13
day6.cr
Normal file
@@ -0,0 +1,13 @@
|
||||
require "advent"
|
||||
INPUT = input(2020, 6).split("\n\n")
|
||||
|
||||
def part1
|
||||
INPUT.sum &.chars.uniq!.count(&.ascii_letter?)
|
||||
end
|
||||
|
||||
def part2
|
||||
INPUT.sum &.lines.map(&.chars.to_set).intersect.size
|
||||
end
|
||||
|
||||
puts part1
|
||||
puts part2
|
||||
45
day7.cr
Normal file
45
day7.cr
Normal file
@@ -0,0 +1,45 @@
|
||||
require "advent"
|
||||
|
||||
INPUT = input(2020, 7).lines.map(&.chomp).map do |s|
|
||||
data = s.match(/^([a-z ]+) bags contain (.*).$/).not_nil!
|
||||
contents = data[2].split(", ").compact_map do |s|
|
||||
s.match(/(\d+) (.*) bags?/).try { |m| {m[1].to_i32, m[2]} }
|
||||
end
|
||||
{data[1], contents}
|
||||
end
|
||||
|
||||
def build_graph(input)
|
||||
gr = Graph(String).new
|
||||
seen = Set(String).new
|
||||
input.each do |i|
|
||||
seen << i[0]
|
||||
i[1].each do |to|
|
||||
n, m = to
|
||||
gr.add_edge(i[0], m, n)
|
||||
end
|
||||
end
|
||||
{seen, gr}
|
||||
end
|
||||
|
||||
def part1
|
||||
seen, gr = build_graph(INPUT)
|
||||
seen.count do |s|
|
||||
gr.find_path(s, "shiny gold") && s != "shiny gold"
|
||||
end
|
||||
end
|
||||
|
||||
def count_bags(gr, current)
|
||||
return 1 unless es = gr.@edges[current]?
|
||||
1 + es.sum do |e|
|
||||
t, k = e
|
||||
k * count_bags(gr, t)
|
||||
end
|
||||
end
|
||||
|
||||
def part2
|
||||
seen, gr = build_graph(INPUT)
|
||||
count_bags(gr, "shiny gold") - 1
|
||||
end
|
||||
|
||||
puts part1
|
||||
puts part2
|
||||
24
day8.cr
Normal file
24
day8.cr
Normal file
@@ -0,0 +1,24 @@
|
||||
require "advent"
|
||||
require "./console.cr"
|
||||
|
||||
INPUT = input(2020, 8)
|
||||
|
||||
def part1
|
||||
run(parse(INPUT))[1].receive[1]
|
||||
end
|
||||
|
||||
def part2
|
||||
input = parse(INPUT)
|
||||
jnp = input.find_indices { |e| e[0] == "jmp" || e[0] == "nop" }
|
||||
jnp.each do |i|
|
||||
prog = input.clone
|
||||
op, int = prog[i]
|
||||
prog[i] = {op.tr("jmpnop", "nopjmp"), int}
|
||||
|
||||
code, acc = run(prog)[1].receive
|
||||
return acc if code == :term
|
||||
end
|
||||
end
|
||||
|
||||
puts part1
|
||||
puts part2
|
||||
255
day8.v
Normal file
255
day8.v
Normal file
@@ -0,0 +1,255 @@
|
||||
Require Import Coq.ZArith.Int.
|
||||
Require Import Coq.Lists.ListSet.
|
||||
Require Import Coq.Vectors.VectorDef.
|
||||
Require Import Coq.Vectors.Fin.
|
||||
Require Import Coq.Program.Equality.
|
||||
Require Import Coq.Logic.Eqdep_dec.
|
||||
Require Import Coq.Arith.Peano_dec.
|
||||
Require Import Coq.Program.Wf.
|
||||
Require Import Lia.
|
||||
|
||||
Module DayEight (Import M:Int).
|
||||
(* We need to coerce natural numbers into integers to add them. *)
|
||||
Parameter nat_to_t : nat -> t.
|
||||
(* We need a way to convert integers back into finite sets. *)
|
||||
Parameter clamp : forall {n}, t -> option (Fin.t n).
|
||||
|
||||
Definition fin := Fin.t.
|
||||
|
||||
(* The opcode of our instructions. *)
|
||||
Inductive opcode : Type :=
|
||||
| add
|
||||
| nop
|
||||
| jmp.
|
||||
|
||||
(* The result of running a program is either the accumulator
|
||||
or an infinite loop error. In the latter case, we return the
|
||||
set of instructions that we tried. *)
|
||||
Inductive run_result {n : nat} : Type :=
|
||||
| Ok : t -> run_result
|
||||
| Fail : set (fin n) -> run_result.
|
||||
|
||||
(* A single program state .*)
|
||||
Definition state n : Type := (fin (S n) * set (fin n) * t).
|
||||
|
||||
(* An instruction is a pair of an opcode and an argument. *)
|
||||
Definition inst : Type := (opcode * t).
|
||||
(* An input is a bounded list of instructions. *)
|
||||
Definition input (n : nat) := VectorDef.t inst n.
|
||||
(* 'indices' represents the list of instruction
|
||||
addresses, which are used for calculating jumps. *)
|
||||
Definition indices (n : nat) := VectorDef.t (fin n) n.
|
||||
|
||||
(* Change a jump to a nop, or a nop to a jump. *)
|
||||
Definition swap (i : inst) : inst :=
|
||||
match i with
|
||||
| (add, t) => (add, t)
|
||||
| (nop, t) => (jmp, t)
|
||||
| (jmp, t) => (nop, t)
|
||||
end.
|
||||
|
||||
Inductive swappable : inst -> Prop :=
|
||||
| swap_nop : forall t, swappable (nop, t)
|
||||
| swap_jmp : forall t, swappable (jmp, t).
|
||||
|
||||
(* Compute the destination jump index, an integer. *)
|
||||
Definition jump_t {n} (pc : fin n) (off : t) : t :=
|
||||
M.add (nat_to_t (proj1_sig (to_nat pc))) off.
|
||||
|
||||
(* Compute a destination index that's valid.
|
||||
Not all inputs are valid, so this may fail. *)
|
||||
Definition valid_jump_t {n} (pc : fin n) (off : t) : option (fin (S n)) := @clamp (S n) (jump_t pc off).
|
||||
|
||||
(* Cast a fin n to a fin (S n). *)
|
||||
Fixpoint weaken_one {n} (f : fin n) : fin (S n) :=
|
||||
match f with
|
||||
| F1 => F1
|
||||
| FS f' => FS (weaken_one f')
|
||||
end.
|
||||
|
||||
(* Convert a nat to fin. *)
|
||||
Fixpoint nat_to_fin (n : nat) : fin (S n) :=
|
||||
match n with
|
||||
| O => F1
|
||||
| S n' => FS (nat_to_fin n')
|
||||
end.
|
||||
|
||||
(* A finite natural is either its maximum value (aka nat_to_fin n),
|
||||
or it's not thatbig, which means it can be cast down to
|
||||
a fin (pred n). *)
|
||||
Lemma fin_big_or_small : forall {n} (f : fin (S n)),
|
||||
(f = nat_to_fin n) \/ (exists (f' : fin n), f = weaken_one f').
|
||||
Proof.
|
||||
(* Hey, looks like the creator of Fin provided
|
||||
us with nice inductive principles. Using Coq's
|
||||
default `induction` breaks here.
|
||||
|
||||
Merci, Pierre! *)
|
||||
apply Fin.rectS.
|
||||
- intros n. destruct n.
|
||||
+ left. reflexivity.
|
||||
+ right. exists F1. auto.
|
||||
- intros n p IH.
|
||||
destruct IH.
|
||||
+ left. rewrite H. reflexivity.
|
||||
+ right. destruct H as [f' Heq].
|
||||
exists (FS f'). simpl. rewrite Heq.
|
||||
reflexivity.
|
||||
Qed.
|
||||
|
||||
(* One modification: we really want to use 'allowed' addresses,
|
||||
a set that shrinks as the program continues, rather than 'visited'
|
||||
addresses, a set that increases as the program continues. *)
|
||||
Inductive step_noswap {n} : inst -> (fin n * t) -> (fin (S n) * t) -> Prop :=
|
||||
| step_noswap_add : forall pc acc t,
|
||||
step_noswap (add, t) (pc, acc) (FS pc, M.add acc t)
|
||||
| step_noswap_nop : forall pc acc t,
|
||||
step_noswap (nop, t) (pc, acc) (FS pc, acc)
|
||||
| step_noswap_jmp : forall pc pc' acc t,
|
||||
valid_jump_t pc t = Some pc' ->
|
||||
step_noswap (jmp, t) (pc, acc) (pc', acc).
|
||||
|
||||
Inductive done {n} : input n -> state n -> Prop :=
|
||||
| done_prog : forall inp v acc, done inp (nat_to_fin n, v, acc).
|
||||
|
||||
Inductive stuck {n} : input n -> state n -> Prop :=
|
||||
| stuck_prog : forall inp pc' v acc,
|
||||
~ set_In pc' v -> stuck inp (weaken_one pc', v, acc).
|
||||
|
||||
Inductive run_noswap {n} : input n -> state n -> state n -> Prop :=
|
||||
| run_noswap_ok : forall inp st, done inp st -> run_noswap inp st st
|
||||
| run_noswap_fail : forall inp st, stuck inp st -> run_noswap inp st st
|
||||
| run_noswap_trans : forall inp pc pc' v acc acc' st',
|
||||
set_In pc v ->
|
||||
step_noswap (nth inp pc) (pc, acc) (pc', acc') ->
|
||||
run_noswap inp (pc', set_remove Fin.eq_dec pc v, acc') st' ->
|
||||
run_noswap inp (weaken_one pc, v, acc) st'.
|
||||
|
||||
Inductive run_swap {n} : input n -> state n -> state n -> Prop :=
|
||||
| run_swap_normal : forall inp pc pc' v acc acc' st',
|
||||
set_In pc v ->
|
||||
~ swappable (nth inp pc) ->
|
||||
step_noswap (nth inp pc) (pc, acc) (pc', acc') ->
|
||||
run_swap inp (pc', set_remove Fin.eq_dec pc v, acc') st' ->
|
||||
run_swap inp (weaken_one pc, v, acc) st'
|
||||
| run_swap_swapped_ok : forall inp pc pc' v acc acc' st',
|
||||
set_In pc v ->
|
||||
swappable (nth inp pc) ->
|
||||
step_noswap (swap (nth inp pc)) (pc, acc) (pc', acc') ->
|
||||
run_noswap inp (pc', set_remove Fin.eq_dec pc v, acc') st' ->
|
||||
done inp st' ->
|
||||
run_swap inp (weaken_one pc, v, acc) st'
|
||||
| run_swap_swapped_next : forall inp pc pc'w pc'n v acc acc'w acc'n st'w st'n,
|
||||
set_In pc v ->
|
||||
swappable (nth inp pc) ->
|
||||
step_noswap (swap (nth inp pc)) (pc, acc) (pc'w, acc'w) ->
|
||||
run_noswap inp (pc'w, set_remove Fin.eq_dec pc v, acc'w) st'w ->
|
||||
stuck inp st'w ->
|
||||
step_noswap (nth inp pc) (pc, acc) (pc'n, acc'n) ->
|
||||
run_swap inp (pc'n, set_remove Fin.eq_dec pc v, acc'n) st'n ->
|
||||
run_swap inp (weaken_one pc, v, acc) st'n.
|
||||
|
||||
Inductive valid_inst {n} : inst -> fin n -> Prop :=
|
||||
| valid_inst_add : forall t f, valid_inst (add, t) f
|
||||
| valid_inst_nop : forall t f f',
|
||||
valid_jump_t f t = Some f' -> valid_inst (nop, t) f
|
||||
| valid_inst_jmp : forall t f f',
|
||||
valid_jump_t f t = Some f' -> valid_inst (jmp, t) f.
|
||||
|
||||
(* An input is valid if all its instructions are valid. *)
|
||||
Definition valid_input {n} (inp : input n) : Prop := forall (pc : fin n),
|
||||
valid_inst (nth inp pc) pc.
|
||||
|
||||
Section ValidInput.
|
||||
Variable n : nat.
|
||||
Variable inp : input n.
|
||||
Hypothesis Hv : valid_input inp.
|
||||
|
||||
Theorem valid_input_can_step : forall pc acc, exists pc' acc',
|
||||
step_noswap (nth inp pc) (pc, acc) (pc', acc').
|
||||
Proof.
|
||||
intros pc acc.
|
||||
destruct (nth inp pc) eqn:Hop.
|
||||
destruct o.
|
||||
- exists (FS pc). exists (M.add acc t0). apply step_noswap_add.
|
||||
- exists (FS pc). exists acc. eapply step_noswap_nop.
|
||||
- specialize (Hv pc). rewrite Hop in Hv. inversion Hv; subst.
|
||||
exists f'. exists acc. eapply step_noswap_jmp. apply H0.
|
||||
Qed.
|
||||
|
||||
(* A program is either done, stuck (at an invalid/visited address), or can step. *)
|
||||
Theorem valid_input_progress : forall pc v acc,
|
||||
(pc = nat_to_fin n /\ done inp (pc, v, acc)) \/
|
||||
(exists pcs, pc = weaken_one pcs /\
|
||||
((~ set_In pcs v /\ stuck inp (pc, v, acc)) \/
|
||||
(exists pc' acc', set_In pcs v /\
|
||||
step_noswap (nth inp pcs) (pcs, acc) (pc', acc')))).
|
||||
Proof.
|
||||
intros pc v acc.
|
||||
(* Have we reached the end? *)
|
||||
destruct (fin_big_or_small pc).
|
||||
(* We're at the end, so we're done. *)
|
||||
left. rewrite H. split. reflexivity. apply done_prog.
|
||||
(* We're not at the end. *)
|
||||
right. destruct H as [pcs H]. exists pcs. rewrite H. split. reflexivity.
|
||||
(* We're not at the end. Is the PC valid? *)
|
||||
destruct (set_In_dec Fin.eq_dec pcs v).
|
||||
- (* It is. *)
|
||||
right.
|
||||
destruct (valid_input_can_step pcs acc) as [pc' [acc' Hstep]].
|
||||
exists pc'; exists acc'; auto.
|
||||
- (* It is not. *)
|
||||
left. split; auto. apply stuck_prog; auto.
|
||||
Qed.
|
||||
|
||||
Theorem list_length_induction {X : Type} (P : list X -> Prop) :
|
||||
(forall l, (forall l', length l' < length l -> P l') -> P l) ->
|
||||
forall l, P l.
|
||||
Proof.
|
||||
intros Hrec.
|
||||
assert (forall (l l' : list X), length l' <= length l -> P l').
|
||||
{ induction l; intros l' Hlen; apply Hrec; intros l'0 Hlen0.
|
||||
- simpl in Hlen. lia.
|
||||
- apply IHl. simpl in Hlen. lia. }
|
||||
intros l. apply H with l. lia.
|
||||
Qed.
|
||||
|
||||
Theorem set_remove_length : forall (f : fin n) (s : set (fin n)),
|
||||
set_In f s ->
|
||||
length (set_remove Fin.eq_dec f s) < length s.
|
||||
Proof.
|
||||
intros f s Hin.
|
||||
induction s.
|
||||
- inversion Hin.
|
||||
- simpl. destruct (Fin.eq_dec f a) eqn:Heq.
|
||||
+ unfold lt. apply le_n. (* Why couldn't lia get this one? *)
|
||||
+ inversion Hin; subst. exfalso. apply n0. auto.
|
||||
apply IHs in H. simpl. lia.
|
||||
Qed.
|
||||
|
||||
Theorem valid_input_terminates : forall (pc : fin (S n)) (v : set (fin n)) (acc : t),
|
||||
(exists pc', run_noswap inp (pc, v, acc) pc').
|
||||
Proof.
|
||||
intros pc v. generalize dependent pc.
|
||||
induction v using list_length_induction.
|
||||
intros pc acc.
|
||||
destruct (valid_input_progress pc l acc) as [[_ Hd]|[pc' [Hw [[_ Hst]|[pc'' [acc'' [Hin Hst]]]]]]].
|
||||
- (* We're done. *)
|
||||
eexists. apply run_noswap_ok. assumption.
|
||||
- (* We're stuck. *)
|
||||
eexists. apply run_noswap_fail. assumption.
|
||||
- (* We can make a step. This will remove our current PC from the valid list, *)
|
||||
edestruct (H (set_remove Fin.eq_dec pc' l)).
|
||||
(* Since the PC must be in the list, removing it makes the list smaller. *)
|
||||
apply (set_remove_length _ _ Hin).
|
||||
(* Without the current PC, our valid set shrinks.
|
||||
Since this is the inductive step, we have assumed
|
||||
that programs with smaller sets of valid PCs always
|
||||
terminate. Thus, after we make the step, we're done. *)
|
||||
exists x. subst. eapply run_noswap_trans.
|
||||
+ auto.
|
||||
+ apply Hst.
|
||||
+ apply H0.
|
||||
Qed.
|
||||
End ValidInput.
|
||||
End DayEight.
|
||||
37
day9.cr
Normal file
37
day9.cr
Normal file
@@ -0,0 +1,37 @@
|
||||
require "advent"
|
||||
INPUT = input(2020, 9).lines.map(&.to_i64)
|
||||
|
||||
def is_sum(is, from, to, n)
|
||||
if to <= from
|
||||
return n == 0_i64
|
||||
end
|
||||
return is_sum(is, from, to-1, n) || is_sum(is, from, to-1, n-is[to-1])
|
||||
end
|
||||
|
||||
def part1(input)
|
||||
is = input
|
||||
i = 25
|
||||
loop do
|
||||
return is[i] unless is_sum(is, i-25, i, is[i])
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
|
||||
def part2(input, i)
|
||||
input.each_with_index do |e, j|
|
||||
next if e == i
|
||||
acc = i-e
|
||||
k = j
|
||||
while acc > 0
|
||||
k += 1
|
||||
acc -= input[k]
|
||||
end
|
||||
if acc == 0
|
||||
min, max = input[j..k].minmax
|
||||
return min+max
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
p1 = part1(INPUT.clone)
|
||||
puts part2(INPUT.clone, p1)
|
||||
48
graph.cr
48
graph.cr
@@ -1,48 +0,0 @@
|
||||
class Graph(A)
|
||||
def initialize
|
||||
@edges = {} of A => Set({A, Int32})
|
||||
end
|
||||
|
||||
def add_edge(f, t, c = 1)
|
||||
@edges[f] ||= Set({A, Int32}).new
|
||||
@edges[f] << {t, c}
|
||||
end
|
||||
|
||||
def add_biedge(f, t, c = 1)
|
||||
add_edge(f, t, c)
|
||||
add_edge(t, f, c)
|
||||
end
|
||||
|
||||
def find_path(f, t)
|
||||
visited = Set(A).new
|
||||
candidates = Set { f }
|
||||
distances = {f => 0}
|
||||
prev = {} of A => A
|
||||
|
||||
while !candidates.empty?
|
||||
candidate = candidates.min_by { |c| distances[c] }
|
||||
break if candidate == t
|
||||
visited << candidate
|
||||
candidates.delete candidate
|
||||
dist = distances[candidate]
|
||||
|
||||
@edges.fetch(candidate, Set({A, Int32}).new).each do |e|
|
||||
node, cost = e
|
||||
new_dist = dist + cost
|
||||
candidates << node unless visited.includes? node
|
||||
next if (old_dist = distances[node]?) && old_dist < new_dist
|
||||
distances[node] = new_dist
|
||||
prev[node] = candidate
|
||||
end
|
||||
end
|
||||
|
||||
backtrack = t
|
||||
path = [t] of A
|
||||
while backtrack != f
|
||||
return nil unless prev_bt = prev[backtrack]?
|
||||
path << prev_bt
|
||||
backtrack = prev_bt
|
||||
end
|
||||
{path.reverse!, distances[t]}
|
||||
end
|
||||
end
|
||||
83
heap.cr
83
heap.cr
@@ -1,83 +0,0 @@
|
||||
class Array(T)
|
||||
def bubble_up(i, &cmp)
|
||||
return if i >= size
|
||||
while i != 0
|
||||
j = (i-1)//2
|
||||
break if yield self[i], self[j]
|
||||
self[i], self[j] = self[j], self[i]
|
||||
i = j
|
||||
end
|
||||
end
|
||||
|
||||
def percalate_down(i, &cmp)
|
||||
while i*2+1 < size
|
||||
j1, j2 = i*2+1, i*2+2
|
||||
v1 = self[j1]
|
||||
v2 = self[j2]?
|
||||
if v2 && (yield v1, v2) && (yield self[i], v2)
|
||||
self[j2], self[i] = self[i], v2
|
||||
i = j2
|
||||
elsif yield self[i], v1
|
||||
self[j1], self[i] = self[i], v1
|
||||
i = j1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def heapify(&cmp : T,T -> Bool)
|
||||
size.times do |i|
|
||||
i = size - i - 1
|
||||
bubble_up(i, &cmp)
|
||||
end
|
||||
self
|
||||
end
|
||||
|
||||
def heapify
|
||||
heapify do |i,j|
|
||||
i < j
|
||||
end
|
||||
end
|
||||
|
||||
def heap_push(v, &cmp : T,T -> Bool)
|
||||
self << v
|
||||
bubble_up(size - 1, &cmp)
|
||||
end
|
||||
|
||||
def heap_push(v)
|
||||
heap_push(v) do |i,j|
|
||||
i < j
|
||||
end
|
||||
end
|
||||
|
||||
def heap_pop(&cmp : T,T -> Bool)
|
||||
self[0], self[size-1] = self[size-1], self[0]
|
||||
v = pop
|
||||
percalate_down(0, &cmp)
|
||||
v
|
||||
end
|
||||
|
||||
def heap_pop
|
||||
heap_pop do |i, j|
|
||||
i < j
|
||||
end
|
||||
end
|
||||
|
||||
def is_heap?(&cmp : T,T -> Bool)
|
||||
(size-1).times do |i|
|
||||
i = size - i - 1
|
||||
vi = self[i]
|
||||
vp = self[(i-1)//2]
|
||||
return false unless (yield self[i], self[(i-1)//2]) || vi == vp
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
def is_heap?
|
||||
is_heap? do |i,j|
|
||||
i < j
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
13
shard.yml
Normal file
13
shard.yml
Normal file
@@ -0,0 +1,13 @@
|
||||
name: advent-2020
|
||||
version: 0.1.0
|
||||
|
||||
authors:
|
||||
- your-name-here <your-email-here>
|
||||
|
||||
dependencies:
|
||||
advent:
|
||||
git: https://dev.danilafe.com/Advent-of-Code/advent
|
||||
|
||||
crystal: 0.35.1
|
||||
|
||||
license: MIT
|
||||
13
template.cr
13
template.cr
@@ -1,12 +1,11 @@
|
||||
INPUT = File.read("dayN.txt")#.lines.map(&.chomp)
|
||||
require "advent"
|
||||
INPUT = input(2020, n)#.lines.map(&.to_i32)
|
||||
|
||||
def part1
|
||||
input = INPUT.clone
|
||||
def part1(input)
|
||||
end
|
||||
|
||||
def part2
|
||||
input = INPUT.clone
|
||||
def part2(input)
|
||||
end
|
||||
|
||||
part1
|
||||
part2
|
||||
puts part1(INPUT.clone)
|
||||
puts part2(INPUT.clone)
|
||||
|
||||
Reference in New Issue
Block a user