Year 2021, Day 08
Section
Input
input = Day08.test_input()
Split to individual codes
input = String.split(input, [" ", " | ", "\n"], trim: true)
Split every code to characters and sort it so it is easier to work with
input = Enum.map(input, &(&1 |> String.graphemes() |> Enum.sort()))
Split it to the original lines (10 signals + 4 output = 14)
input = Enum.chunk_every(input, 14)
Split every line to signal and output
input = Enum.map(input, &Enum.chunk_every(&1, 10))
Sort signals by length and output as Tuple instead of list
input =
Enum.map(input, fn [signal, output] ->
{Enum.sort_by(signal, &length/1), output}
end)
Part 1
Get just the outputs in one flat list
part1 = Enum.flat_map(input, &elem(&1, 1))
Count just outputs with specified lengths
Enum.count(part1, &(length(&1) in [2, 3, 4, 7]))
Part 2
Parse signal to determine mapping of digits. These are the rules:
- 2 segments is one
- 3 segments is seven
- 4 segments is four
-
5 segments is two, three or five
- three contains all segments from one
- two has exactly 2 segments from four
- five has exactly 3 segments from four
-
6 segments is zero, six or nine
- nine has all segments from four
- zero has all segments from one
- six doesn’t match any of previous rules
- 7 segments is eight
Produces map with shape:
%{
1 => 'ab',
2 => '...',
...
}
defmodule Display do
def find_digit(data, acc) when length(data) == 2, do: Map.put(acc, 1, data)
def find_digit(data, acc) when length(data) == 3, do: Map.put(acc, 7, data)
def find_digit(data, acc) when length(data) == 4, do: Map.put(acc, 4, data)
def find_digit(data, acc) when length(data) == 7, do: Map.put(acc, 8, data)
def find_digit(data, %{1 => one, 4 => four} = acc) when length(data) == 5 do
cond do
# three contains all segments of one
Enum.all?(one, &(&1 in data)) ->
Map.put(acc, 3, data)
# two contains 2 segments from four
Enum.count(data, &(&1 in four)) == 2 ->
Map.put(acc, 2, data)
# five contains 3 segments from four
:otherwise ->
Map.put(acc, 5, data)
end
end
def find_digit(data, %{1 => one, 4 => four} = acc) when length(data) == 6 do
cond do
# nine contains all segments from four
Enum.all?(four, &(&1 in data)) ->
Map.put(acc, 9, data)
# zero contains all segments from one
Enum.all?(one, &(&1 in data)) ->
Map.put(acc, 0, data)
# six is the only 6 segment number left
:otherwise ->
Map.put(acc, 6, data)
end
end
end
Split input to signals and outputs
[signals, outputs] =
Enum.reduce(input, [[], []], fn {s, o}, [sig, out] ->
[[s | sig], [o | out]]
end)
Convert signal data to mapping data
mappings =
Enum.map(signals, fn signal ->
Enum.reduce(signal, %{}, &Display.find_digit/2)
end)
Reverse the mapping key and value
mappings = Enum.map(mappings, &Enum.map(&1, fn {k, v} -> {v, k} end))
Covert the number to string so we can join it easily later
mappings = Enum.map(mappings, &Enum.map(&1, fn {k, v} -> {k, Integer.to_string(v)} end))
Convert mapping back to map
mappings = Enum.map(mappings, &Enum.into(&1, %{}))
Zip the mappings and outputs back together
part2 = Enum.zip(mappings, outputs)
Apply the mapping to the output
part2 =
Enum.map(part2, fn {mapping, output} ->
Enum.map(output, &Map.get(mapping, &1))
end)
Join the numbers to single string
part2 = Enum.map(part2, &Enum.join/1)
Convert the output from string to integer
part2 = Enum.map(part2, &String.to_integer/1)
Sum the outputs to get the final answer
Enum.sum(part2)