From 32895b3e171e26ca65b73e53456f67426b222536 Mon Sep 17 00:00:00 2001 From: Danila Fedorin Date: Sun, 20 Dec 2020 00:31:30 -0800 Subject: [PATCH] Add updated solutions, including day 20. --- day15.cr | 27 +++------ day17.cr | 51 ++++++++--------- day18.cr | 54 ++++++++++++++++++ day19.cr | 68 +++++++++++++++++++++++ day20.cr | 166 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 321 insertions(+), 45 deletions(-) create mode 100644 day18.cr create mode 100644 day19.cr create mode 100644 day20.cr diff --git a/day15.cr b/day15.cr index a3a1709..0a31433 100644 --- a/day15.cr +++ b/day15.cr @@ -2,27 +2,14 @@ require "advent" INPUT = input(2020, 15).split(",").map(&.to_i32) def run(input, times) - ls = {} of Int32 => {Int32,Int32} - last = 0 - input.each_with_index do |n, i| - ls[n] ||= {-1,-1} - f,s = ls[n] - ls[n] = {i,f} - last = n + ls = {} of Int32 => Int32 + temp = 0 + (times-1).times do |i| + n = input[i]? || temp + temp = i - (ls[n]? || i) + ls[n] = i end - count = input.size - while count < times - n = 0 - if ls[last][1] != -1 - n = ls[last][0] - ls[last][1] - end - last = n - ls[n] ||= {-1,-1} - f,s = ls[n] - ls[n] = {count,f} - count += 1 - end - last + return temp end def part1(input) diff --git a/day17.cr b/day17.cr index 12c97f9..da05e7a 100644 --- a/day17.cr +++ b/day17.cr @@ -1,46 +1,47 @@ require "advent" +require "benchmark" + INPUT = input(2020, 17).lines.map(&.chars) -def part1(input) +def solve(input, dim) step = input.clone - cubes = Set({Int32,Int32,Int32,Int32}).new - new_cubes = Set({Int32,Int32,Int32,Int32}).new + 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,0,0} if c == '#' + cubes << [x,y].concat([0] * (dim-2)) if c == '#' end end - 6.times do |i| - neighbor_count = {} of {Int32,Int32,Int32,Int32} => Int32 - cubes.each do |c| - x,y,z,w = c - (-1..1).each do |dx| - (-1..1).each do |dy| - (-1..1).each do |dz| - (-1..1).each do |dw| - next if dx == 0 && dy == 0 && dz == 0 && dw == 0 - neighbor_count[{x+dx,y+dy,z+dz,w+dw}] = (neighbor_count[{x+dx,y+dy,z+dz,w+dw}]? || 0) + 1 - end - 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| - if cubes.includes?(n) - new_cubes << n if (i == 2 || i == 3) - elsif i == 3 - new_cubes << n - end + new_cubes << n if i == 3 || (cubes.includes?(n) && i == 2) end new_cubes, cubes = cubes, new_cubes end cubes.size end -def part2(input) +def part1(input) + solve(input, 3) end -puts part1(INPUT.clone) -puts part2(INPUT.clone) +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 diff --git a/day18.cr b/day18.cr new file mode 100644 index 0000000..7db0db1 --- /dev/null +++ b/day18.cr @@ -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) diff --git a/day19.cr b/day19.cr new file mode 100644 index 0000000..779f74c --- /dev/null +++ b/day19.cr @@ -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}) diff --git a/day20.cr b/day20.cr new file mode 100644 index 0000000..22b3304 --- /dev/null +++ b/day20.cr @@ -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)