From c5945a1a486db2eb89b52139cb0a7e8d549d5530 Mon Sep 17 00:00:00 2001 From: red Date: Sat, 9 Mar 2024 23:38:06 +0100 Subject: [PATCH] Year 2016: Day 11 --- README.md | 2 +- spec/year_2016/day_11_input | 4 ++ spec/year_2016/day_11_sample_one | 4 ++ spec/year_2016/day_11_spec.rb | 34 +++++++++ year_2016.md | 17 +++++ year_2016/day_11.rb | 116 +++++++++++++++++++++++++++++++ 6 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 spec/year_2016/day_11_input create mode 100644 spec/year_2016/day_11_sample_one create mode 100644 spec/year_2016/day_11_spec.rb create mode 100644 year_2016/day_11.rb diff --git a/README.md b/README.md index 49d4b90..7578901 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ I'm also adding notes that may be useful if you're learning Ruby. Notes for solving: * [2015, complete](year_2015.md) -* [2016, up to day 08](year_2016.md) +* [2016, up to day 11](year_2016.md) * [2023, up to day 04](year_2023.md) # How to use diff --git a/spec/year_2016/day_11_input b/spec/year_2016/day_11_input new file mode 100644 index 0000000..e82fca5 --- /dev/null +++ b/spec/year_2016/day_11_input @@ -0,0 +1,4 @@ +The first floor contains a polonium generator, a thulium generator, a thulium-compatible microchip, a promethium generator, a ruthenium generator, a ruthenium-compatible microchip, a cobalt generator, and a cobalt-compatible microchip. +The second floor contains a polonium-compatible microchip and a promethium-compatible microchip. +The third floor contains nothing relevant. +The fourth floor contains nothing relevant. diff --git a/spec/year_2016/day_11_sample_one b/spec/year_2016/day_11_sample_one new file mode 100644 index 0000000..0a01c5e --- /dev/null +++ b/spec/year_2016/day_11_sample_one @@ -0,0 +1,4 @@ +The first floor contains a hydrogen-compatible microchip and a lithium-compatible microchip. +The second floor contains a hydrogen generator. +The third floor contains a lithium generator. +The fourth floor contains nothing relevant. diff --git a/spec/year_2016/day_11_spec.rb b/spec/year_2016/day_11_spec.rb new file mode 100644 index 0000000..a76ee8b --- /dev/null +++ b/spec/year_2016/day_11_spec.rb @@ -0,0 +1,34 @@ +require 'year_2016/day_11' + +describe Year2016::Day11 do + context 'when Part 1' do + subject(:sample_one) do + described_class.new(File.read('spec/year_2016/day_11_sample_one'), true) + end + + it 'checks for safe states' do + %w(01020 11120 22220 12120 02020 12121 22222 32323 22223 33233 23232).each do |input| + input = input.split.map(&:to_i) + expect(described_class).to be_state_is_safe(input) + end + end + + it 'gives a final result' do + expect(sample_one.solve).to eq(11) + end + end + + context 'when Results' do + subject(:input_data) do + File.read('spec/year_2016/day_11_input') + end + + it 'correctly answers part 1' do + expect(described_class.new(input_data, true).solve).to eq(47) + end + + it 'correctly answers part 2', skip: 'Test is too slow for CI' do + expect(described_class.new(input_data).solve).to eq(71) + end + end +end diff --git a/year_2016.md b/year_2016.md index 8cb3018..9328739 100644 --- a/year_2016.md +++ b/year_2016.md @@ -144,3 +144,20 @@ Year2016::Day10 ``` This one is a bit similar to the [Dining philosophers problem](https://en.wikipedia.org/wiki/Dining_philosophers_problem) in idea, but we are far from the concept, since the original exercise is all about threading and async. + + +## Day 11: Radioisotope Thermoelectric Generators + +``` +Year2016::Day11 + when Part 1 + checks for safe states + gives a final result + when Results + correctly answers part 1 + correctly answers part 2 (PENDING: Test is too slow for CI) +``` + +It seems this exercise got [some people to give up](https://markheath.net/post/aoc-2016-day11), and quite frankly, I felt like it for a while too. In the end, brute-forcing wasn't efficient, but trying a proper implementation of [A* (A-Star)](https://en.wikipedia.org/wiki/A*_search_algorithm) will solve it in a good time. + +Now, as for part two... I hope you got twelve minutes to run tests. diff --git a/year_2016/day_11.rb b/year_2016/day_11.rb new file mode 100644 index 0000000..86ca53d --- /dev/null +++ b/year_2016/day_11.rb @@ -0,0 +1,116 @@ +class Year2016 + class Day11 + class << self + # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/BlockLength + def next_moves(state) + moves = [] + state.each_with_index do |item, idx| + next if idx.zero? + next if item != state[0] + + if state[0] < 3 + ns = state.dup + ns[0] += 1 + ns[idx] += 1 + moves.push(ns) if state_is_safe?(ns) + state.each_with_index do |item_in, idx_in| + next if idx_in <= idx + next if item_in != state[0] + + ns = state.dup + ns[0] += 1 + ns[idx] += 1 + ns[idx_in] += 1 + moves.push(ns) if state_is_safe?(ns) + end + end + + next unless state[0] >= 1 + + ns = state.dup + ns[0] -= 1 + ns[idx] -= 1 + moves.push(ns) if state_is_safe?(ns) + state.each_with_index do |item_in, idx_in| + next if idx_in <= idx + next if item_in != state[0] + + ns = state.dup + ns[0] -= 1 + ns[idx] -= 1 + ns[idx_in] -= 1 + moves.push(ns) if state_is_safe?(ns) + end + end + moves + end + # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/BlockLength + + def state_is_safe?(state) + pairs = state[1..].each_slice(2) + pairs.each_with_index do |pair_out, idx_out| + next if pair_out[0] == pair_out[1] + + pairs.each_with_index do |pair_in, idx_in| + next if idx_in == idx_out + return false if pair_out[0] == pair_in[1] + end + end + true + end + end + + # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity + def initialize(input_file, input_part_one = false) + @version = input_part_one ? 1 : 2 + @alltypes = [] + @chips = {} + @gens = {} + input_file.chomp.split("\n").each_with_index do |input_line, idx| + next if input_line.match?(/contains nothing relevant.$/) + + input_line.scan(/(\w+)-compatible/).flatten.each do |chip| + @chips[chip] = idx + end + input_line.scan(/(\w+) generator/).flatten.each do |gen| + @gens[gen] = idx + end + end + unless input_part_one + @chips['elerium'] = 0 + @gens['elerium'] = 0 + @chips['dilithium'] = 0 + @gens['dilithium'] = 0 + end + @alltypes = [[@chips.keys, @gens.keys].flatten.uniq * 2].flatten.sort + @alltypes = ['E', @alltypes].flatten + @initial_state = @alltypes.map.each_with_index do |type, idx| + next 0 if type == 'E' + + (idx.odd? ? @chips : @gens)[type] + end + @cnt = 0 + end + # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity + + def search(states, moves_tried, depth) + # puts "DEPTH: #{depth} => #{states.length} (#{moves_tried.length})" + return depth if states.any?{|state| state.all?(3) } + + next_states = [] + states.sort.reverse_each do |state| + next if moves_tried.include?(state) + + moves_tried.add(state) + Day11.next_moves(state).each do |next_state| + next_states.push(next_state) unless moves_tried.include?(next_state) + end + end + search(next_states, moves_tried, depth + 1) + end + + def solve + search([@initial_state], Set.new, 0) + end + end +end