From d1b87d10007c20d71f2a911323a9cb19ef937a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20=C3=81lvaro?= Date: Tue, 30 Jan 2024 17:47:53 +0100 Subject: [PATCH] Advent of Code 2023 - Day 11 - Solve day 11 puzzle --- README.md | 5 + lib/puzzles/2023/day11/README.md | 126 +++++++++++++++++++++++ lib/puzzles/2023/day11/day11.rb | 13 +++ lib/puzzles/2023/day11/input.txt | 140 ++++++++++++++++++++++++++ lib/puzzles/2023/day11/part1.rb | 130 ++++++++++++++++++++++++ lib/puzzles/2023/day11/part2.rb | 85 ++++++++++++++++ sig/puzzles/2023/day11.rbs | 39 +++++++ sig/test/puzzles/2023/day11_test.rbs | 33 ++++++ test/puzzles/2023/day11/day11_test.rb | 81 +++++++++++++++ test/puzzles/2023/day11/test_data.txt | 10 ++ 10 files changed, 662 insertions(+) create mode 100644 lib/puzzles/2023/day11/README.md create mode 100644 lib/puzzles/2023/day11/day11.rb create mode 100644 lib/puzzles/2023/day11/input.txt create mode 100644 lib/puzzles/2023/day11/part1.rb create mode 100644 lib/puzzles/2023/day11/part2.rb create mode 100644 sig/puzzles/2023/day11.rbs create mode 100644 sig/test/puzzles/2023/day11_test.rbs create mode 100644 test/puzzles/2023/day11/day11_test.rb create mode 100644 test/puzzles/2023/day11/test_data.txt diff --git a/README.md b/README.md index b4f5c59..b7ff1e1 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,11 @@ SKIP_SLOW_TESTS=1 bundle exec rake test lib/puzzles/2023/day10 🌟🌟 + + 1️⃣1️⃣ Cosmic Expansion + lib/puzzles/2023/day11 + 🌟🌟 +

diff --git a/lib/puzzles/2023/day11/README.md b/lib/puzzles/2023/day11/README.md new file mode 100644 index 0000000..fdd622b --- /dev/null +++ b/lib/puzzles/2023/day11/README.md @@ -0,0 +1,126 @@ +# [Day 11: Cosmic Expansion](https://adventofcode.com/2023/day/11) + +## Part One + +You continue following signs for "Hot Springs" and eventually come across an [observatory](https://en.wikipedia.org/wiki/Observatory). The Elf within turns out to be a researcher studying cosmic expansion using the giant telescope here. + +He doesn't know anything about the missing machine parts; he's only visiting for this research project. However, he confirms that the hot springs are the next-closest area likely to have people; he'll even take you straight there once he's done with today's observation analysis. + +Maybe you can help him with the analysis to speed things up? + +The researcher has collected a bunch of data and compiled the data into a single giant **image** (your puzzle input). The image includes **empty space** (`.`) and **galaxies** (`#`). For example: + +``` +...#...... +.......#.. +#......... +.......... +......#... +.#........ +.........# +.......... +.......#.. +#...#..... +``` + +The researcher is trying to figure out the sum of the lengths of the **shortest path between every pair of galaxies**. However, there's a catch: the universe expanded in the time it took the light from those galaxies to reach the observatory. + +Due to something involving gravitational effects, **only some space expands**. In fact, the result is that **any rows or columns that contain no galaxies** should all actually be twice as big. + +In the above example, three columns and two rows contain no galaxies: + +``` + v v v + ...#...... + .......#.. + #......... +>..........< + ......#... + .#........ + .........# +>..........< + .......#.. + #...#..... + ^ ^ ^ +``` + +These rows and columns need to be **twice as big**; the result of cosmic expansion therefore looks like this: + +``` +....#........ +.........#... +#............ +............. +............. +........#.... +.#........... +............# +............. +............. +.........#... +#....#....... +``` + +Equipped with this expanded universe, the shortest path between every pair of galaxies can be found. It can help to assign every galaxy a unique number: + +``` +....1........ +.........2... +3............ +............. +............. +........4.... +.5........... +............6 +............. +............. +.........7... +8....9....... +``` + +In these 9 galaxies, there are **36 pairs**. Only count each pair once; order within the pair doesn't matter. For each pair, find any shortest path between the two galaxies using only steps that move up, down, left, or right exactly one `.` or `#` at a time. (The shortest path between two galaxies is allowed to pass through another galaxy.) + +For example, here is one of the shortest paths between galaxies `5` and `9`: + +``` +....1........ +.........2... +3............ +............. +............. +........4.... +.5........... +.##.........6 +..##......... +...##........ +....##...7... +8....9....... +``` + +This path has length `9` because it takes a minimum of **nine steps** to get from galaxy `5` to galaxy `9` (the eight locations marked `#` plus the step onto galaxy `9` itself). Here are some other example shortest path lengths: + +- Between galaxy `1` and galaxy `7`: 15 +- Between galaxy `3` and galaxy `6`: 17 +- Between galaxy `8` and galaxy `9`: 5 + +In this example, after expanding the universe, the sum of the shortest path between all 36 pairs of galaxies is **`374`**. + +Expand the universe, then find the length of the shortest path between every pair of galaxies. **What is the sum of these lengths?** + +Your puzzle answer was `10313550`. + +**The first half of this puzzle is complete! It provides one gold star:** 🌟 + +## Part Two + +The galaxies are much **older** (and thus much **farther apart**) than the researcher initially estimated. + +Now, instead of the expansion you did before, make each empty row or column **one million times** larger. That is, each empty row should be replaced with `1000000` empty rows, and each empty column should be replaced with `1000000` empty columns. + +(In the example above, if each empty row or column were merely `10` times larger, the sum of the shortest paths between every pair of galaxies would be **`1030`**. If each empty row or column were merely `100` times larger, the sum of the shortest paths between every pair of galaxies would be **`8410`**. However, your universe will need to expand far beyond these values.) + +Starting with the same initial image, expand the universe according to these new rules, then find the length of the shortest path between every pair of galaxies. **What is the sum of these lengths?** + +Your puzzle answer was `611998089572`. + +**Both parts of this puzzle are complete! They provide two gold stars:** 🌟🌟 diff --git a/lib/puzzles/2023/day11/day11.rb b/lib/puzzles/2023/day11/day11.rb new file mode 100644 index 0000000..1b4ac17 --- /dev/null +++ b/lib/puzzles/2023/day11/day11.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require_relative "part1" +require_relative "part2" + +module AdventOfCode + module Puzzles2023 + ## + # {include:file:lib/puzzles/2023/day11/README.md} + module Day11 + end + end +end diff --git a/lib/puzzles/2023/day11/input.txt b/lib/puzzles/2023/day11/input.txt new file mode 100644 index 0000000..8fbd6cf --- /dev/null +++ b/lib/puzzles/2023/day11/input.txt @@ -0,0 +1,140 @@ +..#............................#....................................................................................#..........#........#... +..........#.........................................#....................................................................................... +...................#..........................................................................#............................................. +.....................................................................................#.............................................#........ +.......#.......#.............#..........#......#...................#.....#.................................................................. +................................................................................#..................#..............#......................... +....................................#....................................................................................................... +...........#...................................................................................#......................................#..... +.......................................................................................................................#.........#.......... +.....................#.................................................#...................#.................#..............#............... +....#...........................................#...............#........................................................................... +.........................#.......................................................................#.......................................... +...................................#......................#.......................................................#.......................#. +..................#...........................................................#.......................#.................#...........#....... +..........#........................................................#..........................................#............................. +.............................................#.........#.....#.............................................................................. +.......................#.....................................................................#..............................#..........#.... +.............................#.......................................................#...........................#.......................... +..#...........#.......................#.....................................#............................................................... +...........................................................#.............................................#.................................. +......#..........................................................#......#........................................................#.......... +................................#.........#...........#....................................#........#...................#..................# +...........................#..................................................#..............................#.............................. +..............................................#.................................................#........................................... +.#.................................................................................................................#........................ +.......................................................................#.................................................................... +...............#.............................................................................................................#..........#... +.................................#.............................#............................................#............................... +.....#.................#...................#................................................................................................ +....................................................#...................................................#............#...................... +...........#.....#...................................................................#............................................#......... +..............................................#...........................................................................#................. +...#.........................#.....................................................................#........................................ +.........................................................#...................................#........................................#..... +.........#....................................................#........#................#.....................#............................. +........................#..........................................................................................#........................ +..............#...................................#............................#.............................................#.....#........ +.................................#................................................................#......................................... +..................#........................#...............#................................#.............#.............................#... +..........#........................................................#............................................................#........... +..#.....................................................................................#.............................#..................... +..........................#..........#..................................#......................................#...........#................ +................................................................#........................................................................... +................#................................#..........................#............................................................... +...........................................#..................................................#.....#.......#............................... +..........#............................................#.....................................................................#.............. +.......................#....................................#...........................................................#..............#.... +.....#.........................#........#..............................................#.................#........................#......... +.................#....................................................#............................................#........................ +....................................#...............#................................................#...................................... +.........................#........................................#................#.......#...................#............................ +....................................................................................................................................#....... +...........#...................................#...........................#......................#.........................#..............# +..................................#......#.................................................................................................. +......#....................#.........................#..................................................#................................... +......................#...................................#......#.....#.................................................#.................. +..............#..............................................................................#.................#.....................#...... +............................................................................................................................................ +#.............................#................#....................................................................#....................#.. +............................................................................................................................................ +........#...........................................#....................................................................................... +...................................................................#..........#..................#.......................................... +.....................#............#........#.............................................#............#................#.................... +................#............................................................................................................#.............. +...............................................................................................................#...................#........ +........................#......#..........................#.....#......#................................................................#... +................................................#..........................................#.............#.................................. +......................................#..............................................................................#...................... +..#.................................................................#.........#............................................................. +..........#.....#...............................................................................................#...........#............... +.................................#....................................................................#..............................#...... +.....................................................#........................................#........................#.................... +..........................................#..................#............#................................................................. +......................#...............................................................#..................#.........#............#........... +......#........................................................................#............................................................ +...................................#...................................#..................................................#................. +...................................................#.........................................#.................#............................ +...........................#...................................#.....................................................................#...... +........................................#................................................................................................... +................#......................................#........................#.........#.....#........................................... +...........#......................#.....................................................................#................................... +....#....................#...................#...................#................................................#...........#...........#. +............................................................................................................................................ +............................................................................................................................................ +.....................................#....................................#................................................................. +.......................#..................#........#............................#................#.............#............................ +...............#...............................................#......................#..............................................#...... +............................................................................................................................................ +.................................#...........#.....................#....................................................#................... +............................................................................................................................................ +..#........#..........#...............#..................................................................#..........#......................# +............................................................#............................#.................................................. +.................................................................#..........#.................................#.................#........... +.............................#......................#....................................................................................... +....#..................................................................................................................................#.... +...................#...............#..................................................#....................#................................ +.........................................................#......................................#...................#..............#........ +.....................................................................#...................................................#.................. +........................#...................................................................#..................#............................ +...#.....#...............................#....................#............................................................................. +................#...............#........................................................................................................... +.....................#........................................................#.....................................................#....... +..............................................#.......#................#...............................................#...................# +.........................................................................................#...............#...................#.............. +.#...........................#.......#...................................................................................................... +.............................................................................................................#........................#..... +.................................#.......................................................................................................... +...........#........................................................................................#.............................#......... +................#...............................................#............................#...................#.......................... +...#............................................................................#........................................................... +....................................................#.......#.......#......#...........................................................#.... +.........................#.................................................................................................................. +....................#...............#.........................................................................................#............. +.........................................................#....................#......................#...................................... +....#........................#...............#........................#...............#..................................................... +..........#......#....................................................................................................................#..... +..............................................................................................#...................#......................... +.......................................................#...................................................#.................#.............. +..........................#....................#............................................................................................ +........................................#......................#.........#...............#.......#......................#.........#.....#... +...............................#....................................#....................................................................... +........#...........#.............................#......................................................................................... +...#...........#...................................................................#..................#............#........................ +...........................#..............................#...................................#............................................. +...................................#.............................#..........................................#............................... +..............................................#........................#........#..........................................#................ +.......................................................................................#.............................#..............#....... +.........................................#.................................................................................................. +.....#............#......................................#.....#............................#............................................... +............................#............................................................................................................... +.......................#.........#........................................#..............................#...............................#.. +..#...........#.......................................#.........................#........................................................... +.......................................#.........................#.......................................................................... +............................................................#............................................................................... +.......#......................#........................................#................................................#..............#.... +..............................................................................................................#............................. +...............................................#.....................................................#...................................... +......................#..................................................................#......#.................................#.......#. +..#.......#................#......#..................................#.......#.....................................#........................ +................#...............................................#..................#....................#..................#................ diff --git a/lib/puzzles/2023/day11/part1.rb b/lib/puzzles/2023/day11/part1.rb new file mode 100644 index 0000000..30daf64 --- /dev/null +++ b/lib/puzzles/2023/day11/part1.rb @@ -0,0 +1,130 @@ +# frozen_string_literal: true + +module AdventOfCode + module Puzzles2023 + module Day11 + ## + # Class for solving Day 11 - Part 1 puzzle + class Part1 + ## + # @param file [String] The input file + def initialize(file: nil) + file ||= "#{File.dirname(__FILE__)}/input.txt" + parse_file(file) + end + + ## + # Get the sum of the minimum space between galaxies. + # + # @return [Integer] The sum of the space between galaxies + def answer + galaxies = find_galaxies + pairs = make_pairs(galaxies.size) + get_distances(pairs, galaxies).sum + end + + protected + + ## + # The symbol for a galaxy. + # @return [Symbol] The symbol for a galaxy + GALAXY_SYMBOL = :"#" + + ## + # The space. + # @return [Array>] The space + attr_reader :space + + ## + # Parse the input file. + # + # @param file [String] The input file + def parse_file(file) + data = [] + File.readlines(file, chomp: true).each do |line| + data << line.chars.map(&:to_sym) + end + + @space = expand_space(data) + end + + ## + # Expand all empty space in the data. + # + # @param data [Array>] The data + # + # @return [Array>] The expanded data + def expand_space(data) + data = expand_empty_space(data) + data = expand_empty_space(data.transpose) + data.transpose + end + + ## + # Find all of the galaxies in the space. + # + # @return [Array>] The galaxies + def find_galaxies + galaxies = [] + space.each_with_index do |row, row_id| + row.each_with_index do |cell, col_id| + galaxies << [row_id, col_id] if cell == GALAXY_SYMBOL + end + end + galaxies + end + + ## + # Make all of the pairs of galaxies. + # + # @param number_galaxies [Integer] The number of galaxies + # + # @return [Array>] The pairs of galaxies + def make_pairs(number_galaxies) + (0...number_galaxies).to_a.combination(2).to_a + end + + ## + # Get the distances between all of the pairs of galaxies. + # + # @param pairs [Array>] The pairs of galaxies + # @param galaxies [Array>] The galaxies + # + # @return [Array] The distances + def get_distances(pairs, galaxies) + pairs.map do |pair| + get_distance(galaxies[pair[0]], galaxies[pair[1]]) + end + end + + ## + # Get the distance between two galaxies. + # + # @param galaxy1 [Array] The first galaxy + # @param galaxy2 [Array] The second galaxy + # + # @return [Integer] The distance between the two galaxies + def get_distance(galaxy1, galaxy2) + (galaxy2[0] - galaxy1[0]).abs + (galaxy2[1] - galaxy1[1]).abs + end + + private + + ## + # Expand all empty rows in the data. + # + # @param data [Array>] The data + # + # @return [Array>] The expanded data + def expand_empty_space(data) + new_data = [] + data.each do |row| + new_data << row + new_data << row unless row.include?(GALAXY_SYMBOL) + end + new_data + end + end + end + end +end diff --git a/lib/puzzles/2023/day11/part2.rb b/lib/puzzles/2023/day11/part2.rb new file mode 100644 index 0000000..b174a39 --- /dev/null +++ b/lib/puzzles/2023/day11/part2.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +require_relative "part1" + +module AdventOfCode + module Puzzles2023 + module Day11 + ## + # Class for solving Day 11 - Part 2 puzzle + class Part2 < Part1 + ## + # @param file [String] The input file + # @param expansion_rate [Integer] The expansion rate + def initialize(file: nil, expansion_rate: Integer(1E06)) + super(file:) + @expansion_rate = expansion_rate + end + + protected + + ## + # The expansion rate for empty space. + # @return [Integer] The expansion rate for empty space + attr_reader :expansion_rate + + ## + # The regions that have been expanded. + # @return [Hash>] The regions that have been expanded + attr_reader :expanded_regions + + ## + # Expand all empty space in the data. + # This method does not change the data itself, + # but it does keep track of the expanded regions. + # + # @param data [Array>] The raw data + # + # @return [Array>] The raw data + def expand_space(data) + @expanded_regions = { rows: Set.new, columns: Set.new } + data.each_with_index do |row, row_id| + expanded_regions[:rows] << row_id unless row.include?(GALAXY_SYMBOL) + end + data.transpose.each_with_index do |column, column_id| + expanded_regions[:columns] << column_id unless column.include?(GALAXY_SYMBOL) + end + + # Data remains unchanged + data + end + + ## + # Get the distance between two galaxies. + # This method takes into account the expansion rate. + # + # @param galaxy1 [Array] The first galaxy + # @param galaxy2 [Array] The second galaxy + # + # @return [Integer] The distance between the two galaxies + def get_distance(galaxy1, galaxy2) + number_of_rows = get_1d_distance(galaxy1, galaxy2, dimension: :rows) + number_of_columns = get_1d_distance(galaxy1, galaxy2, dimension: :columns) + number_of_rows + number_of_columns + end + + ## + # Get the distance between two galaxies in one dimension. + # This method takes into account the expansion rate. + # + # @param galaxy1 [Array] The first galaxy + # @param galaxy2 [Array] The second galaxy + # @param dimension [Symbol] The dimension to calculate the distance in + # + # @return [Integer] The distance between the two galaxies in the given dimension + def get_1d_distance(galaxy1, galaxy2, dimension:) + dim = dimension == :rows ? 0 : 1 + min, max = [galaxy1[dim], galaxy2[dim]].minmax + (min...max).inject(0) do |sum, position| + sum + (expanded_regions[dimension].include?(position) ? expansion_rate : 1) + end + end + end + end + end +end diff --git a/sig/puzzles/2023/day11.rbs b/sig/puzzles/2023/day11.rbs new file mode 100644 index 0000000..8ea9a69 --- /dev/null +++ b/sig/puzzles/2023/day11.rbs @@ -0,0 +1,39 @@ +module AdventOfCode + module Puzzles2023 + module Day11 + type galaxy = Array[Integer] + type data = Array[Array[Symbol]] + type pairs = Array[galaxy] + type positions = Array[galaxy] + + class Part1 + GALAXY_SYMBOL: Symbol + + attr_reader space: data + + def answer: -> Integer + + def parse_file: (String) -> void + + def expand_empty_space: (data) -> data + + def expand_space: (data) -> data + + def find_galaxies: -> positions + + def make_pairs: (Integer) -> pairs + + def get_distances: (pairs, positions) -> Array[Integer] + + def get_distance: (galaxy, galaxy) -> Integer + end + + class Part2 < Part1 + attr_reader expansion_rate: Integer + attr_reader expanded_regions: Hash[Symbol, Set[Integer]] + + def get_1d_distance: (galaxy, galaxy, Symbol) -> Integer + end + end + end +end diff --git a/sig/test/puzzles/2023/day11_test.rbs b/sig/test/puzzles/2023/day11_test.rbs new file mode 100644 index 0000000..00ac6a3 --- /dev/null +++ b/sig/test/puzzles/2023/day11_test.rbs @@ -0,0 +1,33 @@ +module AdventOfCode + module Test + module Puzzles2023 + module Day11 + class Part1Test < MiniTest::Test + def setup: -> untyped + + def teardown: -> untyped + + def test_answer_test_data_set: -> untyped + + def test_answer_input_set: -> untyped + end + + class Part2Test < MiniTest::Test + def setup: -> untyped + + def teardown: -> untyped + + def test_answer_test_data_set_expansion_rate2: -> untyped + + def test_answer_test_data_set_expansion_rate10: -> untyped + + def test_answer_test_data_set_expansion_rate100: -> untyped + + def test_answer_test_data_set_default_expansion_rate: -> untyped + + def test_answer_input_set: -> untyped + end + end + end + end +end diff --git a/test/puzzles/2023/day11/day11_test.rb b/test/puzzles/2023/day11/day11_test.rb new file mode 100644 index 0000000..09e28e6 --- /dev/null +++ b/test/puzzles/2023/day11/day11_test.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +require "test_helper" +require "puzzles/2023/day11/day11" + +module AdventOfCode + module Test + module Puzzles2023 + module Day11 + # Tests Day 11 - Part 1 + class Part1Test < Minitest::Test + def setup + # Do nothing + end + + def teardown + # Do nothing + end + + def test_answer_test_data_set + input_file = "#{File.dirname(__FILE__)}/test_data.txt" + puzzle = AdventOfCode::Puzzles2023::Day11::Part1.new(file: input_file) + + assert_equal 374, puzzle.answer + end + + def test_answer_input_set + puzzle = AdventOfCode::Puzzles2023::Day11::Part1.new + + assert_equal 10_313_550, puzzle.answer + end + end + + # Tests Day 10 - Part 2 + class Part2Test < Minitest::Test + def setup + # Do nothing + end + + def teardown + # Do nothing + end + + def test_answer_test_data_set_expansion_rate2 + input_file = "#{File.dirname(__FILE__)}/test_data.txt" + puzzle = AdventOfCode::Puzzles2023::Day11::Part2.new(file: input_file, expansion_rate: 2) + + assert_equal 374, puzzle.answer + end + + def test_answer_test_data_set_expansion_rate10 + input_file = "#{File.dirname(__FILE__)}/test_data.txt" + puzzle = AdventOfCode::Puzzles2023::Day11::Part2.new(file: input_file, expansion_rate: 10) + + assert_equal 1_030, puzzle.answer + end + + def test_answer_test_data_set_expansion_rate100 + input_file = "#{File.dirname(__FILE__)}/test_data.txt" + puzzle = AdventOfCode::Puzzles2023::Day11::Part2.new(file: input_file, expansion_rate: 100) + + assert_equal 8410, puzzle.answer + end + + def test_answer_test_data_set_default_expansion_rate + input_file = "#{File.dirname(__FILE__)}/test_data.txt" + puzzle = AdventOfCode::Puzzles2023::Day11::Part2.new(file: input_file) + + assert_equal 82_000_210, puzzle.answer + end + + def test_answer_input_set + puzzle = AdventOfCode::Puzzles2023::Day11::Part2.new + + assert_equal 611_998_089_572, puzzle.answer + end + end + end + end + end +end diff --git a/test/puzzles/2023/day11/test_data.txt b/test/puzzles/2023/day11/test_data.txt new file mode 100644 index 0000000..986aad4 --- /dev/null +++ b/test/puzzles/2023/day11/test_data.txt @@ -0,0 +1,10 @@ +...#...... +.......#.. +#......... +.......... +......#... +.#........ +.........# +.......... +.......#.. +#...#.....