diff --git a/day16/day16part2.exs b/day16/day16part2.exs index 02b0f3a..8c697b5 100644 --- a/day16/day16part2.exs +++ b/day16/day16part2.exs @@ -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)