Compare commits
14 Commits
f53c65fb0d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a8503c3fe | |||
| 2b69cbd391 | |||
| 661bcbb557 | |||
| c6ac448ad2 | |||
| 3d85144065 | |||
| 4e8b636d2f | |||
| ee819d1362 | |||
| 34cb639327 | |||
| b33e678c7e | |||
| 94ddabc590 | |||
| 32895b3e17 | |||
| 77c91f8386 | |||
| 8ed0896adb | |||
| c17cf38795 |
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.
|
||||||
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
|
||||||
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})
|
||||||
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)
|
||||||
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)
|
||||||
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
|
||||||
77
day8.v
77
day8.v
@@ -202,35 +202,54 @@ Module DayEight (Import M:Int).
|
|||||||
left. split; auto. apply stuck_prog; auto.
|
left. split; auto. apply stuck_prog; auto.
|
||||||
Qed.
|
Qed.
|
||||||
|
|
||||||
(* A valid input always terminates, either by getting to the end of the program,
|
Theorem list_length_induction {X : Type} (P : list X -> Prop) :
|
||||||
or by looping and thus getting stuck. *)
|
(forall l, (forall l', length l' < length l -> P l') -> P l) ->
|
||||||
Program Fixpoint valid_input_terminates (pc : fin (S n)) (v : set (fin n)) (acc : t) (Hnd : List.NoDup v)
|
forall l, P l.
|
||||||
{ measure (length v) }:
|
Proof.
|
||||||
(exists pc', run_noswap inp (pc, v, acc) pc') :=
|
intros Hrec.
|
||||||
match valid_input_progress pc v acc with
|
assert (forall (l l' : list X), length l' <= length l -> P l').
|
||||||
| or_introl (conj Heq Hdone) => _
|
{ induction l; intros l' Hlen; apply Hrec; intros l'0 Hlen0.
|
||||||
| or_intror (ex_intro _ pcs (conj Hw w)) =>
|
- simpl in Hlen. lia.
|
||||||
match w with
|
- apply IHl. simpl in Hlen. lia. }
|
||||||
| or_introl (conj Hnin Hstuck) => _
|
intros l. apply H with l. lia.
|
||||||
| or_intror (ex_intro _ pc' (ex_intro _ acc' (conj Hin Hst))) =>
|
Qed.
|
||||||
match valid_input_terminates pc' (set_remove Fin.eq_dec pcs v) acc' (set_remove_nodup Fin.eq_dec pcs Hnd) with
|
|
||||||
| ex_intro _ pc'' Hrun => _
|
Theorem set_remove_length : forall (f : fin n) (s : set (fin n)),
|
||||||
end
|
set_In f s ->
|
||||||
end
|
length (set_remove Fin.eq_dec f s) < length s.
|
||||||
end.
|
Proof.
|
||||||
Obligation 1. eexists. apply run_noswap_ok. assumption. Qed.
|
intros f s Hin.
|
||||||
Obligation 2. eexists. apply run_noswap_fail. assumption. Qed.
|
induction s.
|
||||||
Obligation 3.
|
- inversion Hin.
|
||||||
clear Heq_anonymous. clear valid_input_terminates. clear Hst.
|
- simpl. destruct (Fin.eq_dec f a) eqn:Heq.
|
||||||
induction v.
|
+ unfold lt. apply le_n. (* Why couldn't lia get this one? *)
|
||||||
- inversion Hin.
|
+ inversion Hin; subst. exfalso. apply n0. auto.
|
||||||
- destruct (Fin.eq_dec pcs a) eqn:Heq_dec.
|
apply IHs in H. simpl. lia.
|
||||||
+ simpl. rewrite Heq_dec. lia.
|
Qed.
|
||||||
+ inversion Hnd; subst.
|
|
||||||
inversion Hin. subst. exfalso. apply n0. auto.
|
Theorem valid_input_terminates : forall (pc : fin (S n)) (v : set (fin n)) (acc : t),
|
||||||
specialize (IHv H2 H).
|
(exists pc', run_noswap inp (pc, v, acc) pc').
|
||||||
simpl. rewrite Heq_dec. simpl. lia.
|
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.
|
Qed.
|
||||||
Obligation 4. eexists. eapply run_noswap_trans; auto. apply Hst. apply Hrun. Qed.
|
|
||||||
End ValidInput.
|
End ValidInput.
|
||||||
End DayEight.
|
End DayEight.
|
||||||
|
|||||||
Reference in New Issue
Block a user