Tidy up Day 16 Part 2

Fewer maps, simpler variable names, break up complex function
This commit is contained in:
Adam Millerchip 2020-12-17 11:50:19 +09:00
parent 343d2ef962
commit cd95b1713c

View file

@ -5,9 +5,11 @@ defmodule Day16Part2 do
tickets
|> Enum.filter(&valid?(&1, rules))
|> transpose_tickets()
|> identify_columns(rules)
|> Enum.filter(&match?({"departure" <> _, _idx}, &1))
|> Enum.reduce(1, fn {_name, idx}, acc -> my_ticket[idx] * acc end)
|> valid_fields_for_columns(rules)
|> reduce_to_column_by_field_name()
|> Stream.filter(&match?({"departure" <> _, _idx}, &1))
|> Stream.map(fn {_name, idx} -> my_ticket[idx] end)
|> Enum.reduce(&Kernel.*/2)
|> IO.puts()
end
@ -22,7 +24,6 @@ defmodule Day16Part2 do
def transpose_tickets(tickets, columns, field_index, stop) do
{remaining_columns, column} = next_column(tickets)
transpose_tickets(remaining_columns, [{field_index, column} | columns], field_index + 1, stop)
end
@ -30,33 +31,27 @@ defmodule Day16Part2 do
Enum.map_reduce(tickets, [], fn [field | rest], column -> {rest, [field | column]} end)
end
# Probably overcomplicated this.
# First find which columns satisfy which rules.
# The number of valid fields for each column is unique, which doesn't seem like a coincidence.
# Then allocate the columns to fields starting from the column with the smallest number
# of valid columns - need to filter out the known values for the rest of the columns.
# It's the final filtering part that makes me think I'm missing a trick.
def identify_columns(transposed_tickets, rules) do
{field_map, by_valid_count} =
Enum.reduce(transposed_tickets, {%{}, %{}}, fn {id, column}, {field_map, by_valid_count} ->
names = names_for(column, rules)
valid_count = length(names)
{Map.put(field_map, id, names), Map.put(by_valid_count, valid_count, id)}
end)
{identified, _seen} =
by_valid_count
|> Enum.sort(fn {k1, _}, {k2, _} -> k1 <= k2 end)
|> Enum.reduce({%{}, MapSet.new()}, fn {_, col}, {identified, seen} ->
field = field_map[col] |> MapSet.new() |> MapSet.difference(seen) |> Enum.at(0)
{Map.put(identified, field, col), MapSet.put(seen, field)}
end)
identified
# Compare each column against the rules to determine the list of fields each column satisfies
def valid_fields_for_columns(transposed_tickets, rules) do
for {idx, column} <- transposed_tickets, do: {idx, valid_fields_for(column, rules)}
end
def names_for(column, rules) do
# Reduce the list of valid fields by elimination.
# The number of valid fields for each column is unique, which doesn't seem like a coincidence.
# Start with the column that only has one possible valid field, mark the field as seen.
# Continue to the column with two valid fields, subtract the seen field, mark remaining field as seen.
# Repeat for all columns.
def reduce_to_column_by_field_name(valid_fields_by_column) do
valid_fields_by_column
|> Enum.sort_by(fn {_k, v} -> length(v) end)
|> Enum.reduce({[], []}, fn {col, fields}, {identified, seen} ->
[field] = fields -- seen
{[{field, col} | identified], [field | seen]}
end)
|> elem(0)
end
def valid_fields_for(column, rules) do
Enum.filter(rules, fn {_field, {range1, range2}} ->
Enum.all?(column, fn val -> val in range1 || val in range2 end)
end)