diff --git a/Gemfile b/Gemfile index 0f609f4..463fd37 100644 --- a/Gemfile +++ b/Gemfile @@ -5,5 +5,6 @@ source 'https://rubygems.org' gem 'colorize' gem 'debug' gem 'minitest' +gem 'priority_queue_cxx', require: false gem 'rake' gem 'rubocop' diff --git a/Gemfile.lock b/Gemfile.lock index ab3679a..d52a8a9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,32 +3,33 @@ GEM specs: ast (2.4.2) colorize (1.1.0) - debug (1.8.0) - irb (>= 1.5.0) - reline (>= 0.3.1) - io-console (0.6.0) + debug (1.9.0) + irb (~> 1.10) + reline (>= 0.3.8) + io-console (0.7.1) irb (1.10.1) rdoc reline (>= 0.3.8) json (2.7.1) language_server-protocol (3.17.0.3) minitest (5.20.0) - parallel (1.23.0) + parallel (1.24.0) parser (3.2.2.4) ast (~> 2.4.1) racc + priority_queue_cxx (0.3.6) psych (5.1.1.1) stringio racc (1.7.3) rainbow (3.1.1) rake (13.1.0) - rdoc (6.6.1) + rdoc (6.6.2) psych (>= 4.0.0) regexp_parser (2.8.3) reline (0.4.1) io-console (~> 0.5) rexml (3.2.6) - rubocop (1.58.0) + rubocop (1.59.0) json (~> 2.3) language_server-protocol (>= 3.17.0) parallel (~> 1.10) @@ -42,7 +43,7 @@ GEM rubocop-ast (1.30.0) parser (>= 3.2.1.0) ruby-progressbar (1.13.0) - stringio (3.1.0) + stringio (3.1.1) unicode-display_width (2.5.0) PLATFORMS @@ -54,6 +55,7 @@ DEPENDENCIES colorize debug minitest + priority_queue_cxx rake rubocop diff --git a/app.rb b/app.rb index ecbfcff..0773955 100644 --- a/app.rb +++ b/app.rb @@ -6,4 +6,5 @@ require 'bundler/setup' require './lib/runner' require './lib/bench' +require './lib/util' Dir['./lib/days/*.rb'].each { |f| require f } diff --git a/examples/17.txt b/examples/17.txt new file mode 100644 index 0000000..f400d6e --- /dev/null +++ b/examples/17.txt @@ -0,0 +1,13 @@ +2413432311323 +3215453535623 +3255245654254 +3446585845452 +4546657867536 +1438598798454 +4457876987766 +3637877979653 +4654967986887 +4564679986453 +1224686865563 +2546548887735 +4322674655533 diff --git a/lib/days/14.rb b/lib/days/14.rb index c2667d5..ea9f01c 100644 --- a/lib/days/14.rb +++ b/lib/days/14.rb @@ -70,15 +70,6 @@ def rotate_clockwise(grid) new_grid end - def print_grid(grid) - grid.first.length.times do |y| - grid.length.times do |x| - print grid[x][y] - end - print "\n" - end - end - def part1(input) calculate(shift_grid(parse(input))) end diff --git a/lib/days/17.rb b/lib/days/17.rb new file mode 100644 index 0000000..b1cc192 --- /dev/null +++ b/lib/days/17.rb @@ -0,0 +1,105 @@ +class Day17 + require 'fc' + include Util + + Crucible = Struct.new( + :x, :y, :cost, :dir, :moves_in_dir + ) do + + def pos + [x, y] + end + end + + def part1(input) + grid = parse_grid(input, :to_i) + goal = [grid.length - 1, grid.first.length - 1] + + queue = FastContainers::PriorityQueue.new(:min) + queue.push(Crucible.new(0, 0, 0, :down, 0), 0) + visited = Set.new + + loop do + crucible = queue.top + queue.pop + + return crucible.cost if crucible.pos == goal + + next_steps = available_moves(grid, crucible.pos, crucible.cost, crucible.dir, crucible.moves_in_dir) + + next_steps.each do |next_step| + hash = [next_step.pos, next_step.dir, next_step.moves_in_dir] + next if visited.include?(hash) + + visited.add(hash) + queue.push(next_step, next_step.cost) + end + + break if queue.none? + end + end + + LEFT_TO_RIGHT = :right + RIGHT_TO_LEFT = :left + DOWN = :down + UP = :up + + def available_moves(grid, position, current_cost, current_direction, num_direction) + moves = [] + + if position[1] != grid.first.length - 1 && + current_direction != UP && + !(current_direction == DOWN && num_direction >= 3) + + moves << Crucible.new( + position[0], + position[1] + 1, + current_cost + grid[position[0]][position[1] + 1], + DOWN, + current_direction == DOWN ? num_direction + 1 : 1) + end + + if !position[1].zero? && + current_direction != DOWN && + !(current_direction == UP && num_direction >= 3) + + moves << Crucible.new( + position[0], + position[1] - 1, + current_cost + grid[position[0]][position[1] - 1], + UP, + current_direction == UP ? num_direction + 1 : 1) + end + + if position[0] != grid.length - 1 && + current_direction != RIGHT_TO_LEFT && + !(current_direction == LEFT_TO_RIGHT && num_direction >= 3) + + moves << Crucible.new( + position[0] + 1, + position[1], + current_cost + grid[position[0] + 1][position[1]], + LEFT_TO_RIGHT, + current_direction == LEFT_TO_RIGHT ? num_direction + 1 : 1) + end + + if !position[0].zero? && + current_direction != LEFT_TO_RIGHT && + !(current_direction == RIGHT_TO_LEFT && num_direction >= 3) + + moves << Crucible.new( + position[0] - 1, + position[1], + current_cost + grid[position[0] - 1][position[1]], + RIGHT_TO_LEFT, + current_direction == RIGHT_TO_LEFT ? num_direction + 1 : 1) + end + + moves + end + + def part2(_input) + 'TODO' + end + +end diff --git a/lib/util.rb b/lib/util.rb new file mode 100644 index 0000000..9fe6e65 --- /dev/null +++ b/lib/util.rb @@ -0,0 +1,28 @@ +module Util + def parse_grid(input, fn = nil) + lines = input.split("\n") + grid = [] + + lines.length.times do |n| + lines[n].chars.each_with_index do |char, i| + grid[i] ||= [] + grid[i] << (fn ? char.send(fn) : char) + end + end + + grid + end + + def print_grid(grid) + grid.first.length.times do |y| + grid.length.times do |x| + print grid[x][y] + end + print "\n" + end + end + + def percent(x, y) + "#{(x / y.to_f * 100).round(1)}%" + end +end diff --git a/test/17_test.rb b/test/17_test.rb new file mode 100644 index 0000000..489ca65 --- /dev/null +++ b/test/17_test.rb @@ -0,0 +1,18 @@ +require 'minitest/autorun' +require 'minitest/pride' +require_relative '../app' + +class TestDay17 < Minitest::Test + def setup + @data = File.read(File.join(APP_ROOT, 'examples', '17.txt')).rstrip + @day = Day17.new + end + + def test_part1 + assert_equal @day.part1(@data), 102 + end + + def test_part2 + assert_equal @day.part2(@data), '' + end +end