|
| 1 | +defmodule AdventOfCode.Y2016.Day11 do |
| 2 | + @moduledoc """ |
| 3 | + --- Day 11: Radioisotope Thermoelectric Generators --- |
| 4 | + Problem Link: https://adventofcode.com/2016/day/11 |
| 5 | + Difficulty: l |
| 6 | + Tags: simulation graph state-space manual-parse |
| 7 | + """ |
| 8 | + alias Yog.Traversal.Implicit |
| 9 | + |
| 10 | + def run(_input \\ "") do |
| 11 | + # Manual parse based on 2016_11.txt |
| 12 | + # Floor 1: strontium(S) G,M, plutonium(P) G,M |
| 13 | + # Floor 2: thulium(T) G, ruthenium(R) G,M, curium(C) G,M |
| 14 | + # Floor 3: thulium(T) M |
| 15 | + # Floor 4: - |
| 16 | + # State: {elevator_floor, sorted_pairs} where pair = {m_floor, g_floor} |
| 17 | + p1_initial = {1, sort_pairs([{1, 1}, {1, 1}, {3, 2}, {2, 2}, {2, 2}])} |
| 18 | + |
| 19 | + # Part 2: add Elerium and Dilithium pairs on floor 1 |
| 20 | + p2_initial = {1, sort_pairs([{1, 1}, {1, 1} | elem(p1_initial, 1)])} |
| 21 | + |
| 22 | + {solve(p1_initial), solve(p2_initial)} |
| 23 | + end |
| 24 | + |
| 25 | + defp solve(initial_state) do |
| 26 | + Implicit.implicit_fold( |
| 27 | + from: initial_state, |
| 28 | + using: :breadth_first, |
| 29 | + initial: nil, |
| 30 | + successors_of: fn state -> generate_successors(state) end, |
| 31 | + with: fn |
| 32 | + _acc, {4, pairs}, meta -> |
| 33 | + if Enum.all?(pairs, fn {m, g} -> m == 4 and g == 4 end) do |
| 34 | + {:halt, meta.depth} |
| 35 | + else |
| 36 | + {:continue, nil} |
| 37 | + end |
| 38 | + |
| 39 | + _acc, _state, _meta -> |
| 40 | + {:continue, nil} |
| 41 | + end |
| 42 | + ) |
| 43 | + end |
| 44 | + |
| 45 | + defp generate_successors({floor, pairs}) do |
| 46 | + # Pruning: Don't go back down to empty floors |
| 47 | + lowest_occupied = |
| 48 | + Enum.find(1..4, fn f -> |
| 49 | + Enum.any?(pairs, fn {m, g} -> m == f or g == f end) |
| 50 | + end) || 4 |
| 51 | + |
| 52 | + next_floors = for f <- [floor - 1, floor + 1], f in 1..4, f >= lowest_occupied, do: f |
| 53 | + |
| 54 | + items = |
| 55 | + for {pair, idx} <- Enum.with_index(pairs), |
| 56 | + type <- [:m, :g], |
| 57 | + item_floor(pair, type) == floor, |
| 58 | + do: {idx, type} |
| 59 | + |
| 60 | + one_item = for item <- items, do: [item] |
| 61 | + two_items = for i1 <- items, i2 <- items, i1 < i2, do: [i1, i2] |
| 62 | + |
| 63 | + for nf <- next_floors, |
| 64 | + load <- one_item ++ two_items, |
| 65 | + new_pairs = move(pairs, nf, load), |
| 66 | + valid?(new_pairs), |
| 67 | + do: {nf, sort_pairs(new_pairs)} |
| 68 | + end |
| 69 | + |
| 70 | + defp item_floor({m, _g}, :m), do: m |
| 71 | + defp item_floor({_m, g}, :g), do: g |
| 72 | + |
| 73 | + defp move(pairs, next_floor, items_to_move) do |
| 74 | + Enum.reduce(items_to_move, pairs, fn {idx, type}, acc -> |
| 75 | + List.update_at(acc, idx, fn {m, g} -> |
| 76 | + if type == :m, do: {next_floor, g}, else: {m, next_floor} |
| 77 | + end) |
| 78 | + end) |
| 79 | + end |
| 80 | + |
| 81 | + defp sort_pairs(pairs), do: Enum.sort(pairs) |
| 82 | + |
| 83 | + defp valid?(pairs) do |
| 84 | + Enum.all?(1..4, fn floor -> |
| 85 | + has_generator = Enum.any?(pairs, fn {_m, g} -> g == floor end) |
| 86 | + |
| 87 | + if has_generator do |
| 88 | + Enum.all?(pairs, fn {m, g} -> |
| 89 | + if m == floor, do: g == floor, else: true |
| 90 | + end) |
| 91 | + else |
| 92 | + true |
| 93 | + end |
| 94 | + end) |
| 95 | + end |
| 96 | +end |
0 commit comments