Compare commits

...

17 Commits

18 changed files with 997 additions and 108 deletions

55
README.md Normal file
View 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
View 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

60
day14.cr Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

228
day8.v
View File

@@ -29,6 +29,7 @@ Module DayEight (Import M:Int).
| 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. *)
@@ -39,6 +40,18 @@ Module DayEight (Import M:Int).
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.
@@ -47,18 +60,23 @@ Module DayEight (Import M:Int).
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.
@@ -79,61 +97,17 @@ Module DayEight (Import M:Int).
reflexivity.
Qed.
Lemma weaken_one_inj : forall n (f1 f2 : fin n),
(weaken_one f1 = weaken_one f2 -> f1 = f2).
Proof.
remember (fun {n} (a b : fin n) => weaken_one a = weaken_one b -> a = b) as P.
(* Base case for rect2 *)
assert (forall n, @P (S n) F1 F1).
{rewrite HeqP. intros n Heq. reflexivity. }
(* 'Impossible' cases for rect2. *)
assert (forall {n} (f : fin n), P (S n) F1 (FS f)).
{rewrite HeqP. intros n f Heq. simpl in Heq. inversion Heq. }
assert (forall {n} (f : fin n), P (S n) (FS f) F1).
{rewrite HeqP. intros n f Heq. simpl in Heq. inversion Heq. }
(* Recursive case for rect2. *)
assert (forall {n} (f g : fin n), P n f g -> P (S n) (FS f) (FS g)).
{rewrite HeqP. intros n f g IH Heq.
simpl in Heq. injection Heq as Heq'.
apply inj_pair2_eq_dec in Heq'.
- rewrite IH. reflexivity. assumption.
- apply eq_nat_dec. }
(* Actually apply recursion. *)
(* This can't be _the_ way to do this. *)
intros n.
specialize (@Fin.rect2 P H H0 H1 H2 n) as Hind.
rewrite HeqP in Hind. apply Hind.
Qed.
Lemma weaken_neq_to_fin : forall {n} (f : fin (S n)),
nat_to_fin (S n) <> weaken_one f.
Proof.
apply Fin.rectS; intros n Heq.
- inversion Heq.
- intros IH. simpl. intros Heq'.
injection Heq' as Hinj. apply inj_pair2_eq_dec in Hinj.
+ simpl in IH. apply IH. apply Hinj.
+ apply eq_nat_dec.
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} : input n -> state n -> state n -> Prop :=
| step_noswap_add : forall inp pc' v acc t,
nth inp pc' = (add, t) ->
set_In pc' v ->
step_noswap inp (weaken_one pc', v, acc) (FS pc', set_remove Fin.eq_dec pc' v, M.add acc t)
| step_noswap_nop : forall inp pc' v acc t,
nth inp pc' = (nop, t) ->
set_In pc' v ->
step_noswap inp (weaken_one pc', v, acc) (FS pc', set_remove Fin.eq_dec pc' v, acc)
| step_noswap_jmp : forall inp pc' pc'' v acc t,
nth inp pc' = (jmp, t) ->
set_In pc' v ->
valid_jump_t pc' t = Some pc'' ->
step_noswap inp (weaken_one pc', v, acc) (pc'', set_remove Fin.eq_dec pc' v, acc).
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).
@@ -145,8 +119,35 @@ Module DayEight (Import M:Int).
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 st st' st'',
step_noswap inp st st' -> run_noswap inp st' 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
@@ -164,80 +165,91 @@ Module DayEight (Import M:Int).
Variable inp : input n.
Hypothesis Hv : valid_input inp.
Lemma step_if_possible : forall pcs v acc,
set_In pcs v ->
exists pc' acc', step_noswap inp (weaken_one pcs, v, acc) (pc', set_remove Fin.eq_dec pcs v, acc').
Theorem valid_input_can_step : forall pc acc, exists pc' acc',
step_noswap (nth inp pc) (pc, acc) (pc', acc').
Proof.
intros pcs v acc Hin.
remember (nth inp pcs) as instr. destruct instr as [op t]. destruct op.
+ exists (FS pcs). exists (M.add acc t). apply step_noswap_add; auto.
+ exists (FS pcs). exists acc. apply step_noswap_nop with t; auto.
+ unfold valid_input in Hv. specialize (Hv pcs).
rewrite <- Heqinstr in Hv. inversion Hv; subst.
exists f'. exists acc. apply step_noswap_jmp with t; auto.
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 inp (pc, v, acc) (pc', set_remove Fin.eq_dec pcs 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. Is the PC valid? *)
(* 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 (step_if_possible pcs v acc) as [pc' [acc' Hstep]]; auto.
exists pc'. exists acc'. split; auto.
- (* It i not. *)
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.
Program Fixpoint valid_input_terminates (pc : fin (S n)) (v : set (fin n)) (acc : t) (Hnd : List.NoDup v)
{ measure (length v) }:
(exists pc', run_noswap inp (pc, v, acc) pc') :=
match valid_input_progress pc v acc with
| or_introl (conj Heq Hdone) =>
inhabited_sig_to_exists
(inhabits
(@exist (state n)
(fun x => run_noswap inp (pc, v, acc) x) (pc, v, acc) (run_noswap_ok _ _ Hdone)))
| or_intror (ex_intro _ pcs (conj Hw w)) =>
match w with
| or_introl (conj Hnin Hstuck) =>
inhabited_sig_to_exists
(inhabits
(@exist (state n)
(fun x => run_noswap inp (pc, v, acc) x) (pc, v, acc) (run_noswap_fail _ _ Hstuck)))
| or_intror (ex_intro _ pc' (ex_intro _ acc' (conj Hin Hst))) =>
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 =>
inhabited_sig_to_exists
(inhabits
(@exist (state n)
(fun x => run_noswap inp (pc, v, acc) x) pc''
(run_noswap_trans _ _ (pc', set_remove Fin.eq_dec pcs v, acc') _ Hst Hrun)))
end
end
end.
Obligation 1.
clear Heq_anonymous. clear valid_input_terminates. clear Hst.
induction v.
- inversion Hin.
- destruct (Fin.eq_dec pcs a) eqn:Heq_dec.
+ simpl. rewrite Heq_dec. lia.
+ inversion Hnd; subst.
inversion Hin. subst. exfalso. apply n0. auto.
specialize (IHv H2 H).
simpl. rewrite Heq_dec. simpl. lia.
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.
(* Stoppped here. *)
Admitted. *)
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.