50 lines
1.5 KiB
Elixir
50 lines
1.5 KiB
Elixir
defmodule Day7Part1 do
|
|
@bag_spec ~r/\A(\d) (\w+ \w+).*\z/
|
|
|
|
def run do
|
|
File.read!("input")
|
|
|> input_to_tree()
|
|
|> invert_tree()
|
|
|> count_ancestors("shiny gold")
|
|
|> IO.puts()
|
|
end
|
|
|
|
def count_ancestors(itree, name), do: count_ancestors([name], MapSet.new(), itree)
|
|
|
|
def count_ancestors([], bags, _itree), do: Enum.count(bags)
|
|
|
|
def count_ancestors([name | rest], bags, itree) do
|
|
case Map.fetch(itree, name) do
|
|
:error -> count_ancestors(rest, bags, itree)
|
|
{:ok, names} -> count_ancestors(names ++ rest, MapSet.union(bags, MapSet.new(names)), itree)
|
|
end
|
|
end
|
|
|
|
# %{"shiny gold" => ["pale magenta", "striped tomato", ...]}
|
|
def invert_tree(tree) do
|
|
Enum.reduce(tree, %{}, fn {parent, children}, inverted_tree ->
|
|
Enum.reduce(children, inverted_tree, fn {_n, child}, inverted_tree ->
|
|
Map.update(inverted_tree, child, [parent], &[parent | &1])
|
|
end)
|
|
end)
|
|
end
|
|
|
|
# %{"shiny gold" => [{5, "pale brown"}, {2, "light red"}, {3, "drab lime"}]}
|
|
def input_to_tree(input) do
|
|
input
|
|
|> String.split(".\n", trim: true)
|
|
|> Enum.map(&String.split(&1, " bags contain "))
|
|
|> Map.new(fn [parent, child_spec] -> {parent, parse_children(child_spec)} end)
|
|
end
|
|
|
|
def parse_children("no other bags"), do: []
|
|
|
|
def parse_children(child_spec) do
|
|
child_spec
|
|
|> String.split(", ")
|
|
|> Enum.map(&Regex.run(@bag_spec, &1, capture: :all_but_first))
|
|
|> Enum.map(fn [count, type] -> {String.to_integer(count), type} end)
|
|
end
|
|
end
|
|
|
|
Day7Part1.run()
|